mirror of
https://github.com/codex-team/editor.js
synced 2024-06-18 13:45:20 +02:00
Sanitizer features (#467)
* Sanitizer features * move to ts, use sanitizer module to clean taintstring * Sanitizer is ready * it works * Code refactored (#476) * Use new features for paste handling * Fix asterix * Add types and some comments * Use any type for deepSanitize method * Make sanitize property static and use apiSettings object * Use sanitize for single-block pasting * Fix comment * little updates * rename sanitize in inline-tools docs * Update pattern handling * Use public getter for available tools * Fix typo
This commit is contained in:
parent
5188933ee3
commit
3f8c7fbb7b
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -39,6 +39,9 @@ Methods that working with Blocks
|
|||
```clean(taintString, config)``` - method uses HTMLJanitor to clean taint string.
|
||||
CodeX Editor provides basic config without attributes, but you can inherit by passing your own config.
|
||||
|
||||
If Tool enables inline-tools, we get it's sanitizing rules and merge with your passed custom
|
||||
rules.
|
||||
|
||||
Usage:
|
||||
|
||||
```js
|
||||
|
|
|
@ -2,27 +2,6 @@
|
|||
|
||||
The `Sanitizer` module represents a set of methods that clears taint strings.
|
||||
Uses lightweight npm package with simple API [html-janitor](https://www.npmjs.com/package/html-janitor)
|
||||
|
||||
Sanitizer class implements basic Module class that holds User configuration
|
||||
and default CodeX Editor instances
|
||||
|
||||
## Properties
|
||||
|
||||
Default Editor Sanitizer configuration according to the html-janitor API
|
||||
```javascript
|
||||
defaultConfig
|
||||
```
|
||||
|
||||
Custom User configuration which passed on Editor initialization. Data type must be according to the html-janitor API
|
||||
```javascript
|
||||
sanitizerConfig
|
||||
```
|
||||
|
||||
|
||||
Property that holds an instance used in Module
|
||||
```javascript
|
||||
sanitizerInstance
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ Also, you can provide optional methods
|
|||
|
||||
- `renderActions()` — create additional element below the buttons
|
||||
- `clear()` — clear Tool's stuff on opening/closing of Inline Toolbar
|
||||
- `sanitize()` — sanitizer configuration
|
||||
|
||||
At the constructor of Tool's class exemplar you will accept an object with the [API](api.md) as a parameter.
|
||||
|
||||
|
@ -102,3 +103,23 @@ Method does not accept any parameters
|
|||
#### Return value
|
||||
|
||||
Method should not return a value.
|
||||
|
||||
### static get sanitize()
|
||||
|
||||
We recommend to specify the Sanitizer config that corresponds with inline tags that is used by your Tool.
|
||||
In that case, your config will be merged with sanitizer configuration of Block Tool
|
||||
that is using the Inline Toolbar with your Tool.
|
||||
|
||||
Example:
|
||||
|
||||
If your Tool wrapps selected text with `<b>` tag, the sanitizer config should looks like this:
|
||||
|
||||
```js
|
||||
static get sanitize() {
|
||||
return {
|
||||
b: {} // {} means clean all attributes. true — leave all attributes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Read more about Sanitizer configuration at the [Tools#sanitize](tools.md#sanitize)
|
||||
|
|
|
@ -209,7 +209,6 @@ static get onPaste() {
|
|||
CodeX Editor provides [API](sanitizer.md) to clean taint strings.
|
||||
Use it manually at the `save()` method or or pass `sanitizer` config to do it automatically.
|
||||
|
||||
|
||||
#### Sanitizer Configuration
|
||||
|
||||
The example of sanitizer configuration
|
||||
|
@ -279,12 +278,15 @@ save() {
|
|||
|
||||
#### Automatic sanitize
|
||||
|
||||
If you pass the sanitizer config, CodeX Editor will automatically sanitize your saved data.
|
||||
If you pass the sanitizer config as static getter, CodeX Editor will automatically sanitize your saved data.
|
||||
|
||||
Note that if your Tool is allowed to use the Inline Toolbar, we will get sanitizing rules for each Inline Tool
|
||||
and merge with your passed config.
|
||||
|
||||
You can define rules for each field
|
||||
|
||||
```javascript
|
||||
get sanitize() {
|
||||
static get sanitize() {
|
||||
return {
|
||||
text: {},
|
||||
items: {
|
||||
|
@ -302,7 +304,7 @@ if you want to sanitize everything and get data without any tags, use `{}` or ju
|
|||
ignore field in case if you want to get pure HTML
|
||||
|
||||
```javascript
|
||||
get sanitize() {
|
||||
static get sanitize() {
|
||||
return {
|
||||
text: {},
|
||||
items: {}, // this rules will be used for all properties of this object
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "codex.editor",
|
||||
"version": "2.1.3",
|
||||
"version": "2.2.0",
|
||||
"description": "Codex Editor. Native JS, based on API and Open Source",
|
||||
"main": "build/codex-editor.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -149,6 +149,14 @@ export default class Block {
|
|||
return this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tool's sanitizer config
|
||||
* @return {object}
|
||||
*/
|
||||
get sanitize(): object {
|
||||
return this.tool.sanitize;
|
||||
}
|
||||
|
||||
/**
|
||||
* is block mergeable
|
||||
* We plugin have merge function then we call it mergable
|
||||
|
@ -287,15 +295,7 @@ export default class Block {
|
|||
* @return {Object}
|
||||
*/
|
||||
public async save(): Promise<void|{tool: string, data: any, time: number}> {
|
||||
let extractedBlock = await this.tool.save(this.pluginsContent);
|
||||
|
||||
/**
|
||||
* if Tool provides custom sanitizer config
|
||||
* then use this config
|
||||
*/
|
||||
if (this.tool.sanitize && typeof this.tool.sanitize === 'object') {
|
||||
extractedBlock = this.sanitizeBlock(extractedBlock, this.tool.sanitize);
|
||||
}
|
||||
const extractedBlock = await this.tool.save(this.pluginsContent);
|
||||
|
||||
/**
|
||||
* Measuring execution time
|
||||
|
@ -373,73 +373,6 @@ export default class Block {
|
|||
return tunesElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method recursively reduces Block's data and cleans with passed rules
|
||||
*
|
||||
* @param {Object|string} blockData - taint string or object/array that contains taint string
|
||||
* @param {Object} rules - object with sanitizer rules
|
||||
*/
|
||||
private sanitizeBlock(blockData, rules) {
|
||||
|
||||
/**
|
||||
* Case 1: Block data is Array
|
||||
* Array's in JS can not be enumerated with for..in because result will be Object not Array
|
||||
* which conflicts with Consistency
|
||||
*/
|
||||
if (Array.isArray(blockData)) {
|
||||
/**
|
||||
* Create new "cleanData" array and fill in with sanitizer data
|
||||
*/
|
||||
return blockData.map((item) => {
|
||||
return this.sanitizeBlock(item, rules);
|
||||
});
|
||||
} else if (typeof blockData === 'object') {
|
||||
|
||||
/**
|
||||
* Create new "cleanData" object and fill with sanitized objects
|
||||
*/
|
||||
const cleanData = {};
|
||||
|
||||
/**
|
||||
* Object's may have 3 cases:
|
||||
* 1. When Data is Array. Then call again itself and recursively clean arrays items
|
||||
* 2. When Data is Object that can have object's inside. Do the same, call itself and clean recursively
|
||||
* 3. When Data is base type (string, int, bool, ...). Check if rule is passed
|
||||
*/
|
||||
for (const data in blockData) {
|
||||
if (Array.isArray(blockData[data]) || typeof blockData[data] === 'object') {
|
||||
/**
|
||||
* Case 1 & Case 2
|
||||
*/
|
||||
if (rules[data]) {
|
||||
cleanData[data] = this.sanitizeBlock(blockData[data], rules[data]);
|
||||
} else if (_.isEmpty(rules)) {
|
||||
cleanData[data] = this.sanitizeBlock(blockData[data], rules);
|
||||
} else {
|
||||
cleanData[data] = blockData[data];
|
||||
}
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Case 3.
|
||||
*/
|
||||
if (rules[data]) {
|
||||
cleanData[data] = this.api.sanitizer.clean(blockData[data], rules[data]);
|
||||
} else {
|
||||
cleanData[data] = this.api.sanitizer.clean(blockData[data], rules);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cleanData;
|
||||
} else {
|
||||
/**
|
||||
* In case embedded objects use parent rules
|
||||
*/
|
||||
return this.api.sanitizer.clean(blockData, rules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle drop target state
|
||||
* @param {boolean} state
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import InlineTool from '../interfaces/tools/inline-tool';
|
||||
import SelectionUtils from '../selection';
|
||||
import ISanitizerConfig from '../interfaces/sanitizer-config';
|
||||
|
||||
declare var $: any;
|
||||
|
||||
|
@ -12,6 +12,24 @@ declare var $: any;
|
|||
*/
|
||||
export default class BoldInlineTool implements InlineTool {
|
||||
|
||||
/**
|
||||
* Specifies Tool as Inline Toolbar Tool
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
public static isInline = true;
|
||||
|
||||
/**
|
||||
* Sanitizer Rule
|
||||
* Leave <b> tags
|
||||
* @return {object}
|
||||
*/
|
||||
static get sanitize(): ISanitizerConfig {
|
||||
return {
|
||||
b: {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Native Document's command that uses for Bold
|
||||
*/
|
||||
|
@ -29,7 +47,7 @@ export default class BoldInlineTool implements InlineTool {
|
|||
/**
|
||||
* Elements
|
||||
*/
|
||||
private nodes = {
|
||||
private nodes: {button: HTMLButtonElement} = {
|
||||
button: undefined,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import InlineTool from '../interfaces/tools/inline-tool';
|
||||
import ISanitizerConfig from '../interfaces/sanitizer-config';
|
||||
|
||||
declare var $: any;
|
||||
|
||||
|
@ -11,6 +12,24 @@ declare var $: any;
|
|||
*/
|
||||
export default class ItalicInlineTool implements InlineTool {
|
||||
|
||||
/**
|
||||
* Specifies Tool as Inline Toolbar Tool
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
public static isInline = true;
|
||||
|
||||
/**
|
||||
* Sanitizer Rule
|
||||
* Leave <i> tags
|
||||
* @return {object}
|
||||
*/
|
||||
static get sanitize(): ISanitizerConfig {
|
||||
return {
|
||||
i: {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Native Document's command that uses for Italic
|
||||
*/
|
||||
|
@ -28,7 +47,7 @@ export default class ItalicInlineTool implements InlineTool {
|
|||
/**
|
||||
* Elements
|
||||
*/
|
||||
private nodes = {
|
||||
private nodes: {button: HTMLButtonElement} = {
|
||||
button: null,
|
||||
};
|
||||
|
||||
|
@ -74,5 +93,4 @@ export default class ItalicInlineTool implements InlineTool {
|
|||
public get shortcut(): string {
|
||||
return 'CMD+I';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import InlineTool from '../interfaces/tools/inline-tool';
|
||||
import SelectionUtils from '../selection';
|
||||
import ISanitizerConfig from '../interfaces/sanitizer-config';
|
||||
|
||||
declare var $: any;
|
||||
declare var _: any;
|
||||
|
@ -12,6 +13,29 @@ declare var _: any;
|
|||
* Wrap selected text with <a> tag
|
||||
*/
|
||||
export default class LinkInlineTool implements InlineTool {
|
||||
|
||||
/**
|
||||
* Specifies Tool as Inline Toolbar Tool
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
public static isInline = true;
|
||||
|
||||
/**
|
||||
* Sanitizer Rule
|
||||
* Leave <a> tags
|
||||
* @return {object}
|
||||
*/
|
||||
static get sanitize(): ISanitizerConfig {
|
||||
return {
|
||||
a: {
|
||||
href: true,
|
||||
target: '_blank',
|
||||
rel: 'nofollow',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Native Document's commands for link/unlink
|
||||
*/
|
||||
|
@ -38,7 +62,7 @@ export default class LinkInlineTool implements InlineTool {
|
|||
/**
|
||||
* Elements
|
||||
*/
|
||||
private nodes = {
|
||||
private nodes: {button: HTMLButtonElement, input: HTMLInputElement} = {
|
||||
button: null,
|
||||
input: null,
|
||||
};
|
||||
|
|
|
@ -32,5 +32,5 @@ export default interface ISanitizerConfig {
|
|||
* }
|
||||
* }
|
||||
*/
|
||||
[key: string]: boolean|object|(() => any);
|
||||
[key: string]: boolean|{[attr: string]: boolean|string}|(() => any);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Base structure for the Inline Toolbar Tool
|
||||
*/
|
||||
import ITool from './tool';
|
||||
import ISanitizerConfig from '../sanitizer-config';
|
||||
|
||||
export default interface IInlineTool extends ITool {
|
||||
/**
|
||||
|
@ -9,6 +10,11 @@ export default interface IInlineTool extends ITool {
|
|||
*/
|
||||
shortcut?: string;
|
||||
|
||||
/**
|
||||
* Inline tool sanitize configuration
|
||||
*/
|
||||
sanitize?: ISanitizerConfig;
|
||||
|
||||
/**
|
||||
* Returns button for the Inline Toolbar
|
||||
*/
|
||||
|
@ -37,5 +43,4 @@ export default interface IInlineTool extends ITool {
|
|||
* Function called with Inline Toolbar closing
|
||||
*/
|
||||
clear?(): void;
|
||||
|
||||
}
|
||||
|
|
|
@ -152,7 +152,8 @@ export default class Paste extends Module {
|
|||
return result;
|
||||
}, {});
|
||||
|
||||
const customConfig = Object.assign({}, toolsTags, Sanitizer.defaultConfig.tags);
|
||||
const customConfig = Object.assign({}, toolsTags, Sanitizer.getAllInlineToolsConfig());
|
||||
|
||||
const cleanData = Sanitizer.clean(htmlData, customConfig);
|
||||
|
||||
/** If there is no HTML or HTML string is equal to plain one, process it as plain text */
|
||||
|
@ -522,7 +523,7 @@ export default class Paste extends Module {
|
|||
|
||||
return result;
|
||||
}, {});
|
||||
const customConfig = Object.assign({}, toolTags, Sanitizer.defaultConfig.tags);
|
||||
const customConfig = Object.assign({}, toolTags, Sanitizer.getInlineToolsConfig(tool));
|
||||
|
||||
content.innerHTML = Sanitizer.clean(content.innerHTML, customConfig);
|
||||
|
||||
|
@ -570,7 +571,7 @@ export default class Paste extends Module {
|
|||
*/
|
||||
private async processSingleBlock(dataToInsert: IPasteData): Promise<void> {
|
||||
const initialTool = this.config.initialBlock,
|
||||
{BlockManager, Caret} = this.Editor,
|
||||
{BlockManager, Caret, Sanitizer} = this.Editor,
|
||||
{content, tool} = dataToInsert;
|
||||
|
||||
if (tool === initialTool && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
|
||||
|
@ -580,6 +581,10 @@ export default class Paste extends Module {
|
|||
this.splitBlock();
|
||||
let insertedBlock;
|
||||
|
||||
const sanitizeConfig = Sanitizer.composeToolConfig(tool);
|
||||
|
||||
blockData.data = Sanitizer.deepSanitize(blockData.data, sanitizeConfig);
|
||||
|
||||
if (BlockManager.currentBlock && BlockManager.currentBlock.isEmpty) {
|
||||
insertedBlock = BlockManager.replace(blockData.tool, blockData.data);
|
||||
} else {
|
||||
|
@ -590,8 +595,10 @@ export default class Paste extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
const currentToolSanitizeConfig = Sanitizer.getInlineToolsConfig(BlockManager.currentBlock.name);
|
||||
|
||||
/** If there is no pattern substitute - insert string as it is */
|
||||
document.execCommand('insertHTML', false, content.innerHTML);
|
||||
document.execCommand('insertHTML', false, Sanitizer.clean(content.innerHTML, currentToolSanitizeConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
/**
|
||||
* CodeX Sanitizer
|
||||
*
|
||||
* @module Sanitizer
|
||||
* Clears HTML from taint tags
|
||||
*
|
||||
* @version 2.0.0
|
||||
*
|
||||
* @example
|
||||
* Module can be used within two ways:
|
||||
* 1) When you have an instance
|
||||
* - this.Editor.Sanitizer.clean(yourTaintString);
|
||||
* 2) As static method
|
||||
* - CodexEditor.Sanitizer.clean(yourTaintString, yourCustomConfiguration);
|
||||
*
|
||||
* {@link SanitizerConfig}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object} SanitizerConfig
|
||||
* @property {Object} tags - define tags restrictions
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* tags : {
|
||||
* p: true,
|
||||
* a: {
|
||||
* href: true,
|
||||
* rel: "nofollow",
|
||||
* target: "_blank"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export default class Sanitizer extends Module {
|
||||
/**
|
||||
* Initializes Sanitizer module
|
||||
* Sets default configuration if custom not exists
|
||||
*
|
||||
* @property {SanitizerConfig} this.defaultConfig
|
||||
* @property {HTMLJanitor} this._sanitizerInstance - Sanitizer library
|
||||
*
|
||||
* @param {SanitizerConfig} config
|
||||
*/
|
||||
constructor({config}) {
|
||||
super({config});
|
||||
|
||||
this._sanitizerInstance = null;
|
||||
|
||||
/** Custom configuration */
|
||||
this.sanitizerConfig = config.settings ? config.settings.sanitizer : null;
|
||||
|
||||
/** HTML Janitor library */
|
||||
this.sanitizerInstance = require('html-janitor');
|
||||
}
|
||||
|
||||
/**
|
||||
* If developer uses editor's API, then he can customize sanitize restrictions.
|
||||
* Or, sanitizing config can be defined globally in editors initialization. That config will be used everywhere
|
||||
* At least, if there is no config overrides, that API uses Default configuration
|
||||
*
|
||||
* @uses https://www.npmjs.com/package/html-janitor
|
||||
*
|
||||
* @param {HTMLJanitor} library - sanitizer extension
|
||||
*/
|
||||
set sanitizerInstance(library) {
|
||||
if (this.sanitizerConfig) {
|
||||
this._sanitizerInstance = new library(this.sanitizerConfig);
|
||||
}
|
||||
|
||||
return this._sanitizerInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sanitizer configuration. Uses default config if user didn't pass the restriction
|
||||
*/
|
||||
get defaultConfig() {
|
||||
return {
|
||||
tags: {
|
||||
p: {},
|
||||
a: {
|
||||
href: true,
|
||||
target: '_blank',
|
||||
rel: 'nofollow'
|
||||
},
|
||||
b: {},
|
||||
i: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sanitizer instance
|
||||
* @return {null|library}
|
||||
*/
|
||||
get sanitizerInstance() {
|
||||
return this._sanitizerInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans string from unwanted tags
|
||||
* @param {String} taintString - HTML string
|
||||
* @param {Object} customConfig - custom sanitizer configuration. Method uses default if param is empty
|
||||
* @return {String} clean HTML
|
||||
*/
|
||||
clean(taintString, customConfig) {
|
||||
if (customConfig && typeof customConfig === 'object') {
|
||||
/**
|
||||
* API client can use custom config to manage sanitize process
|
||||
*/
|
||||
let newConfig = {
|
||||
tags: customConfig
|
||||
};
|
||||
|
||||
return Sanitizer.clean(taintString, newConfig);
|
||||
} else {
|
||||
/**
|
||||
* Ignore sanitizing when nothing passed in config
|
||||
*/
|
||||
if (!this.sanitizerInstance) {
|
||||
return taintString;
|
||||
} else {
|
||||
return this.sanitizerInstance.clean(taintString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans string from unwanted tags
|
||||
* @static
|
||||
*
|
||||
* Method allows to use default config
|
||||
*
|
||||
* @param {String} taintString - taint string
|
||||
* @param {SanitizerConfig} customConfig - allowed tags
|
||||
*
|
||||
* @return {String} clean HTML
|
||||
*/
|
||||
static clean(taintString, customConfig) {
|
||||
let newInstance = new Sanitizer({
|
||||
config: {
|
||||
settings: {
|
||||
sanitizer: customConfig
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return newInstance.clean(taintString);
|
||||
}
|
||||
}
|
313
src/components/modules/sanitizer.ts
Normal file
313
src/components/modules/sanitizer.ts
Normal file
|
@ -0,0 +1,313 @@
|
|||
/**
|
||||
* CodeX Sanitizer
|
||||
*
|
||||
* @module Sanitizer
|
||||
* Clears HTML from taint tags
|
||||
*
|
||||
* @version 2.0.0
|
||||
*
|
||||
* @example
|
||||
* Module can be used within two ways:
|
||||
* 1) When you have an instance
|
||||
* - this.Editor.Sanitizer.clean(yourTaintString);
|
||||
* 2) As static method
|
||||
* - CodexEditor.Sanitizer.clean(yourTaintString, yourCustomConfiguration);
|
||||
*
|
||||
* {@link SanitizerConfig}
|
||||
*/
|
||||
|
||||
import ISanitizerConfig from '../interfaces/sanitizer-config';
|
||||
|
||||
/**
|
||||
* @typedef {Object} SanitizerConfig
|
||||
* @property {Object} tags - define tags restrictions
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* tags : {
|
||||
* p: true,
|
||||
* a: {
|
||||
* href: true,
|
||||
* rel: "nofollow",
|
||||
* target: "_blank"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
declare const Module: any;
|
||||
declare const _: any;
|
||||
|
||||
import HTMLJanitor from 'html-janitor';
|
||||
import IBlockToolData from '../interfaces/tools/block-tool-data';
|
||||
import IInlineTool from '../interfaces/tools/inline-tool';
|
||||
|
||||
export default class Sanitizer extends Module {
|
||||
/**
|
||||
* Memoize tools config
|
||||
*/
|
||||
private configCache: {[toolName: string]: ISanitizerConfig} = {};
|
||||
|
||||
/**
|
||||
* Cached inline tools config
|
||||
*/
|
||||
private inlineToolsConfigCache: ISanitizerConfig | null = null;
|
||||
|
||||
/**
|
||||
* Initializes Sanitizer module
|
||||
* Sets default configuration if custom not exists
|
||||
*
|
||||
* @property {HTMLJanitor} this._sanitizerInstance - Sanitizer library
|
||||
*
|
||||
* @param {IEditorConfig} config
|
||||
*/
|
||||
constructor({config}) {
|
||||
super({config});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize Blocks
|
||||
*
|
||||
* Enumerate blocks and clean data
|
||||
*
|
||||
* @param {{tool, data: IBlockToolData}[]} blocksData[]
|
||||
*/
|
||||
public sanitizeBlocks(
|
||||
blocksData: Array<{tool: string, data: IBlockToolData}>,
|
||||
): Array<{tool: string, data: IBlockToolData}> {
|
||||
|
||||
return blocksData.map((block) => {
|
||||
const toolConfig = this.composeToolConfig(block.tool);
|
||||
|
||||
block.data = this.deepSanitize(block.data, toolConfig);
|
||||
|
||||
return block;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method recursively reduces Block's data and cleans with passed rules
|
||||
*
|
||||
* @param {IBlockToolData|object|*} dataToSanitize - taint string or object/array that contains taint string
|
||||
* @param {ISanitizerConfig} rules - object with sanitizer rules
|
||||
*/
|
||||
public deepSanitize(dataToSanitize: any, rules: ISanitizerConfig): any {
|
||||
/**
|
||||
* BlockData It may contain 3 types:
|
||||
* - Array
|
||||
* - Object
|
||||
* - Primitive
|
||||
*/
|
||||
if (Array.isArray(dataToSanitize)) {
|
||||
/**
|
||||
* Array: call sanitize for each item
|
||||
*/
|
||||
return this.cleanArray(dataToSanitize, rules);
|
||||
} else if (typeof dataToSanitize === 'object') {
|
||||
/**
|
||||
* Objects: just clean object deeper.
|
||||
*/
|
||||
return this.cleanObject(dataToSanitize, rules);
|
||||
} else {
|
||||
/**
|
||||
* Primitives (number|string|boolean): clean this item
|
||||
*/
|
||||
return this.cleanOneItem(dataToSanitize, rules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans string from unwanted tags
|
||||
* Method allows to use default config
|
||||
*
|
||||
* @param {String} taintString - taint string
|
||||
* @param {SanitizerConfig} customConfig - allowed tags
|
||||
*
|
||||
* @return {String} clean HTML
|
||||
*/
|
||||
public clean(taintString: string, customConfig: ISanitizerConfig = {}): string {
|
||||
|
||||
const sanitizerConfig = {
|
||||
tags: customConfig,
|
||||
};
|
||||
|
||||
/**
|
||||
* API client can use custom config to manage sanitize process
|
||||
*/
|
||||
const sanitizerInstance = this.createHTMLJanitorInstance(sanitizerConfig);
|
||||
return sanitizerInstance.clean(taintString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge with inline tool config
|
||||
*
|
||||
* @param {string} toolName
|
||||
* @param {ISanitizerConfig} toolRules
|
||||
* @return {ISanitizerConfig}
|
||||
*/
|
||||
public composeToolConfig(toolName: string): ISanitizerConfig {
|
||||
/**
|
||||
* If cache is empty, then compose tool config and put it to the cache object
|
||||
*/
|
||||
if (this.configCache[toolName]) {
|
||||
return this.configCache[toolName];
|
||||
}
|
||||
|
||||
const sanitizeGetter = this.Editor.Tools.apiSettings.SANITIZE_CONFIG;
|
||||
const toolClass = this.Editor.Tools.available[toolName];
|
||||
|
||||
/**
|
||||
* If Tools doesn't provide sanitizer config or it is empty
|
||||
*/
|
||||
if (!toolClass.sanitize || (toolClass[sanitizeGetter] && _.isEmpty(toolClass[sanitizeGetter]))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const toolRules = toolClass.sanitize;
|
||||
const baseConfig = this.getInlineToolsConfig(toolName);
|
||||
|
||||
const toolConfig = {};
|
||||
for (const fieldName in toolRules) {
|
||||
if (toolRules.hasOwnProperty(fieldName)) {
|
||||
const rule = toolRules[fieldName];
|
||||
if (typeof rule === 'object') {
|
||||
toolConfig[fieldName] = Object.assign({}, baseConfig, rule);
|
||||
} else {
|
||||
toolConfig[fieldName] = rule;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.configCache[toolName] = toolConfig;
|
||||
|
||||
return toolConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Sanitizer config
|
||||
* When Tool's "inlineToolbar" value is True, get all sanitizer rules from all tools,
|
||||
* otherwise get only enabled
|
||||
*/
|
||||
public getInlineToolsConfig(name: string): ISanitizerConfig {
|
||||
const {Tools} = this.Editor;
|
||||
|
||||
const toolsConfig = Tools.getToolSettings(name),
|
||||
enableInlineTools = toolsConfig.inlineToolbar || [];
|
||||
|
||||
let config = {};
|
||||
|
||||
if (typeof enableInlineTools === 'boolean' && enableInlineTools) {
|
||||
/**
|
||||
* getting all tools sanitizer rule
|
||||
*/
|
||||
config = this.getAllInlineToolsConfig();
|
||||
} else {
|
||||
/**
|
||||
* getting only enabled
|
||||
*/
|
||||
enableInlineTools.map( (inlineToolName) => {
|
||||
config = Object.assign(config, Tools.inline[inlineToolName][Tools.apiSettings.SANITIZE_CONFIG]);
|
||||
});
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return general config for all inline tools
|
||||
*/
|
||||
public getAllInlineToolsConfig(): ISanitizerConfig {
|
||||
const {Tools} = this.Editor;
|
||||
|
||||
if (this.inlineToolsConfigCache) {
|
||||
return this.inlineToolsConfigCache;
|
||||
}
|
||||
|
||||
const config: ISanitizerConfig = {};
|
||||
|
||||
Object.entries(Tools.inline)
|
||||
.forEach( ([name, inlineTool]: [string, IInlineTool]) => {
|
||||
Object.assign(config, inlineTool[Tools.apiSettings.SANITIZE_CONFIG]);
|
||||
});
|
||||
|
||||
this.inlineToolsConfigCache = config;
|
||||
|
||||
return this.inlineToolsConfigCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean array
|
||||
* @param {array} array - [1, 2, {}, []]
|
||||
* @param {object} ruleForItem
|
||||
*/
|
||||
private cleanArray(array: any[], ruleForItem: ISanitizerConfig): any[] {
|
||||
return array.map( (arrayItem) => this.deepSanitize(arrayItem, ruleForItem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean object
|
||||
* @param {object} object - {level: 0, text: 'adada', items: [1,2,3]}}
|
||||
* @param {object} rules - { b: true } or true|false
|
||||
* @return {object}
|
||||
*/
|
||||
private cleanObject(object: any, rules: ISanitizerConfig|{[field: string]: ISanitizerConfig}): any {
|
||||
const cleanData = {};
|
||||
|
||||
for (const fieldName in object) {
|
||||
if (!object.hasOwnProperty(fieldName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentIterationItem = object[fieldName];
|
||||
|
||||
/**
|
||||
* Get object from config by field name
|
||||
* - if it is a HTML Janitor rule, call with this rule
|
||||
* - otherwise, call with parent's config
|
||||
*/
|
||||
const ruleForItem = this.isRule(rules[fieldName] as ISanitizerConfig) ? rules[fieldName] : rules;
|
||||
|
||||
cleanData[fieldName] = this.deepSanitize(currentIterationItem, ruleForItem as ISanitizerConfig);
|
||||
}
|
||||
return cleanData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} taintString
|
||||
* @param {ISanitizerConfig|boolean} rule
|
||||
* @return {string}
|
||||
*/
|
||||
private cleanOneItem(taintString: string, rule: ISanitizerConfig|boolean): string {
|
||||
if (typeof rule === 'object') {
|
||||
return this.clean(taintString, rule);
|
||||
} else if (rule === false) {
|
||||
return this.clean(taintString, {});
|
||||
} else {
|
||||
return taintString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed item is a HTML Janitor rule:
|
||||
* { a : true }, {}, false, true, function(){} — correct rules
|
||||
* undefined, null, 0, 1, 2 — not a rules
|
||||
* @param config
|
||||
*/
|
||||
private isRule(config: ISanitizerConfig): boolean {
|
||||
return typeof config === 'object' || typeof config === 'boolean' || typeof config === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* If developer uses editor's API, then he can customize sanitize restrictions.
|
||||
* Or, sanitizing config can be defined globally in editors initialization. That config will be used everywhere
|
||||
* At least, if there is no config overrides, that API uses Default configuration
|
||||
*
|
||||
* @uses https://www.npmjs.com/package/html-janitor
|
||||
*
|
||||
* @param {SanitizerConfig} config - sanitizer extension
|
||||
*/
|
||||
private createHTMLJanitorInstance(config: {tags: ISanitizerConfig}): any {
|
||||
if (config) {
|
||||
return new HTMLJanitor(config);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
/**
|
||||
* Codex Editor Saver
|
||||
*
|
||||
* @module Saver
|
||||
* @author Codex Team
|
||||
* @version 2.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SavedData
|
||||
* @property {Date} time - saving proccess time
|
||||
* @property {Object} items - extracted data
|
||||
* @property {String} version - CodexEditor version
|
||||
*/
|
||||
|
||||
/**
|
||||
* @classdesc This method reduces all Blocks asyncronically and calls Block's save method to extract data
|
||||
*
|
||||
* @typedef {Saver} Saver
|
||||
* @property {Element} html - Editor HTML content
|
||||
* @property {String} json - Editor JSON output
|
||||
*/
|
||||
export default class Saver extends Module {
|
||||
/**
|
||||
* @constructor
|
||||
* @param config
|
||||
*/
|
||||
constructor({config}) {
|
||||
super({config});
|
||||
|
||||
this.output = null;
|
||||
this.blocksData = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes new chain of Promises to fire them alternatelly
|
||||
* @return {SavedData}
|
||||
*/
|
||||
save() {
|
||||
let blocks = this.Editor.BlockManager.blocks,
|
||||
chainData = [];
|
||||
|
||||
blocks.forEach((block) => {
|
||||
chainData.push(block.data);
|
||||
});
|
||||
|
||||
return Promise.all(chainData)
|
||||
.then((allExtractedData) => this.makeOutput(allExtractedData))
|
||||
.then((outputData) => {
|
||||
return outputData;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates output object with saved data, time and version of editor
|
||||
* @param {Object} allExtractedData
|
||||
* @return {SavedData}
|
||||
*/
|
||||
makeOutput(allExtractedData) {
|
||||
let blocks = [],
|
||||
totalTime = 0;
|
||||
|
||||
console.groupCollapsed('[CodexEditor saving]:');
|
||||
|
||||
allExtractedData.forEach((extraction) => {
|
||||
/** Group process info */
|
||||
console.log(`«${extraction.tool}» saving info`, extraction);
|
||||
totalTime += extraction.time;
|
||||
blocks.push({
|
||||
type: extraction.tool,
|
||||
data: extraction.data
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Total', totalTime);
|
||||
console.groupEnd();
|
||||
|
||||
return {
|
||||
time: +new Date(),
|
||||
blocks: blocks,
|
||||
version: VERSION,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = (function (saver) {
|
||||
//
|
||||
// let editor = codex.editor;
|
||||
//
|
||||
// /**
|
||||
// * @public
|
||||
// * Save blocks
|
||||
// */
|
||||
// saver.save = function () {
|
||||
//
|
||||
// /** Save html content of redactor to memory */
|
||||
// editor.state.html = editor.nodes.redactor.innerHTML;
|
||||
//
|
||||
// /** Clean jsonOutput state */
|
||||
// editor.state.jsonOutput = [];
|
||||
//
|
||||
// return saveBlocks(editor.nodes.redactor.childNodes);
|
||||
//
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
// * @private
|
||||
// * Save each block data
|
||||
// *
|
||||
// * @param blocks
|
||||
// * @returns {Promise.<TResult>}
|
||||
// */
|
||||
// let saveBlocks = function (blocks) {
|
||||
//
|
||||
// let data = [];
|
||||
//
|
||||
// for(let index = 0; index < blocks.length; index++) {
|
||||
//
|
||||
// data.push(getBlockData(blocks[index]));
|
||||
//
|
||||
// }
|
||||
//
|
||||
// return Promise.all(data)
|
||||
// .then(makeOutput)
|
||||
// .catch(editor.core.log);
|
||||
//
|
||||
// };
|
||||
//
|
||||
// /** Save and validate block data */
|
||||
// let getBlockData = function (block) {
|
||||
//
|
||||
// return saveBlockData(block)
|
||||
// .then(validateBlockData)
|
||||
// .catch(editor.core.log);
|
||||
//
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
// * @private
|
||||
// * Call block`s plugin save method and return saved data
|
||||
// *
|
||||
// * @param block
|
||||
// * @returns {Object}
|
||||
// */
|
||||
// let saveBlockData = function (block) {
|
||||
//
|
||||
// let pluginName = block.dataset.tool;
|
||||
//
|
||||
// /** Check for plugin existence */
|
||||
// if (!editor.tools[pluginName]) {
|
||||
//
|
||||
// editor.core.log(`Plugin «${pluginName}» not found`, 'error');
|
||||
// return {data: null, pluginName: null};
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /** Check for plugin having save method */
|
||||
// if (typeof editor.tools[pluginName].save !== 'function') {
|
||||
//
|
||||
// editor.core.log(`Plugin «${pluginName}» must have save method`, 'error');
|
||||
// return {data: null, pluginName: null};
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /** Result saver */
|
||||
// let blockContent = block.childNodes[0],
|
||||
// pluginsContent = blockContent.childNodes[0],
|
||||
// position = pluginsContent.dataset.inputPosition;
|
||||
//
|
||||
// /** If plugin wasn't available then return data from cache */
|
||||
// if ( editor.tools[pluginName].available === false ) {
|
||||
//
|
||||
// return Promise.resolve({data: codex.editor.state.blocks.items[position].data, pluginName});
|
||||
//
|
||||
// }
|
||||
//
|
||||
// return Promise.resolve(pluginsContent)
|
||||
// .then(editor.tools[pluginName].save)
|
||||
// .then(data => Object({data, pluginName}));
|
||||
//
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
// * Call plugin`s validate method. Return false if validation failed
|
||||
// *
|
||||
// * @param data
|
||||
// * @param pluginName
|
||||
// * @returns {Object|Boolean}
|
||||
// */
|
||||
// let validateBlockData = function ({data, pluginName}) {
|
||||
//
|
||||
// if (!data || !pluginName) {
|
||||
//
|
||||
// return false;
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if (editor.tools[pluginName].validate) {
|
||||
//
|
||||
// let result = editor.tools[pluginName].validate(data);
|
||||
//
|
||||
// /**
|
||||
// * Do not allow invalid data
|
||||
// */
|
||||
// if (!result) {
|
||||
//
|
||||
// return false;
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// return {data, pluginName};
|
||||
//
|
||||
//
|
||||
// };
|
||||
//
|
||||
// /**
|
||||
// * Compile article output
|
||||
// *
|
||||
// * @param savedData
|
||||
// * @returns {{time: number, version, items: (*|Array)}}
|
||||
// */
|
||||
// let makeOutput = function (savedData) {
|
||||
//
|
||||
// savedData = savedData.filter(blockData => blockData);
|
||||
//
|
||||
// let items = savedData.map(blockData => Object({type: blockData.pluginName, data: blockData.data}));
|
||||
//
|
||||
// editor.state.jsonOutput = items;
|
||||
//
|
||||
// return {
|
||||
// id: editor.state.blocks.id || null,
|
||||
// time: +new Date(),
|
||||
// version: editor.version,
|
||||
// items
|
||||
// };
|
||||
//
|
||||
// };
|
||||
//
|
||||
// return saver;
|
||||
//
|
||||
// })({});
|
94
src/components/modules/saver.ts
Normal file
94
src/components/modules/saver.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Codex Editor Saver
|
||||
*
|
||||
* @module Saver
|
||||
* @author Codex Team
|
||||
* @version 2.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} SavedData
|
||||
* @property {Date} time - saving proccess time
|
||||
* @property {Object} blocks - extracted data
|
||||
* @property {String} version - CodexEditor version
|
||||
*/
|
||||
interface SavedData {
|
||||
time: number;
|
||||
blocks: object[];
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @classdesc This method reduces all Blocks asyncronically and calls Block's save method to extract data
|
||||
*
|
||||
* @typedef {Saver} Saver
|
||||
* @property {Element} html - Editor HTML content
|
||||
* @property {String} json - Editor JSON output
|
||||
*/
|
||||
|
||||
declare const Module: any;
|
||||
declare const VERSION: string;
|
||||
|
||||
export default class Saver extends Module {
|
||||
/**
|
||||
* @constructor
|
||||
* @param config
|
||||
*/
|
||||
constructor({config}) {
|
||||
super({config});
|
||||
|
||||
this.output = null;
|
||||
this.blocksData = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes new chain of Promises to fire them alternatelly
|
||||
* @return {SavedData}
|
||||
*/
|
||||
public save(): Promise<SavedData> {
|
||||
const blocks = this.Editor.BlockManager.blocks,
|
||||
chainData = [];
|
||||
|
||||
blocks.forEach((block) => {
|
||||
chainData.push(block.data);
|
||||
});
|
||||
|
||||
return Promise.all(chainData)
|
||||
.then((extractedData) => this.Editor.Sanitizer.sanitizeBlocks(extractedData))
|
||||
.then((allExtractedData) => this.makeOutput(allExtractedData))
|
||||
.then((outputData) => {
|
||||
return outputData;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates output object with saved data, time and version of editor
|
||||
* @param {Object} allExtractedData
|
||||
* @return {SavedData}
|
||||
*/
|
||||
private makeOutput(allExtractedData): SavedData {
|
||||
let totalTime = 0;
|
||||
const blocks = [];
|
||||
|
||||
console.groupCollapsed('[CodexEditor saving]:');
|
||||
|
||||
allExtractedData.forEach((extraction) => {
|
||||
/** Group process info */
|
||||
console.log(`«${extraction.tool}» saving info`, extraction);
|
||||
totalTime += extraction.time;
|
||||
blocks.push({
|
||||
type: extraction.tool,
|
||||
data: extraction.data,
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Total', totalTime);
|
||||
console.groupEnd();
|
||||
|
||||
return {
|
||||
time: +new Date(),
|
||||
version: VERSION,
|
||||
blocks,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import ITool from '../interfaces/tools/tool';
|
||||
|
||||
declare var Module: any;
|
||||
declare var $: any;
|
||||
|
||||
import BoldInlineTool from '../inline-tools/inline-tool-bold';
|
||||
import ItalicInlineTool from '../inline-tools/inline-tool-italic';
|
||||
import LinkInlineTool from '../inline-tools/inline-tool-link';
|
||||
import EditorConfig from '../interfaces/editor-config';
|
||||
import InlineTool from '../interfaces/tools/inline-tool';
|
||||
import SelectionUtils from '../selection';
|
||||
|
@ -63,13 +62,12 @@ export default class InlineToolbar extends Module {
|
|||
|
||||
/**
|
||||
* Inline Toolbar Tools
|
||||
* includes internal and external tools
|
||||
*
|
||||
* @returns Map<string, InlineTool>
|
||||
*/
|
||||
get tools(): Map<string, InlineTool> {
|
||||
if (!this.toolsInstances || this.toolsInstances.size === 0) {
|
||||
const allTools = {...this.internalTools, ...this.externalTools};
|
||||
const allTools = this.inlineTools;
|
||||
|
||||
this.toolsInstances = new Map();
|
||||
for (const tool in allTools) {
|
||||
|
@ -289,6 +287,11 @@ export default class InlineToolbar extends Module {
|
|||
* Add tool button and activate clicks
|
||||
*/
|
||||
private addTool(toolName: string, tool: InlineTool): void {
|
||||
const {
|
||||
Listeners,
|
||||
Tools,
|
||||
} = this.Editor;
|
||||
|
||||
const button = tool.render();
|
||||
|
||||
if (!button) {
|
||||
|
@ -304,7 +307,7 @@ export default class InlineToolbar extends Module {
|
|||
this.nodes.actions.appendChild(actions);
|
||||
}
|
||||
|
||||
this.Editor.Listeners.on(button, 'click', (event) => {
|
||||
Listeners.on(button, 'click', (event) => {
|
||||
this.toolClicked(tool);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
@ -313,18 +316,26 @@ export default class InlineToolbar extends Module {
|
|||
* Enable shortcuts
|
||||
* Ignore tool that doesn't have shortcut or empty string
|
||||
*/
|
||||
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
|
||||
const toolSettings = Tools.getToolSettings(toolName);
|
||||
|
||||
let shortcut = null;
|
||||
|
||||
/**
|
||||
* Get internal inline tools
|
||||
*/
|
||||
const internalTools: string[] = Object
|
||||
.entries(Tools.internalTools)
|
||||
.filter(([name, toolClass]: [string, ITool]) => toolClass[Tools.apiSettings.IS_INLINE])
|
||||
.map(([name, toolClass]: [string, ITool]) => name);
|
||||
|
||||
/**
|
||||
* 1) For internal tools, check public getter 'shortcut'
|
||||
* 2) For external tools, check tool's settings
|
||||
*/
|
||||
if (this.internalTools[toolName]) {
|
||||
shortcut = this.internalTools[toolName].shortcut;
|
||||
} else if (toolSettings && toolSettings[this.Editor.Tools.apiSettings.SHORTCUT]) {
|
||||
shortcut = toolSettings[this.Editor.Tools.apiSettings.SHORTCUT];
|
||||
if (internalTools.includes(toolName)) {
|
||||
shortcut = this.inlineTools[toolName].shortcut;
|
||||
} else if (toolSettings && toolSettings[Tools.apiSettings.SHORTCUT]) {
|
||||
shortcut = toolSettings[Tools.apiSettings.SHORTCUT];
|
||||
}
|
||||
|
||||
if (shortcut) {
|
||||
|
@ -390,22 +401,10 @@ export default class InlineToolbar extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns internal inline tools
|
||||
* Includes Bold, Italic, Link
|
||||
*/
|
||||
private get internalTools(): {[name: string]: InlineTool} {
|
||||
return {
|
||||
bold: this.Editor.Tools.constructInline(BoldInlineTool),
|
||||
italic: this.Editor.Tools.constructInline(ItalicInlineTool),
|
||||
link: this.Editor.Tools.constructInline(LinkInlineTool),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get external tools
|
||||
* Get inline tools tools
|
||||
* Tools that has isInline is true
|
||||
*/
|
||||
private get externalTools(): {[name: string]: InlineTool} {
|
||||
private get inlineTools(): {[name: string]: InlineTool} {
|
||||
const result = {};
|
||||
|
||||
for (const tool in this.Editor.Tools.inline) {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import BoldInlineTool from '../inline-tools/inline-tool-bold';
|
||||
import ItalicInlineTool from '../inline-tools/inline-tool-italic';
|
||||
import LinkInlineTool from '../inline-tools/inline-tool-link';
|
||||
|
||||
const Paragraph = require('../tools/paragraph/dist/bundle');
|
||||
|
||||
/**
|
||||
|
@ -186,6 +190,7 @@ export default class Tools extends Module {
|
|||
IS_PASTE_DISALLOWED: 'disallowPaste',
|
||||
SHORTCUT: 'shortcut',
|
||||
TOOLBAR_ICON: 'toolboxIcon',
|
||||
SANITIZE_CONFIG: 'sanitize'
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -194,10 +199,10 @@ export default class Tools extends Module {
|
|||
* @return {Promise}
|
||||
*/
|
||||
prepare() {
|
||||
this.config.tools.paragraph = {
|
||||
class: Paragraph,
|
||||
inlineToolbar: true
|
||||
};
|
||||
/**
|
||||
* Assign internal tools
|
||||
*/
|
||||
Object.assign(this.config.tools, this.internalTools);
|
||||
|
||||
if (!this.config.hasOwnProperty('tools') || Object.keys(this.config.tools).length === 0) {
|
||||
return Promise.reject('Can\'t start without tools');
|
||||
|
@ -369,4 +374,20 @@ export default class Tools extends Module {
|
|||
getToolSettings(toolName) {
|
||||
return this.toolsSettings[toolName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns internal tools
|
||||
* Includes Bold, Italic, Link and Paragraph
|
||||
*/
|
||||
get internalTools() {
|
||||
return {
|
||||
bold: BoldInlineTool,
|
||||
italic: ItalicInlineTool,
|
||||
link: LinkInlineTool,
|
||||
paragraph: {
|
||||
class: Paragraph,
|
||||
inlineToolbar: true
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue