mirror of
https://github.com/codex-team/editor.js
synced 2024-06-10 18:03:25 +02:00
Inline Toolbar moving (#258)
* Inline Toolbar moving * simplify code * Check is need to show Inline Toolbar * remove duplicate from doc * fix doc * open/close IT * Close IT by clicks on Redactor * @guryn going strange Co-Authored-By: Taly <vitalik7tv@yandex.ru>
This commit is contained in:
parent
dbb4cd6f8f
commit
cba999a77d
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -20,7 +20,9 @@ Method that specifies how to merge two `Blocks` of the same type, for example on
|
|||
Method does accept data object in same format as the `Render` and it should provide logic how to combine new
|
||||
data with the currently stored value.
|
||||
|
||||
### Available settings
|
||||
### Internal Tool Settings
|
||||
|
||||
Options that Tool can specify. All settings should be passed as static properties of Tool's class.
|
||||
|
||||
| Name | Type | Default Value | Description |
|
||||
| -- | -- | -- | -- |
|
||||
|
@ -29,4 +31,34 @@ data with the currently stored value.
|
|||
| `irreplaceable` | _Boolean_ | `false` | By default, **empty** `Blocks` can be **replaced** by other `Blocks` with the `Toolbox`. Some tools with media-content may prefer another behaviour. Pass `true` and `Toolbox` will add a new block below yours. |
|
||||
| `contentless` | _Boolean_ | `false` | Pass `true` for Tool which represents decorative empty `Blocks` |
|
||||
|
||||
### User configuration
|
||||
|
||||
All Tools can be configured by users. For this reason, we provide `toolConfig` option at the Editor Initial Settings.
|
||||
Unlike Internal Tool Settings, this options can be specified outside the Tool class,
|
||||
so users can set up different configurations for the same Tool.
|
||||
|
||||
```js
|
||||
var editor = new CodexEditor({
|
||||
holderId : 'codex-editor',
|
||||
initialBlock : 'text',
|
||||
tools: {
|
||||
text: Text // 'Text' Tool class for Blocks with type 'text'
|
||||
},
|
||||
toolsConfig: {
|
||||
text: { // user configuration for Blocks with type 'text'
|
||||
inlineToolbar : true,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
There are few options available by CodeX Editor.
|
||||
|
||||
| Name | Type | Default Value | Description |
|
||||
| -- | -- | -- | -- |
|
||||
| `enableLineBreaks` | _Boolean_ | `false` | With this option, CodeX Editor won't handle Enter keydowns. Can be helpful for Tools like `<code>` where line breaks should be handled by default behaviour. |
|
||||
| `inlineToolbar` | _Boolean/Array_ | `false` | Pass `true` to enable the Inline Toolbar with all Tools, or pass an array with specified Tools list |
|
||||
|
||||
|
||||
|
||||
### Sanitize
|
||||
|
|
|
@ -65,9 +65,10 @@
|
|||
text: Text
|
||||
},
|
||||
toolsConfig: {
|
||||
text: {
|
||||
inlineToolbar : true,
|
||||
},
|
||||
quote: {
|
||||
iconClassname : 'quote-icon',
|
||||
displayInToolbox : true,
|
||||
enableLineBreaks : true
|
||||
}
|
||||
},
|
||||
|
|
|
@ -12,146 +12,120 @@
|
|||
* @property {String} text — HTML content to insert to text element
|
||||
*
|
||||
*/
|
||||
|
||||
class Text {
|
||||
/**
|
||||
* Pass true to display this tool in the Editor's Toolbox
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static get displayInToolbox() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass true to display this tool in the Editor's Toolbox
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static get displayInToolbox() {
|
||||
/**
|
||||
* Class for the Toolbox icon
|
||||
* @returns {string}
|
||||
*/
|
||||
static get iconClassName() {
|
||||
return 'cdx-text-icon';
|
||||
}
|
||||
|
||||
return true;
|
||||
/**
|
||||
* Render plugin`s html and set initial content
|
||||
* @param {TextData} data — initial plugin content
|
||||
*/
|
||||
constructor(data = {}, config) {
|
||||
this._CSS = {
|
||||
wrapper: 'ce-text'
|
||||
};
|
||||
|
||||
this._data = {};
|
||||
this._element = this.draw();
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method fires before rendered data appended to the editors area
|
||||
*/
|
||||
appendCallback() {
|
||||
console.log("text appended");
|
||||
}
|
||||
|
||||
draw() {
|
||||
let div = document.createElement('DIV');
|
||||
|
||||
div.classList.add(this._CSS.wrapper);
|
||||
div.contentEditable = true;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create div element and add needed css classes
|
||||
* @returns {HTMLDivElement} Created DIV element
|
||||
*/
|
||||
render() {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge current data with passed data
|
||||
* @param {TextData} data
|
||||
*/
|
||||
merge(data) {
|
||||
let newData = {
|
||||
text : this.data.text + data.text
|
||||
};
|
||||
|
||||
this.data = newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if saved text is empty
|
||||
*
|
||||
* @param {TextData} savedData — data received from plugins`s element
|
||||
* @returns {boolean} false if saved text is empty, true otherwise
|
||||
*/
|
||||
validate(savedData) {
|
||||
if (savedData.text.trim() === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for the Toolbox icon
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
static get iconClassName() {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'cdx-text-icon';
|
||||
/**
|
||||
* Get plugin`s element HTMLDivElement
|
||||
* @param {HTMLDivElement} block - returned self content
|
||||
* @returns {HTMLDivElement} Plugin`s element
|
||||
*/
|
||||
save(block) {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Get current plugin`s data
|
||||
*
|
||||
* @todo sanitize data while saving
|
||||
*
|
||||
* @returns {TextData} Current data
|
||||
*/
|
||||
get data() {
|
||||
let text = this._element.innerHTML;
|
||||
|
||||
/**
|
||||
* Render plugin`s html and set initial content
|
||||
*
|
||||
* @param {TextData} data — initial plugin content
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
this._data.text = text;
|
||||
|
||||
this._CSS = {
|
||||
wrapper: 'ce-text'
|
||||
};
|
||||
return this._data;
|
||||
}
|
||||
|
||||
this._data = {};
|
||||
this._element = this.draw();
|
||||
/**
|
||||
* Set new data for plugin
|
||||
*
|
||||
* @param {TextData} data — data to set
|
||||
*/
|
||||
set data(data) {
|
||||
Object.assign(this._data, data);
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method fires before rendered data appended to the editors area
|
||||
*/
|
||||
appendCallback() {
|
||||
|
||||
console.log("text appended");
|
||||
|
||||
}
|
||||
|
||||
draw() {
|
||||
|
||||
let div = document.createElement('DIV');
|
||||
|
||||
div.classList.add(this._CSS.wrapper);
|
||||
div.contentEditable = true;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create div element and add needed css classes
|
||||
* @returns {HTMLDivElement} Created DIV element
|
||||
*/
|
||||
render() {
|
||||
|
||||
return this._element;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge current data with passed data
|
||||
* @param {TextData} data
|
||||
*/
|
||||
merge(data) {
|
||||
let newData = {
|
||||
text : this.data.text + data.text
|
||||
};
|
||||
|
||||
this.data = newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if saved text is empty
|
||||
*
|
||||
* @param {TextData} savedData — data received from plugins`s element
|
||||
* @returns {boolean} false if saved text is empty, true otherwise
|
||||
*/
|
||||
validate(savedData) {
|
||||
|
||||
if (savedData.text.trim() === '') {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin`s element HTMLDivElement
|
||||
* @param {HTMLDivElement} block - returned self content
|
||||
* @returns {HTMLDivElement} Plugin`s element
|
||||
*/
|
||||
save(block) {
|
||||
|
||||
return this.data;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current plugin`s data
|
||||
*
|
||||
* @todo sanitize data while saving
|
||||
*
|
||||
* @returns {TextData} Current data
|
||||
*/
|
||||
get data() {
|
||||
|
||||
let text = this._element.innerHTML;
|
||||
|
||||
this._data.text = text;
|
||||
|
||||
return this._data;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new data for plugin
|
||||
*
|
||||
* @param {TextData} data — data to set
|
||||
*/
|
||||
set data(data) {
|
||||
|
||||
Object.assign(this._data, data);
|
||||
|
||||
this._element.innerHTML = this._data.text || '';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
this._element.innerHTML = this._data.text || '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,4 +215,4 @@ export default class Block {
|
|||
this._html.classList.remove(Block.CSS.selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ export default class BlockManager extends Module {
|
|||
bindEvents(block) {
|
||||
this.Editor.Listeners.on(block.pluginsContent, 'keydown', (event) => this.Editor.Keyboard.blockKeydownsListener(event));
|
||||
this.Editor.Listeners.on(block.pluginsContent, 'mouseup', (event) => {
|
||||
this.Editor.InlineToolbar.move(event);
|
||||
this.Editor.InlineToolbar.handleShowingEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -246,10 +246,14 @@ export default class BlockManager extends Module {
|
|||
|
||||
/**
|
||||
* Get Block instance by html element
|
||||
* @param {HTMLElement} element
|
||||
* @param {Node} element
|
||||
* @returns {Block}
|
||||
*/
|
||||
getBlock(element) {
|
||||
if (!$.isElement(element)) {
|
||||
element = element.parentNode;
|
||||
}
|
||||
|
||||
let nodes = this._blocks.nodes,
|
||||
firstLevelBlock = element.closest(`.${Block.CSS.wrapper}`),
|
||||
index = nodes.indexOf(firstLevelBlock);
|
||||
|
@ -544,4 +548,4 @@ class Blocks {
|
|||
|
||||
return instance.get(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
* @version 2.0.0
|
||||
*/
|
||||
|
||||
import Selection from '../selection';
|
||||
|
||||
/**
|
||||
* @typedef {Caret} Caret
|
||||
*/
|
||||
import Selection from '../Selection';
|
||||
|
||||
export default class Caret extends Module {
|
||||
/**
|
||||
* @constructor
|
||||
|
|
|
@ -71,7 +71,7 @@ export default class Keyboard extends Module {
|
|||
* Don't handle Enter keydowns when Tool sets enableLineBreaks to true.
|
||||
* Uses for Tools like <code> where line breaks should be handled by default behaviour.
|
||||
*/
|
||||
if (toolsConfig && toolsConfig.enableLineBreaks) {
|
||||
if (toolsConfig && toolsConfig[this.Editor.Tools.apiSettings.IS_ENABLED_LINE_BREAKS]) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -146,4 +146,4 @@ export default class Keyboard extends Module {
|
|||
arrowLeftAndUpPressed() {
|
||||
this.Editor.BlockManager.navigatePrevious();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,23 +146,20 @@ export default class Listeners extends Module {
|
|||
* @return {Array}
|
||||
*/
|
||||
findAll(element, eventType, handler) {
|
||||
let foundAllListeners,
|
||||
foundByElements = [],
|
||||
foundByEventType = [],
|
||||
foundByHandler = [];
|
||||
let found,
|
||||
foundByElements = element ? this.findByElement(element) : [];
|
||||
// foundByEventType = eventType ? this.findByType(eventType) : [],
|
||||
// foundByHandler = handler ? this.findByHandler(handler) : [];
|
||||
|
||||
if (element)
|
||||
foundByElements = this.findByElement(element);
|
||||
if (element && eventType && handler) {
|
||||
found = foundByElements.filter( event => event.eventType === eventType && event.handler === handler );
|
||||
} else if (element && eventType) {
|
||||
found = foundByElements.filter( event => event.eventType === eventType);
|
||||
} else {
|
||||
found = foundByElements;
|
||||
}
|
||||
|
||||
if (eventType)
|
||||
foundByEventType = this.findByType(eventType);
|
||||
|
||||
if (handler)
|
||||
foundByHandler = this.findByHandler(handler);
|
||||
|
||||
foundAllListeners = foundByElements.concat(foundByEventType, foundByHandler);
|
||||
|
||||
return foundAllListeners;
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -175,4 +172,4 @@ export default class Listeners extends Module {
|
|||
|
||||
this.allListeners = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
/**
|
||||
* Inline toolbar with actions that modifies selected text fragment
|
||||
*
|
||||
* ________________________
|
||||
* | |
|
||||
* |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
|
||||
* | B i [link] [mark] |
|
||||
* | _______________________|
|
||||
*/
|
||||
declare var Module: any;
|
||||
declare var $: any;
|
||||
declare var _: any;
|
||||
import Selection from '../selection';
|
||||
|
||||
/**
|
||||
* DOM Elements
|
||||
*/
|
||||
interface InlineToolbarNodes {
|
||||
wrapper?: Element; // main wrapper
|
||||
interface INodes {
|
||||
wrapper?: HTMLElement; // main wrapper
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS
|
||||
*/
|
||||
interface InlineToolbarCSS {
|
||||
interface ICSS {
|
||||
inlineToolbar: string;
|
||||
inlineToolbarShowed: string;
|
||||
}
|
||||
|
||||
export default class InlineToolbar extends Module {
|
||||
|
@ -28,17 +30,23 @@ export default class InlineToolbar extends Module {
|
|||
/**
|
||||
* Inline Toolbar elements
|
||||
*/
|
||||
private nodes: InlineToolbarNodes = {
|
||||
wrapper: null,
|
||||
private nodes: INodes = {
|
||||
wrapper: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* CSS styles
|
||||
*/
|
||||
private CSS: InlineToolbarCSS = {
|
||||
inlineToolbar: 'ce-inline-toolbar',
|
||||
private CSS: ICSS = {
|
||||
inlineToolbar: 'ce-inline-toolbar',
|
||||
inlineToolbarShowed: 'ce-inline-toolbar--showed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Margin above/below the Toolbar
|
||||
*/
|
||||
private readonly toolbarVerticalMargin: number = 20;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
|
@ -62,7 +70,98 @@ export default class InlineToolbar extends Module {
|
|||
|
||||
}
|
||||
|
||||
public move() {
|
||||
// moving
|
||||
/**
|
||||
* Shows Inline Toolbar by keyup/mouseup
|
||||
* @param {KeyboardEvent|MouseEvent} event
|
||||
*/
|
||||
public handleShowingEvent(event): void {
|
||||
if (!this.allowedToShow(event)) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.move();
|
||||
this.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move Toolbar to the selected text
|
||||
*/
|
||||
public move(): void {
|
||||
|
||||
const selectionRect = Selection.rect;
|
||||
const wrapperOffset = this.Editor.UI.nodes.wrapper.getBoundingClientRect();
|
||||
|
||||
const newCoords = {
|
||||
x: selectionRect.x - wrapperOffset.left,
|
||||
y: selectionRect.y
|
||||
+ selectionRect.height
|
||||
// + window.scrollY
|
||||
- wrapperOffset.top
|
||||
+ this.toolbarVerticalMargin,
|
||||
};
|
||||
|
||||
/**
|
||||
* If we know selections width, place InlineToolbar to center
|
||||
*/
|
||||
if (selectionRect.width) {
|
||||
newCoords.x += Math.floor(selectionRect.width / 2);
|
||||
}
|
||||
|
||||
this.nodes.wrapper.style.left = Math.floor(newCoords.x) + 'px';
|
||||
this.nodes.wrapper.style.top = Math.floor(newCoords.y) + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows Inline Toolbar
|
||||
*/
|
||||
private open() {
|
||||
this.nodes.wrapper.classList.add(this.CSS.inlineToolbarShowed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides Inline Toolbar
|
||||
*/
|
||||
private close() {
|
||||
this.nodes.wrapper.classList.remove(this.CSS.inlineToolbarShowed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to show Inline Toolbar or not
|
||||
* @param {KeyboardEvent|MouseEvent} event
|
||||
*/
|
||||
private allowedToShow(event): boolean {
|
||||
/**
|
||||
* Tags conflicts with window.selection function.
|
||||
* Ex. IMG tag returns null (Firefox) or Redactors wrapper (Chrome)
|
||||
*/
|
||||
const tagsConflictsWithSelection = ['IMG', 'INPUT'];
|
||||
if (event && tagsConflictsWithSelection.includes(event.target.tagName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentSelection = Selection.get(),
|
||||
selectedText = Selection.text;
|
||||
|
||||
// old browsers
|
||||
if (!currentSelection || !currentSelection.anchorNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// empty selection
|
||||
if (currentSelection.isCollapsed || selectedText.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// is enabled by current Block's Tool
|
||||
const currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode);
|
||||
|
||||
if (!currentBlock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const toolConfig = this.config.toolsConfig[currentBlock.name];
|
||||
|
||||
return toolConfig && toolConfig[this.Editor.Tools.apiSettings.IS_ENABLED_INLINE_TOOLBAR];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,9 @@ export default class Toolbox extends Module {
|
|||
* @param {Tool} tool - tool class
|
||||
*/
|
||||
addTool(toolName, tool) {
|
||||
if (tool.displayInToolbox && !tool.iconClassName) {
|
||||
const api = this.Editor.Tools.apiSettings;
|
||||
|
||||
if (tool[api.IS_DISPLAYED_IN_TOOLBOX] && !tool[api.TOOLBAR_ICON_CLASS]) {
|
||||
_.log('Toolbar icon class name is missed. Tool %o skipped', 'warn', toolName);
|
||||
return;
|
||||
}
|
||||
|
@ -85,11 +87,11 @@ export default class Toolbox extends Module {
|
|||
/**
|
||||
* Skip tools that pass 'displayInToolbox=false'
|
||||
*/
|
||||
if (!tool.displayInToolbox) {
|
||||
if (!tool[api.IS_DISPLAYED_IN_TOOLBOX]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let button = $.make('li', [Toolbox.CSS.toolboxButton, tool.iconClassName], {
|
||||
let button = $.make('li', [Toolbox.CSS.toolboxButton, tool[api.TOOLBAR_ICON_CLASS]], {
|
||||
title: toolName
|
||||
});
|
||||
|
||||
|
@ -135,7 +137,7 @@ export default class Toolbox extends Module {
|
|||
* - block is not irreplaceable
|
||||
* @type {Array}
|
||||
*/
|
||||
if (!tool.irreplaceable && currentBlock.isEmpty) {
|
||||
if (!tool[this.Editor.Tools.apiSettings.IS_IRREPLACEBLE_TOOL] && currentBlock.isEmpty) {
|
||||
this.Editor.BlockManager.replace(toolName);
|
||||
} else {
|
||||
this.Editor.BlockManager.insert(toolName);
|
||||
|
@ -184,4 +186,4 @@ export default class Toolbox extends Module {
|
|||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
* @property {String} iconClassname - this a icon in toolbar
|
||||
* @property {Boolean} displayInToolbox - will be displayed in toolbox. Default value is TRUE
|
||||
* @property {Boolean} enableLineBreaks - inserts new block or break lines. Default value is FALSE
|
||||
* @property {Boolean|String[]} inlineToolbar - Pass `true` to enable the Inline Toolbar with all Tools, all pass an array with specified Tools list |
|
||||
* @property render @todo add description
|
||||
* @property save @todo add description
|
||||
* @property settings @todo add description
|
||||
|
@ -57,18 +58,31 @@ export default class Tools extends Module {
|
|||
return this.toolsUnavailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant for available Tools Settings
|
||||
* @return {object}
|
||||
*/
|
||||
get apiSettings() {
|
||||
return {
|
||||
TOOLBAR_ICON_CLASS: 'iconClassName',
|
||||
IS_DISPLAYED_IN_TOOLBOX: 'displayInToolbox',
|
||||
IS_ENABLED_LINE_BREAKS: 'enableLineBreaks',
|
||||
IS_IRREPLACEBLE_TOOL: 'irreplaceable',
|
||||
IS_ENABLED_INLINE_TOOLBAR: 'inlineToolbar',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Static getter for default Tool config fields
|
||||
*
|
||||
* @usage Tools.defaultConfig.displayInToolbox
|
||||
* @return {ToolConfig}
|
||||
*/
|
||||
static get defaultConfig() {
|
||||
get defaultConfig() {
|
||||
return {
|
||||
iconClassName : '',
|
||||
displayInToolbox : false,
|
||||
enableLineBreaks : false,
|
||||
irreplaceable : false
|
||||
[this.apiSettings.TOOLBAR_ICON_CLASS] : false,
|
||||
[this.apiSettings.IS_DISPLAYED_IN_TOOLBOX] : false,
|
||||
[this.apiSettings.IS_ENABLED_LINE_BREAKS] : false,
|
||||
[this.apiSettings.IS_IRREPLACEBLE_TOOL] : false,
|
||||
[this.apiSettings.IS_ENABLED_INLINE_TOOLBAR]: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -192,11 +206,7 @@ export default class Tools extends Module {
|
|||
let plugin = this.toolClasses[tool],
|
||||
config = this.config.toolsConfig[tool];
|
||||
|
||||
if (!config) {
|
||||
config = this.defaultConfig;
|
||||
}
|
||||
|
||||
let instance = new plugin(data, config);
|
||||
let instance = new plugin(data, config || {});
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
@ -209,4 +219,4 @@ export default class Tools extends Module {
|
|||
isInitial(tool) {
|
||||
return tool instanceof this.available[this.config.initialBlock];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -222,17 +222,9 @@ export default class UI extends Module {
|
|||
|
||||
|
||||
/**
|
||||
* @todo hide the Inline Toolbar
|
||||
* Close Inline Toolbar when nothing selected
|
||||
*/
|
||||
// var selectedText = editor.toolbar.inline.getSelectionText(),
|
||||
// firstLevelBlock;
|
||||
|
||||
/** If selection range took off, then we hide inline toolbar */
|
||||
// if (selectedText.length === 0) {
|
||||
|
||||
// editor.toolbar.inline.close();
|
||||
|
||||
// }
|
||||
this.Editor.InlineToolbar.handleShowingEvent(event);
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/**
|
||||
* Working with selection
|
||||
* @typedef {Selection} Selection
|
||||
*/
|
||||
export default class Selection {
|
||||
/**
|
||||
|
@ -24,7 +25,7 @@ export default class Selection {
|
|||
* {@link https://developer.mozilla.org/ru/docs/Web/API/Selection/anchorNode}
|
||||
* @return {Node|null}
|
||||
*/
|
||||
static getAnchorNode() {
|
||||
static get anchorNode() {
|
||||
let selection = window.getSelection();
|
||||
|
||||
return selection ? selection.anchorNode : null;
|
||||
|
@ -35,7 +36,7 @@ export default class Selection {
|
|||
* {@link https://developer.mozilla.org/ru/docs/Web/API/Selection/anchorOffset}
|
||||
* @return {Number|null}
|
||||
*/
|
||||
static getAnchorOffset() {
|
||||
static get anchorOffset() {
|
||||
let selection = window.getSelection();
|
||||
|
||||
return selection ? selection.anchorOffset : null;
|
||||
|
@ -50,4 +51,75 @@ export default class Selection {
|
|||
|
||||
return selection ? selection.isCollapsed : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates position and size of selected text
|
||||
* @return {{x, y, width, height, top?, left?, bottom?, right?}}
|
||||
*/
|
||||
static get rect() {
|
||||
let sel = document.selection, range;
|
||||
let rect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
if (sel && sel.type !== 'Control') {
|
||||
range = sel.createRange();
|
||||
rect.x = range.boundingLeft;
|
||||
rect.y = range.boundingTop;
|
||||
rect.width = range.boundingWidth;
|
||||
rect.height = range.boundingHeight;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
if (!window.getSelection) {
|
||||
_.log('Method window.getSelection is not supported', 'warn');
|
||||
return rect;
|
||||
}
|
||||
|
||||
sel = window.getSelection();
|
||||
|
||||
if (!sel.rangeCount) {
|
||||
_.log('Method Selection.rangeCount() is not supported', 'warn');
|
||||
return rect;
|
||||
}
|
||||
|
||||
range = sel.getRangeAt(0).cloneRange();
|
||||
|
||||
if (range.getBoundingClientRect) {
|
||||
rect = range.getBoundingClientRect();
|
||||
}
|
||||
// Fall back to inserting a temporary element
|
||||
if (rect.x === 0 && rect.y === 0) {
|
||||
let span = document.createElement('span');
|
||||
|
||||
if (span.getBoundingClientRect) {
|
||||
// Ensure span has dimensions and position by
|
||||
// adding a zero-width space character
|
||||
span.appendChild( document.createTextNode('\u200b') );
|
||||
range.insertNode(span);
|
||||
rect = span.getBoundingClientRect();
|
||||
|
||||
let spanParent = span.parentNode;
|
||||
|
||||
spanParent.removeChild(span);
|
||||
|
||||
// Glue any broken text nodes back together
|
||||
spanParent.normalize();
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected text as String
|
||||
* @returns {string}
|
||||
*/
|
||||
static get text() {
|
||||
return window.getSelection ? window.getSelection().toString() : '';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -168,4 +168,4 @@ export default class Util {
|
|||
window.setTimeout(() => method.apply(context, args), timeout);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
.ce-inline-toolbar {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
@apply --overlay-pane;
|
||||
@apply --overlay-pane;
|
||||
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
}
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
transform: translateX(-50%);
|
||||
display: none;
|
||||
|
||||
&--showed {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@
|
|||
--toolbar-buttons-size: 34px;
|
||||
|
||||
--overlay-pane: {
|
||||
position: absolute;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 8px 23px -6px rgba(21,40,54,0.31), 22px -14px 34px -18px rgba(33,48,73,0.26);
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
|
@ -39,4 +40,4 @@
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
"compilerOptions" : {
|
||||
"target": "es6",
|
||||
"declaration": false,
|
||||
"lib": ["es2016", "dom"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"quotemark": [true, "single"]
|
||||
"quotemark": [true, "single"],
|
||||
"no-console": false,
|
||||
"one-variable-per-declaration": false
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue