Inline Toolbar Tools base example (#260)

* Inline Toolbar Tools base example

* texts fixed

* imrpove texts

* little fixes
This commit is contained in:
Peter Savchenko 2018-06-21 17:13:02 +03:00 committed by GitHub
parent cba999a77d
commit 36f505cb02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 537 additions and 50 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

57
docs/tools-inline.md Normal file
View file

@ -0,0 +1,57 @@
# Tools for the Inline Toolbar
Similar with [Tools](tools.md) represented Blocks, you can create Tools for the Inline Toolbar. It will work with
selected fragment of text. The simplest example is `bold` or `italic` Tools.
## Base structure
Inline Tool should implement next methods
- `render()` — create a button
- `surround()` — works with selected range
- `checkState()` — get Tool's activated state by selected range
### render()
Method that returns button to append at the Inline Toolbar
#### Parameters
Method does not accept any parameters
#### Return value
type | description |
-- | -- |
`HTMLElement` | element that will be added to the Inline Toolbar |
### surround(range: Range)
Method that accepts selected range and wrap it somehow
#### Parameters
name | type | description |
-- |-- | -- |
range | Range | first range of current Selection |
#### Return value
There is no return value
### checkState(selection: Selection)
Get Selection and detect if Tool was applied. For example, after that Tool can highlight button or show some details.
#### Parameters
name | type | description |
-- |-- | -- |
selection | Selection | current Selection |
#### Return value
type | description |
-- | -- |
`Boolean` | `true` if Tool is active, otherwise `false` |

View file

@ -0,0 +1,64 @@
import InlineTool from './inline-tool';
/**
* Bold Tool
*
* Inline Toolbar Tool
*
* Makes selected text bolder
*/
export default class BoldInlineTool implements InlineTool {
/**
* Native Document's command that uses for Bold
*/
private readonly commandName: string = 'bold';
/**
* Styles
*/
private readonly CSS = {
button: 'ce-inline-tool',
buttonActive: 'ce-inline-tool--active',
buttonModifier: 'ce-inline-tool--bold',
};
/**
* Elements
*/
private nodes = {
button: null,
};
constructor() {
console.log('Bold Inline Tool is ready');
}
/**
* Create button for Inline Toolbar
*/
public render(): HTMLElement {
this.nodes.button = document.createElement('button');
this.nodes.button.classList.add(this.CSS.button, this.CSS.buttonModifier);
return this.nodes.button;
}
/**
* Wrap range with <b> tag
* @param {Range} range
*/
public surround(range: Range): void {
document.execCommand(this.commandName);
}
/**
* Check selection and set activated state to button if there are <b> tag
* @param {Selection} selection
*/
public checkState(selection: Selection): boolean {
const isActive = document.queryCommandState(this.commandName);
this.nodes.button.classList.toggle(this.CSS.buttonActive, isActive);
return isActive;
}
}

View file

@ -0,0 +1,52 @@
/**
* Base structure for the Inline Toolbar Tool
*/
export default interface InlineTool {
/**
* Returns button for the Inline Toolbar
*/
render(): HTMLElement;
/**
* Method that accepts selected range and wrap it somehow
* @param {Range} range - selection's range
*/
surround(range: Range): void;
/**
* Get Selection and detect if Tool was applied
* For example, after that Tool can highlight button or show some details
* @param {Selection} selection - current Selection
*/
checkState(selection: Selection): boolean;
/**
* Allow Tool to determine shortcut that will fire 'surround' method
*/
shortcut?(): Shortcut;
}
/**
* Specify Shortcut structure for the Inline Tools
*/
interface Shortcut {
/**
* Name like 'CMD+I'
*/
readonly name: string;
/**
* Key that activates the Shortcut, for example: 73
*/
readonly keyCode: number;
/**
* Is need to CMD or CTRL should be pressed
*/
readonly cmdKey: boolean;
/**
* Is need to ALT should be pressed
*/
readonly altKey: boolean;
}

View file

@ -8,6 +8,8 @@
declare var Module: any;
declare var $: any;
declare var _: any;
import InlineTool from '../inline-tools/inline-tool';
import BoldInlineTool from '../inline-tools/inline-tool-bold';
import Selection from '../selection';
/**
@ -47,13 +49,23 @@ export default class InlineToolbar extends Module {
*/
private readonly toolbarVerticalMargin: number = 20;
/**
* Available Tools classes
*/
private tools: InlineTool[] = [];
/**
* @constructor
*/
constructor({config}) {
super({config});
super({config});
/**
* @todo Merge internal tools with external
*/
this.tools = [
new BoldInlineTool(),
];
}
/**
@ -61,15 +73,28 @@ export default class InlineToolbar extends Module {
*/
public make() {
this.nodes.wrapper = $.make('div', this.CSS.inlineToolbar);
this.nodes.wrapper = $.make('div', this.CSS.inlineToolbar);
/**
* Append Inline Toolbar to the Editor
*/
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
/**
* Append Inline Toolbar to the Editor
*/
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
/**
* Append Inline Toolbar Tools
*/
this.addTools();
}
/**
*
*
* Moving / appearance
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*/
/**
* Shows Inline Toolbar by keyup/mouseup
* @param {KeyboardEvent|MouseEvent} event
@ -82,6 +107,9 @@ export default class InlineToolbar extends Module {
this.move();
this.open();
/** Check Tools state for selected fragment */
this.checkToolsState();
}
/**
@ -164,4 +192,58 @@ export default class InlineToolbar extends Module {
return toolConfig && toolConfig[this.Editor.Tools.apiSettings.IS_ENABLED_INLINE_TOOLBAR];
}
/**
*
*
* Working with Tools
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*/
/**
* Fill Inline Toolbar with Tools
*/
private addTools(): void {
this.tools.forEach( (tool) => {
this.addTool(tool);
});
}
/**
* Add tool button and activate clicks
* @param {InlineTool} tool - Tool's instance
*/
private addTool(tool: InlineTool): void {
const button = tool.render();
this.nodes.wrapper.appendChild(button);
this.Editor.Listeners.on(button, 'click', () => {
this.toolClicked(tool);
});
}
/**
* Inline Tool button clicks
* @param {InlineTool} tool - Tool's instance
*/
private toolClicked(tool: InlineTool): void {
const range = Selection.range;
if (!range) {
return;
}
tool.surround(range);
this.checkToolsState();
}
/**
* Check Tools` state by selection
*/
private checkToolsState(): void {
this.tools.forEach( (tool) => {
tool.checkState(Selection.get);
});
}
}

View file

@ -26,7 +26,7 @@ export default class Selection {
* @return {Node|null}
*/
static get anchorNode() {
let selection = window.getSelection();
const selection = window.getSelection();
return selection ? selection.anchorNode : null;
}
@ -37,7 +37,7 @@ export default class Selection {
* @return {Number|null}
*/
static get anchorOffset() {
let selection = window.getSelection();
const selection = window.getSelection();
return selection ? selection.anchorOffset : null;
}
@ -47,11 +47,21 @@ export default class Selection {
* @return {boolean|null}
*/
static get isCollapsed() {
let selection = window.getSelection();
const selection = window.getSelection();
return selection ? selection.isCollapsed : null;
}
/**
* Return first range
* @return {Range|null}
*/
static get range() {
const selection = window.getSelection();
return selection ? selection.getRangeAt(0) : null;
}
/**
* Calculates position and size of selected text
* @return {{x, y, width, height, top?, left?, bottom?, right?}}

View file

@ -1,8 +1,6 @@
.ce-inline-toolbar {
@apply --overlay-pane;
width: 100px;
height: 40px;
padding: 6px;
transform: translateX(-50%);
display: none;
@ -10,3 +8,30 @@
display: block;
}
}
.ce-inline-tool {
display: inline-block;
width: 34px;
height: 34px;
border-radius: 3px;
cursor: pointer;
border: 0;
outline: none;
background: transparent;
vertical-align: bottom;
&:hover {
background: var(--bg-light);
}
&--active {
color: var(--color-active-icon);
}
&--bold {
&::before {
font-weight: bold;
content: 'B'
}
}
}

View file

@ -1,43 +1,45 @@
:root {
/**
* Toolbar buttons
*/
--bg-light: #eff2f5;
/**
* Toolbar buttons
*/
--bg-light: #eff2f5;
/**
* All gray texts: placeholders, settings
*/
--grayText: #707684;
/**
* All gray texts: placeholders, settings
*/
--grayText: #707684;
/** Blue icons */
--color-active-icon: #388AE5;
/**
* Block content width
*/
--content-width: 650px;
/**
* Block content width
*/
--content-width: 650px;
/**
* Toolbar Plus Button and Toolbox buttons height and width
*/
--toolbar-buttons-size: 34px;
/**
* Toolbar Plus Button and Toolbox buttons height and width
*/
--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;
z-index: 2;
&::before {
content: '';
width: 15px;
height: 15px;
position: absolute;
top: -7px;
left: 50%;
margin-left: -7px;
transform: rotate(-45deg);
background: #fff;
}
}
--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;
z-index: 2;
&::before {
content: '';
width: 15px;
height: 15px;
position: absolute;
top: -7px;
left: 50%;
margin-left: -7px;
transform: rotate(-45deg);
background: #fff;
z-index: -1;
}
}
}

View file

@ -96,6 +96,7 @@ module.exports = {
resolve : {
// fallback: path.join(__dirname, 'node_modules'),
modules : [ path.join(__dirname, "src"), "node_modules"],
extensions: ['.js', '.ts'],
alias: {
'utils': path.resolve(__dirname + '/src/components/', './utils'),
'dom': path.resolve(__dirname + '/src/components/', './dom'),