mirror of
https://github.com/codex-team/editor.js
synced 2024-06-13 19:32:28 +02:00
Paste handling improvements (#534)
* Make on paste callback non-static method * Add docs * change tools.md header levels * some docs improvements * upd docs * Types improvements * add image tool for testing * Fix file drag'n'drop * improve log on paste * Update submodules * Update bundle * Update paragraph submodule * Fix some bugs with blocks replacement Remove tag from HTMLPasteEvent * Use production webpack mode * minimize: true * Update docs * Update submodules * Update bundle
This commit is contained in:
parent
4c9aa0fbd5
commit
669c11eaa5
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
133
docs/tools.md
133
docs/tools.md
|
@ -47,7 +47,7 @@ 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
|
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.
|
data with the currently stored value.
|
||||||
|
|
||||||
### Internal Tool Settings
|
## Internal Tool Settings
|
||||||
|
|
||||||
Options that Tool can specify. All settings should be passed as static properties of Tool's class.
|
Options that Tool can specify. All settings should be passed as static properties of Tool's class.
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ Options that Tool can specify. All settings should be passed as static propertie
|
||||||
| `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. |
|
| `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. |
|
||||||
| `isInline` | _Boolean_ | `false` | Describes Tool as a [Tool for the Inline Toolbar](tools-inline.md) |
|
| `isInline` | _Boolean_ | `false` | Describes Tool as a [Tool for the Inline Toolbar](tools-inline.md) |
|
||||||
|
|
||||||
### User configuration
|
## User configuration
|
||||||
|
|
||||||
All Tools can be configured by users. You can set up some of available settings along with Tool's class
|
All Tools can be configured by users. You can set up some of available settings along with Tool's class
|
||||||
to the `tools` property of Editor Config.
|
to the `tools` property of Editor Config.
|
||||||
|
@ -85,58 +85,58 @@ There are few options available by CodeX Editor.
|
||||||
| `inlineToolbar` | _Boolean/Array_ | `false` | Pass `true` to enable the Inline Toolbar with all Tools, or pass an array with specified Tools list |
|
| `inlineToolbar` | _Boolean/Array_ | `false` | Pass `true` to enable the Inline Toolbar with all Tools, or pass an array with specified Tools list |
|
||||||
| `config` | _Object_ | `null` | User's configuration for Plugin.
|
| `config` | _Object_ | `null` | User's configuration for Plugin.
|
||||||
|
|
||||||
### Paste handling
|
## Paste handling
|
||||||
|
|
||||||
CodeX Editor handles paste on Blocks and provides API for Tools to process the pasted data.
|
CodeX Editor handles paste on Blocks and provides API for Tools to process the pasted data.
|
||||||
|
|
||||||
When user pastes content into Editor, pasted content is splitted into blocks.
|
When user pastes content into Editor, pasted content will be splitted into blocks.
|
||||||
|
|
||||||
1. If plain text has been pasted, it is split by new line characters
|
1. If plain text will be pasted, it will be splitted by new line characters
|
||||||
2. If HTML string has been pasted, it is split by block tags
|
2. If HTML string will be pasted, it will be splitted by block tags
|
||||||
|
|
||||||
Also Editor API allows you to define RegExp patterns to substitute them by your data.
|
Also Editor API allows you to define your own pasting scenario. You can either:
|
||||||
|
|
||||||
To provide paste handling for your Tool you need to define static getter `onPaste` in Tool class.
|
1. Specify **HTML tags**, that can be represented by your Tool. For example, Image Tool can handle `<img>` tags.
|
||||||
`onPaste` getter should return object with fields described below.
|
If tags you specified will be found on content pasting, your Tool will be rendered.
|
||||||
|
2. Specify **RegExp** for pasted strings. If pattern has been matched, your Tool will be rendered.
|
||||||
|
3. Specify **MIME type** or **extensions** of files that can be handled by your Tool on pasting by drag-n-drop or from clipboard.
|
||||||
|
|
||||||
|
For each scenario, you should do 2 next things:
|
||||||
|
|
||||||
##### HTML tags handling
|
1. Define static getter `pasteConfig` in Tool class. Specify handled patterns there.
|
||||||
|
2. Define public method `onPaste` that will handle PasteEvent to process pasted data.
|
||||||
|
|
||||||
To handle pasted HTML elements object returned from `onPaste` getter should contain following fields:
|
### HTML tags handling
|
||||||
|
|
||||||
|
To handle pasted HTML elements object returned from `pasteConfig` getter should contain following field:
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| -- | -- | -- |
|
| -- | -- | -- |
|
||||||
| `handler(content: HTMLElement)` | `Function` | _Optional_. Pasted HTML elements handler. Gets one argument `content`. `content` is HTML element extracted from pasted data. Handler should return the same object as Tool's `save` method |
|
| `tags` | `String[]` | _Optional_. Should contain all tag names you want to be extracted from pasted data and processed by your `onPaste` method |
|
||||||
| `tags` | `String[]` | _Optional_. Should contain all tag names you want to be extracted from pasted data and be passed to your `handler` method |
|
|
||||||
|
|
||||||
|
For correct work you MUST provide `onPaste` handler at least for `initialBlock` Tool.
|
||||||
For correct work you MUST provide `onPaste.handler` at least for `initialBlock` Tool.
|
|
||||||
|
|
||||||
> Example
|
> Example
|
||||||
|
|
||||||
Header tool can handle `H1`-`H6` tags using paste handling API
|
Header Tool can handle `H1`-`H6` tags using paste handling API
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
static get onPaste() {
|
static get pasteConfig() {
|
||||||
return {
|
return {
|
||||||
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'],
|
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'],
|
||||||
handler: (element) => ({
|
|
||||||
type: element.tagName,
|
|
||||||
text: element.innerHTML
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> One tag can be handled by one Tool only.
|
> Same tag can be handled by one (first specified) Tool only.
|
||||||
|
|
||||||
##### Patterns handling
|
### RegExp patterns handling
|
||||||
|
|
||||||
Your Tool can analyze text by RegExp patterns to substitute pasted string with data you want. Object returned from `onPaste` getter should contain following fields to use patterns:
|
Your Tool can analyze text by RegExp patterns to substitute pasted string with data you want. Object returned from `pasteConfig` getter should contain following field to use patterns:
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| -- | -- | -- |
|
| -- | -- | -- |
|
||||||
| `patterns` | `Object` | _Optional_. `patterns` object contains RegExp patterns with their names as object's keys |
|
| `patterns` | `Object` | _Optional_. `patterns` object contains RegExp patterns with their names as object's keys |
|
||||||
| `patternHandler(text: string, key: string)` | `Function` | _Optional_. Gets pasted string and pattern name. Should return the same object as Tool `save` method |
|
|
||||||
|
|
||||||
**Note** Editor will check pattern's full match, so don't forget to handle all available chars in there.
|
**Note** Editor will check pattern's full match, so don't forget to handle all available chars in there.
|
||||||
|
|
||||||
|
@ -144,70 +144,91 @@ Pattern will be processed only if paste was on `initialBlock` Tool and pasted st
|
||||||
|
|
||||||
> Example
|
> Example
|
||||||
|
|
||||||
You can handle youtube links and insert embeded video instead:
|
You can handle YouTube links and insert embeded video instead:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
static get onPaste() {
|
static get pasteConfig() {
|
||||||
return {
|
return {
|
||||||
patterns: {
|
patterns: {
|
||||||
youtube: /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?/
|
youtube: /http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?/
|
||||||
},
|
},
|
||||||
patternHandler: (text, key) => {
|
|
||||||
const urlData = Youtube.onPaste.patterns[key].exec(text);
|
|
||||||
|
|
||||||
return {
|
|
||||||
iframe: Youtube.makeEmbededFromURL(urlData)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> Both `onPaste.handler` and `onPaste.patternHandler` can be `async` or return a `Promise`.
|
### Files pasting
|
||||||
|
|
||||||
##### Files
|
|
||||||
|
|
||||||
Your Tool can handle files pasted or dropped into the Editor.
|
Your Tool can handle files pasted or dropped into the Editor.
|
||||||
|
|
||||||
To handle file you should provide `files` and `fileHandler` properties in your `onPaste` configuration object.
|
To handle file you should provide `files` property in your `pasteConfig` configuration object.
|
||||||
|
|
||||||
`fileHandler` property should be a function which takes File object as an argument and returns the same object as Tool\`s `save` method.
|
`files` property is an object with the following fields:
|
||||||
|
|
||||||
`file` property is an object with the following fields:
|
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| `extensions` | `string[]` | _Optional_ Array of extensions your Tool can handle |
|
| `extensions` | `string[]` | _Optional_ Array of extensions your Tool can handle |
|
||||||
| `mimeTypes` | `sring[]` | _Optional_ Array of MIME types your Tool can handle |
|
| `mimeTypes` | `sring[]` | _Optional_ Array of MIME types your Tool can handle |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Example
|
Example
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
static get onPaste() {
|
static get pasteConfig() {
|
||||||
return {
|
return {
|
||||||
files: {
|
files: {
|
||||||
mimeTypes: ['image/png'],
|
mimeTypes: ['image/png'],
|
||||||
extensions: ['json']
|
extensions: ['json']
|
||||||
},
|
|
||||||
fileHandler: (file) => {
|
|
||||||
/* do smth with the file */
|
|
||||||
|
|
||||||
return {
|
|
||||||
data // Some extracted content
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sanitize
|
### Pasted data handling
|
||||||
|
|
||||||
|
If you registered some paste substitutions in `pasteConfig` property, you **should** provide `onPaste` callback in your Tool class.
|
||||||
|
`onPaste` should be public non-static method. It accepts custom _PasteEvent_ object as argument.
|
||||||
|
|
||||||
|
PasteEvent is an alias for three types of events - `tag`, `pattern` and `file`. You can get the type from _PasteEvent_ object's `type` property.
|
||||||
|
Each of these events provide `detail` property with info about pasted content.
|
||||||
|
|
||||||
|
| Type | Detail |
|
||||||
|
| ----- | ------ |
|
||||||
|
| `tag` | `data` - pasted HTML element |
|
||||||
|
| `pattern` | `key` - matched pattern key you specified in `pasteConfig` object <br /> `data` - pasted string |
|
||||||
|
| `file` | `file` - pasted file |
|
||||||
|
|
||||||
|
Example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
onPaste (event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'tag':
|
||||||
|
const element = event.detail.data;
|
||||||
|
|
||||||
|
this.handleHTMLPaste(element);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pattern':
|
||||||
|
const text = event.detail.data;
|
||||||
|
const key = event.detail.key;
|
||||||
|
|
||||||
|
this.handlePatternPaste(key, text);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'file':
|
||||||
|
const file = event.detail.file;
|
||||||
|
|
||||||
|
this.handleFilePaste(file);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sanitize
|
||||||
|
|
||||||
CodeX Editor provides [API](sanitizer.md) to clean taint strings.
|
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.
|
Use it manually at the `save()` method or or pass `sanitizer` config to do it automatically.
|
||||||
|
|
||||||
#### Sanitizer Configuration
|
### Sanitizer Configuration
|
||||||
|
|
||||||
The example of sanitizer configuration
|
The example of sanitizer configuration
|
||||||
|
|
||||||
|
@ -220,7 +241,7 @@ let sanitizerConfig = {
|
||||||
|
|
||||||
Keys of config object is tags and the values is a rules.
|
Keys of config object is tags and the values is a rules.
|
||||||
|
|
||||||
##### Rule
|
#### Rule
|
||||||
|
|
||||||
Rule can be boolean, object or function. Object is a dictionary of rules for tag's attributes.
|
Rule can be boolean, object or function. Object is a dictionary of rules for tag's attributes.
|
||||||
|
|
||||||
|
@ -262,7 +283,7 @@ a: function(el) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Manual sanitize
|
### Manual sanitize
|
||||||
|
|
||||||
Call API method `sanitizer.clean()` at the save method for each field in returned data.
|
Call API method `sanitizer.clean()` at the save method for each field in returned data.
|
||||||
|
|
||||||
|
@ -274,7 +295,7 @@ save() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Automatic sanitize
|
### Automatic sanitize
|
||||||
|
|
||||||
If you pass the sanitizer config as static getter, 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.
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<pre class="ce-example__output-content" id="output"></pre>
|
<pre class="ce-example__output-content" id="output"></pre>
|
||||||
|
|
||||||
<div class="ce-example__output-footer">
|
<div class="ce-example__output-footer">
|
||||||
<a href="https://ifmo.su" style="font-weight: bold">Made by CodeX</a>
|
<a href="https://ifmo.su" style="font-weight: bold;">Made by CodeX</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,6 +42,7 @@
|
||||||
https://github.com/codex-editor/header#installation
|
https://github.com/codex-editor/header#installation
|
||||||
-->
|
-->
|
||||||
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
|
<script src="./tools/header/dist/bundle.js"></script><!-- Header -->
|
||||||
|
<script src="./tools/simple-image/dist/bundle.js"></script><!-- Image -->
|
||||||
<script src="./tools/image/dist/bundle.js"></script><!-- Image -->
|
<script src="./tools/image/dist/bundle.js"></script><!-- Image -->
|
||||||
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
|
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
|
||||||
<script src="./tools/list/dist/bundle.js"></script><!-- List -->
|
<script src="./tools/list/dist/bundle.js"></script><!-- List -->
|
||||||
|
@ -94,7 +95,10 @@
|
||||||
image: {
|
image: {
|
||||||
class: ImageTool,
|
class: ImageTool,
|
||||||
config: {
|
config: {
|
||||||
url: 'http://localhost:8008',
|
endpoints: {
|
||||||
|
byFile: 'http://localhost:8008/uploadFile',
|
||||||
|
byUrl: 'http://localhost:8008/fetchUrl',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
inlineToolbar: ['link'],
|
inlineToolbar: ['link'],
|
||||||
},
|
},
|
||||||
|
@ -232,7 +236,7 @@
|
||||||
type: 'image',
|
type: 'image',
|
||||||
data: {
|
data: {
|
||||||
file: {
|
file: {
|
||||||
url : 'https://ifmo.su/upload/redactor_images/o_e48549d1855c7fc1807308dd14990126.jpg',
|
url: 'https://ifmo.su/upload/redactor_images/o_e48549d1855c7fc1807308dd14990126.jpg',
|
||||||
},
|
},
|
||||||
caption: '',
|
caption: '',
|
||||||
stretched: false,
|
stretched: false,
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit d0874d51fabb8f8881926306038079457f0db114
|
Subproject commit 860d3046d0483046b9389d27e3a9c1ab51cf6b87
|
|
@ -1 +1 @@
|
||||||
Subproject commit f64378a2f18ee69c66860a3e45d1e392417a4ca7
|
Subproject commit af3d6545056ef07498363c9b160ad3e0df15bb0f
|
|
@ -1 +1 @@
|
||||||
Subproject commit da319d4757f1909d049f0f205be62ac08ef377f5
|
Subproject commit a25681245f0fdbee1b4a01108e8384bff363a80f
|
|
@ -1 +1 @@
|
||||||
Subproject commit 24a5fe205d55ab481233e60a8263f1433c316852
|
Subproject commit e45da06890c453cb79c8aa88cf657ad988fcc1ca
|
|
@ -1 +1 @@
|
||||||
Subproject commit f14f258b3d993e3b58db76f668d956134fcd813e
|
Subproject commit c6b832e5e4801f531011923a6c7340fb9a0067fa
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7642bb2b541e417307281849d24bc0cce6ff25e2
|
Subproject commit 99c37eb07f9ed93551dd8ca2678f2ff740c6a15f
|
|
@ -1 +1 @@
|
||||||
Subproject commit e970963af843ac1b6131503545f0581325b33f37
|
Subproject commit d026d7d36f1b20e24ea7990b4f629b5b3abb8791
|
|
@ -1 +1 @@
|
||||||
Subproject commit cfde1bc77e32ca884756f11832da282ba73b16b2
|
Subproject commit 169bff33ddec03396f9b193de11b2adf03df7511
|
10378
package-lock.json
generated
10378
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "codex.editor",
|
"name": "codex.editor",
|
||||||
"version": "2.5.6",
|
"version": "2.6.0",
|
||||||
"description": "Codex Editor. Native JS, based on API and Open Source",
|
"description": "Codex Editor. Native JS, based on API and Open Source",
|
||||||
"main": "build/codex-editor.js",
|
"main": "build/codex-editor.js",
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
"ts-loader": "^5.3.0",
|
"ts-loader": "^5.3.0",
|
||||||
"tslint": "^5.11.0",
|
"tslint": "^5.11.0",
|
||||||
"tslint-loader": "^3.6.0",
|
"tslint-loader": "^3.6.0",
|
||||||
"typescript": "^2.9.2",
|
"typescript": "^3.1.6",
|
||||||
"webpack": "4.20.2",
|
"webpack": "4.20.2",
|
||||||
"webpack-cli": "^3.1.0"
|
"webpack-cli": "^3.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,7 +191,7 @@ export default class Dom {
|
||||||
sibling = atLast ? 'previousSibling' : 'nextSibling';
|
sibling = atLast ? 'previousSibling' : 'nextSibling';
|
||||||
|
|
||||||
if (node && node.nodeType === Node.ELEMENT_NODE && node[child]) {
|
if (node && node.nodeType === Node.ELEMENT_NODE && node[child]) {
|
||||||
let nodeChild = node[child];
|
let nodeChild = node[child] as Node;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* special case when child is single tag that can't contain any content
|
* special case when child is single tag that can't contain any content
|
||||||
|
|
|
@ -9,8 +9,9 @@
|
||||||
import Block from '../block';
|
import Block from '../block';
|
||||||
import Module from '../__module';
|
import Module from '../__module';
|
||||||
import $ from '../dom';
|
import $ from '../dom';
|
||||||
|
import _ from '../utils';
|
||||||
import Blocks from '../blocks';
|
import Blocks from '../blocks';
|
||||||
import {BlockTool, BlockToolConstructable, BlockToolData, ToolConfig} from '../../../types';
|
import {BlockTool, BlockToolConstructable, BlockToolData, PasteEvent, ToolConfig} from '../../../types';
|
||||||
import Caret from './caret';
|
import Caret from './caret';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,7 +150,7 @@ export default class BlockManager extends Module {
|
||||||
*
|
*
|
||||||
* @return {Block}
|
* @return {Block}
|
||||||
*/
|
*/
|
||||||
public composeBlock(toolName: string, data: BlockToolData, settings?: ToolConfig): Block {
|
public composeBlock(toolName: string, data: BlockToolData = {}, settings: ToolConfig = {}): Block {
|
||||||
const toolInstance = this.Editor.Tools.construct(toolName, data) as BlockTool;
|
const toolInstance = this.Editor.Tools.construct(toolName, data) as BlockTool;
|
||||||
const toolClass = this.Editor.Tools.available[toolName] as BlockToolConstructable;
|
const toolClass = this.Editor.Tools.available[toolName] as BlockToolConstructable;
|
||||||
const block = new Block(toolName, toolInstance, toolClass, settings, this.Editor.API.methods);
|
const block = new Block(toolName, toolInstance, toolClass, settings, this.Editor.API.methods);
|
||||||
|
@ -182,6 +183,34 @@ export default class BlockManager extends Module {
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert pasted content. Call onPaste callback after insert.
|
||||||
|
*
|
||||||
|
* @param {string} toolName
|
||||||
|
* @param {PasteEvent} pasteEvent - pasted data
|
||||||
|
* @param {boolean} replace - should replace current block
|
||||||
|
*/
|
||||||
|
public paste(
|
||||||
|
toolName: string,
|
||||||
|
pasteEvent: PasteEvent,
|
||||||
|
replace: boolean = false,
|
||||||
|
): Block {
|
||||||
|
let block;
|
||||||
|
|
||||||
|
if (replace) {
|
||||||
|
block = this.replace(toolName);
|
||||||
|
} else {
|
||||||
|
block = this.insert(toolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
block.call('onPaste', pasteEvent);
|
||||||
|
} catch (e) {
|
||||||
|
_.log(`${toolName}: onPaste callback call is failed`, 'error', e);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Always inserts at the end
|
* Always inserts at the end
|
||||||
* @return {Block}
|
* @return {Block}
|
||||||
|
@ -266,7 +295,7 @@ export default class BlockManager extends Module {
|
||||||
const extractedFragment = this.Editor.Caret.extractFragmentFromCaretPosition();
|
const extractedFragment = this.Editor.Caret.extractFragmentFromCaretPosition();
|
||||||
const wrapper = $.make('div');
|
const wrapper = $.make('div');
|
||||||
|
|
||||||
wrapper.append(extractedFragment);
|
wrapper.append(extractedFragment as DocumentFragment);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo make object in accordance with Tool
|
* @todo make object in accordance with Tool
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default class Events extends Module {
|
||||||
* Object with events` names as key and array of callback functions as value
|
* Object with events` names as key and array of callback functions as value
|
||||||
* @type {{}}
|
* @type {{}}
|
||||||
*/
|
*/
|
||||||
private subscribers: {[name: string]: Array<(data?: any) => void>} = {};
|
private subscribers: {[name: string]: Array<(data?: any) => any>} = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe any event on callback
|
* Subscribe any event on callback
|
||||||
|
@ -27,7 +27,7 @@ export default class Events extends Module {
|
||||||
* @param {String} eventName - event name
|
* @param {String} eventName - event name
|
||||||
* @param {Function} callback - subscriber
|
* @param {Function} callback - subscriber
|
||||||
*/
|
*/
|
||||||
public on(eventName: string, callback: (data: any) => void) {
|
public on(eventName: string, callback: (data: any) => any) {
|
||||||
if (!(eventName in this.subscribers)) {
|
if (!(eventName in this.subscribers)) {
|
||||||
this.subscribers[eventName] = [];
|
this.subscribers[eventName] = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import SelectionUtils from '../selection';
|
||||||
import Module from '../__module';
|
import Module from '../__module';
|
||||||
import $ from '../dom';
|
import $ from '../dom';
|
||||||
import _ from '../utils';
|
import _ from '../utils';
|
||||||
import {BlockToolData, PasteConfig} from '../../../types';
|
import {BlockTool, BlockToolConstructable, PasteConfig, PasteEvent, PasteEventDetail} from '../../../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag substitute object.
|
* Tag substitute object.
|
||||||
|
@ -15,14 +15,6 @@ interface TagSubstitute {
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
tool: string;
|
tool: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback to handle pasted element
|
|
||||||
*
|
|
||||||
* @param {HTMLElement} element
|
|
||||||
* @return {BlockToolData}
|
|
||||||
*/
|
|
||||||
handler: (element: HTMLElement) => BlockToolData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,15 +33,6 @@ interface PatternSubstitute {
|
||||||
*/
|
*/
|
||||||
pattern: RegExp;
|
pattern: RegExp;
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback to handle pasted pattern
|
|
||||||
*
|
|
||||||
* @param {string} text
|
|
||||||
* @param {string} key
|
|
||||||
* @return {BlockToolData}
|
|
||||||
*/
|
|
||||||
handler: (text: string, key: string) => BlockToolData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of related Tool
|
* Name of related Tool
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
@ -72,14 +55,6 @@ interface FilesSubstitution {
|
||||||
* @type {string[]}
|
* @type {string[]}
|
||||||
*/
|
*/
|
||||||
mimeTypes: string[];
|
mimeTypes: string[];
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback to handle pasted File
|
|
||||||
*
|
|
||||||
* @param {File} file
|
|
||||||
* @return {BlockToolData}
|
|
||||||
*/
|
|
||||||
handler: (file: File) => BlockToolData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,20 +73,16 @@ interface PasteData {
|
||||||
*/
|
*/
|
||||||
content: HTMLElement;
|
content: HTMLElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pasted data
|
||||||
|
*/
|
||||||
|
event: PasteEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if content should be inserted as new Block
|
* True if content should be inserted as new Block
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
isBlock: boolean;
|
isBlock: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback that returns pasted data in BlockToolData format
|
|
||||||
*
|
|
||||||
* @param {HTMLElement | string} content
|
|
||||||
* @param {RegExp} patten
|
|
||||||
* @return {BlockToolData}
|
|
||||||
*/
|
|
||||||
handler: (content: HTMLElement|string, patten?: RegExp) => BlockToolData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -165,7 +136,7 @@ export default class Paste extends Module {
|
||||||
const { Sanitizer } = this.Editor;
|
const { Sanitizer } = this.Editor;
|
||||||
|
|
||||||
if (dataTransfer.types.includes('Files')) {
|
if (dataTransfer.types.includes('Files')) {
|
||||||
await this.processFiles(dataTransfer.items);
|
await this.processFiles(dataTransfer.files);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,9 +193,19 @@ export default class Paste extends Module {
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {Tool} tool
|
* @param {Tool} tool
|
||||||
*/
|
*/
|
||||||
private processTool = ([name, tool]) => {
|
private processTool = ([name, tool]: [string, BlockToolConstructable]): void => {
|
||||||
try {
|
try {
|
||||||
const toolPasteConfig = tool.onPaste || {};
|
const toolInstance = new this.Editor.Tools.blockTools[name]({
|
||||||
|
api: this.Editor.API.methods,
|
||||||
|
config: {},
|
||||||
|
data: {},
|
||||||
|
}) as BlockTool;
|
||||||
|
|
||||||
|
if (!toolInstance.onPaste || typeof toolInstance.onPaste !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolPasteConfig = tool.pasteConfig || {};
|
||||||
|
|
||||||
this.getTagsConfig(name, toolPasteConfig);
|
this.getTagsConfig(name, toolPasteConfig);
|
||||||
this.getFilesConfig(name, toolPasteConfig);
|
this.getFilesConfig(name, toolPasteConfig);
|
||||||
|
@ -245,26 +226,6 @@ export default class Paste extends Module {
|
||||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
||||||
*/
|
*/
|
||||||
private getTagsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
private getTagsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
||||||
if (this.config.initialBlock === name && !toolPasteConfig.handler) {
|
|
||||||
_.log(
|
|
||||||
`«${name}» Tool must provide a paste handler.`,
|
|
||||||
'warn',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!toolPasteConfig.handler) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof toolPasteConfig.handler !== 'function') {
|
|
||||||
_.log(
|
|
||||||
`Paste handler for «${name}» Tool should be a function.`,
|
|
||||||
'warn',
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tags = toolPasteConfig.tags || [];
|
const tags = toolPasteConfig.tags || [];
|
||||||
|
|
||||||
tags.forEach((tag) => {
|
tags.forEach((tag) => {
|
||||||
|
@ -278,7 +239,6 @@ export default class Paste extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.toolsTags[tag.toUpperCase()] = {
|
this.toolsTags[tag.toUpperCase()] = {
|
||||||
handler: toolPasteConfig.handler,
|
|
||||||
tool: name,
|
tool: name,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -294,15 +254,10 @@ export default class Paste extends Module {
|
||||||
*/
|
*/
|
||||||
private getFilesConfig(name: string, toolPasteConfig: PasteConfig): void {
|
private getFilesConfig(name: string, toolPasteConfig: PasteConfig): void {
|
||||||
|
|
||||||
const {fileHandler, files = {}} = toolPasteConfig;
|
const {files = {}} = toolPasteConfig;
|
||||||
let {extensions, mimeTypes} = files;
|
let {extensions, mimeTypes} = files;
|
||||||
|
|
||||||
if (!fileHandler || (!extensions && !mimeTypes)) {
|
if (!extensions && !mimeTypes) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof fileHandler !== 'function') {
|
|
||||||
_.log(`Drop handler for «${name}» Tool should be a function.`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +285,6 @@ export default class Paste extends Module {
|
||||||
this.toolsFiles[name] = {
|
this.toolsFiles[name] = {
|
||||||
extensions: extensions || [],
|
extensions: extensions || [],
|
||||||
mimeTypes: mimeTypes || [],
|
mimeTypes: mimeTypes || [],
|
||||||
handler: fileHandler,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,16 +295,7 @@ export default class Paste extends Module {
|
||||||
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
* @param {PasteConfig} toolPasteConfig - Tool onPaste configuration
|
||||||
*/
|
*/
|
||||||
private getPatternsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
private getPatternsConfig(name: string, toolPasteConfig: PasteConfig): void {
|
||||||
if (!toolPasteConfig.patternHandler || _.isEmpty(toolPasteConfig.patterns)) {
|
if (!toolPasteConfig.patterns || _.isEmpty(toolPasteConfig.patterns)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof toolPasteConfig.patternHandler !== 'function') {
|
|
||||||
_.log(
|
|
||||||
`Pattern parser for «${name}» Tool should be a function.`,
|
|
||||||
'warn',
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,7 +311,6 @@ export default class Paste extends Module {
|
||||||
this.toolsPatterns.push({
|
this.toolsPatterns.push({
|
||||||
key,
|
key,
|
||||||
pattern,
|
pattern,
|
||||||
handler: toolPasteConfig.patternHandler,
|
|
||||||
tool: name,
|
tool: name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -410,12 +354,12 @@ export default class Paste extends Module {
|
||||||
/**
|
/**
|
||||||
* Get files from data transfer object and insert related Tools
|
* Get files from data transfer object and insert related Tools
|
||||||
*
|
*
|
||||||
* @param {DataTransferItemList} items - pasted or dropped items
|
* @param {FileList} items - pasted or dropped items
|
||||||
*/
|
*/
|
||||||
private async processFiles(items: DataTransferItemList) {
|
private async processFiles(items: FileList) {
|
||||||
const {BlockManager} = this.Editor;
|
const {BlockManager} = this.Editor;
|
||||||
|
|
||||||
let dataToInsert: Array<{type: string, data: BlockToolData}>;
|
let dataToInsert: Array<{type: string, event: PasteEvent}>;
|
||||||
|
|
||||||
dataToInsert = await Promise.all(
|
dataToInsert = await Promise.all(
|
||||||
Array
|
Array
|
||||||
|
@ -427,11 +371,11 @@ export default class Paste extends Module {
|
||||||
dataToInsert.forEach(
|
dataToInsert.forEach(
|
||||||
(data, i) => {
|
(data, i) => {
|
||||||
if (i === 0 && BlockManager.currentBlock && BlockManager.currentBlock.isEmpty) {
|
if (i === 0 && BlockManager.currentBlock && BlockManager.currentBlock.isEmpty) {
|
||||||
BlockManager.replace(data.type, data.data);
|
BlockManager.paste(data.type, data.event, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BlockManager.insert(data.type, data.data);
|
BlockManager.paste(data.type, data.event);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -439,14 +383,9 @@ export default class Paste extends Module {
|
||||||
/**
|
/**
|
||||||
* Get information about file and find Tool to handle it
|
* Get information about file and find Tool to handle it
|
||||||
*
|
*
|
||||||
* @param {DataTransferItem} item
|
* @param {File} file
|
||||||
*/
|
*/
|
||||||
private async processFile(item: DataTransferItem) {
|
private async processFile(file: File) {
|
||||||
if (item.kind === 'string') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = item.getAsFile();
|
|
||||||
const extension = _.getFileExtension(file);
|
const extension = _.getFileExtension(file);
|
||||||
|
|
||||||
const foundConfig = Object
|
const foundConfig = Object
|
||||||
|
@ -468,9 +407,13 @@ export default class Paste extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [tool, {handler}] = foundConfig;
|
const [tool] = foundConfig;
|
||||||
|
const pasteEvent = this.composePasteEvent('file', {
|
||||||
|
file,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: await handler(file),
|
event: pasteEvent,
|
||||||
type: tool,
|
type: tool,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -482,7 +425,7 @@ export default class Paste extends Module {
|
||||||
* @param {boolean} isHTML - if passed string is HTML, this parameter should be true
|
* @param {boolean} isHTML - if passed string is HTML, this parameter should be true
|
||||||
*/
|
*/
|
||||||
private async processText(data: string, isHTML: boolean = false) {
|
private async processText(data: string, isHTML: boolean = false) {
|
||||||
const {Caret, BlockManager} = this.Editor;
|
const {Caret, BlockManager, Tools} = this.Editor;
|
||||||
const dataToInsert = isHTML ? this.processHTML(data) : this.processPlain(data);
|
const dataToInsert = isHTML ? this.processHTML(data) : this.processPlain(data);
|
||||||
|
|
||||||
if (!dataToInsert.length) {
|
if (!dataToInsert.length) {
|
||||||
|
@ -494,16 +437,11 @@ export default class Paste extends Module {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const isCurrentBlockInitial = Tools.isInitial(BlockManager.currentBlock.tool);
|
||||||
* If caret not at the end of of the Block and there is no selection,
|
const needToReplaceCurrentBlock = isCurrentBlockInitial && BlockManager.currentBlock.isEmpty;
|
||||||
* we split the Block and insert content at the middle.
|
|
||||||
*/
|
|
||||||
if (SelectionUtils.isAtEditor && !Caret.isAtEnd && SelectionUtils.isCollapsed) {
|
|
||||||
this.splitBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(dataToInsert.map(
|
await Promise.all(dataToInsert.map(
|
||||||
async (content, i) => await this.insertBlock(content, i === 0),
|
async (content, i) => await this.insertBlock(content, i === 0 && needToReplaceCurrentBlock),
|
||||||
));
|
));
|
||||||
|
|
||||||
Caret.setToBlock(BlockManager.currentBlock, CaretClass.positions.END);
|
Caret.setToBlock(BlockManager.currentBlock, CaretClass.positions.END);
|
||||||
|
@ -516,9 +454,9 @@ export default class Paste extends Module {
|
||||||
* @returns {PasteData[]}
|
* @returns {PasteData[]}
|
||||||
*/
|
*/
|
||||||
private processHTML(innerHTML: string): PasteData[] {
|
private processHTML(innerHTML: string): PasteData[] {
|
||||||
const {Tools, Sanitizer} = this.Editor,
|
const {Tools, Sanitizer} = this.Editor;
|
||||||
initialTool = this.config.initialBlock,
|
const initialTool = this.config.initialBlock;
|
||||||
wrapper = $.make('DIV');
|
const wrapper = $.make('DIV');
|
||||||
|
|
||||||
wrapper.innerHTML = innerHTML;
|
wrapper.innerHTML = innerHTML;
|
||||||
|
|
||||||
|
@ -546,7 +484,7 @@ export default class Paste extends Module {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {handler, tags} = Tools.blockTools[tool].onPaste;
|
const {tags} = Tools.blockTools[tool].pasteConfig;
|
||||||
|
|
||||||
const toolTags = tags.reduce((result, tag) => {
|
const toolTags = tags.reduce((result, tag) => {
|
||||||
result[tag.toLowerCase()] = {};
|
result[tag.toLowerCase()] = {};
|
||||||
|
@ -557,7 +495,11 @@ export default class Paste extends Module {
|
||||||
|
|
||||||
content.innerHTML = Sanitizer.clean(content.innerHTML, customConfig);
|
content.innerHTML = Sanitizer.clean(content.innerHTML, customConfig);
|
||||||
|
|
||||||
return {content, isBlock, handler, tool};
|
const event = this.composePasteEvent('tag', {
|
||||||
|
data: content,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {content, isBlock, tool, event};
|
||||||
})
|
})
|
||||||
.filter((data) => !$.isNodeEmpty(data.content) || $.isSingleTag(data.content));
|
.filter((data) => !$.isNodeEmpty(data.content) || $.isSingleTag(data.content));
|
||||||
}
|
}
|
||||||
|
@ -576,8 +518,7 @@ export default class Paste extends Module {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const tool = initialBlock,
|
const tool = initialBlock;
|
||||||
handler = Tools.blockTools[tool].onPaste.handler;
|
|
||||||
|
|
||||||
return plain
|
return plain
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
|
@ -587,7 +528,11 @@ export default class Paste extends Module {
|
||||||
|
|
||||||
content.innerHTML = text;
|
content.innerHTML = text;
|
||||||
|
|
||||||
return {content, tool, isBlock: false, handler};
|
const event = this.composePasteEvent('tag', {
|
||||||
|
data: content,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {content, tool, isBlock: false, event};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,27 +546,21 @@ export default class Paste extends Module {
|
||||||
*/
|
*/
|
||||||
private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
|
private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
|
||||||
const initialTool = this.config.initialBlock,
|
const initialTool = this.config.initialBlock,
|
||||||
{BlockManager, Caret, Sanitizer} = this.Editor,
|
{BlockManager, Caret, Sanitizer, Tools} = this.Editor,
|
||||||
{content, tool} = dataToInsert;
|
{content, tool} = dataToInsert;
|
||||||
|
|
||||||
if (tool === initialTool && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
|
if (tool === initialTool && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
|
||||||
const blockData = await this.processPattern(content.textContent);
|
const blockData = await this.processPattern(content.textContent);
|
||||||
|
|
||||||
if (blockData) {
|
if (blockData) {
|
||||||
this.splitBlock();
|
|
||||||
let insertedBlock;
|
let insertedBlock;
|
||||||
|
|
||||||
const sanitizeConfig = Sanitizer.composeToolConfig(tool);
|
const needToReplaceCurrentBlock = BlockManager.currentBlock
|
||||||
|
&& Tools.isInitial(BlockManager.currentBlock.tool)
|
||||||
|
&& BlockManager.currentBlock.isEmpty;
|
||||||
|
|
||||||
if (!_.isEmpty(sanitizeConfig)) {
|
insertedBlock = BlockManager.paste(blockData.tool, blockData.event, needToReplaceCurrentBlock);
|
||||||
blockData.data = Sanitizer.deepSanitize(blockData.data, sanitizeConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BlockManager.currentBlock && BlockManager.currentBlock.isEmpty) {
|
|
||||||
insertedBlock = BlockManager.replace(blockData.tool, blockData.data);
|
|
||||||
} else {
|
|
||||||
insertedBlock = BlockManager.insert(blockData.tool, blockData.data);
|
|
||||||
}
|
|
||||||
Caret.setToBlock(insertedBlock, CaretClass.positions.END);
|
Caret.setToBlock(insertedBlock, CaretClass.positions.END);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -639,7 +578,7 @@ export default class Paste extends Module {
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @returns Promise<{data: BlockToolData, tool: string}>
|
* @returns Promise<{data: BlockToolData, tool: string}>
|
||||||
*/
|
*/
|
||||||
private async processPattern(text: string): Promise<{data: BlockToolData, tool: string}> {
|
private async processPattern(text: string): Promise<{event: PasteEvent, tool: string}> {
|
||||||
const pattern = this.toolsPatterns.find((substitute) => {
|
const pattern = this.toolsPatterns.find((substitute) => {
|
||||||
const execResult = substitute.pattern.exec(text);
|
const execResult = substitute.pattern.exec(text);
|
||||||
|
|
||||||
|
@ -650,10 +589,17 @@ export default class Paste extends Module {
|
||||||
return text === execResult.shift();
|
return text === execResult.shift();
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = pattern && await pattern.handler(text, pattern.key);
|
if (!pattern) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return data && {
|
const event = this.composePasteEvent('pattern', {
|
||||||
data,
|
key: pattern.key,
|
||||||
|
data: text,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
event,
|
||||||
tool: pattern.tool,
|
tool: pattern.tool,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -665,40 +611,19 @@ export default class Paste extends Module {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
private async insertBlock(data: PasteData, canReplaceCurrentBlock: boolean = false): Promise<void> {
|
private async insertBlock(data: PasteData, canReplaceCurrentBlock: boolean = false): Promise<void> {
|
||||||
const blockData = await data.handler(data.content),
|
const {BlockManager, Caret} = this.Editor;
|
||||||
{BlockManager, Caret} = this.Editor,
|
const {currentBlock} = BlockManager;
|
||||||
{currentBlock} = BlockManager;
|
|
||||||
|
|
||||||
if (canReplaceCurrentBlock && currentBlock && currentBlock.isEmpty) {
|
if (canReplaceCurrentBlock && currentBlock && currentBlock.isEmpty) {
|
||||||
BlockManager.replace(data.tool, blockData);
|
BlockManager.paste(data.tool, data.event, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const block = BlockManager.insert(data.tool, blockData);
|
const block = BlockManager.paste(data.tool, data.event);
|
||||||
|
|
||||||
Caret.setToBlock(block);
|
Caret.setToBlock(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Split current block if paste isn't in the end of the block
|
|
||||||
*/
|
|
||||||
private splitBlock() {
|
|
||||||
const {BlockManager, Caret} = this.Editor;
|
|
||||||
|
|
||||||
if (!BlockManager.currentBlock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** If we paste into middle of the current block:
|
|
||||||
* 1. Split
|
|
||||||
* 2. Navigate to the first part
|
|
||||||
*/
|
|
||||||
if (!BlockManager.currentBlock.isEmpty && !Caret.isAtEnd) {
|
|
||||||
BlockManager.split();
|
|
||||||
BlockManager.currentBlockIndex--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively divide HTML string to two types of nodes:
|
* Recursively divide HTML string to two types of nodes:
|
||||||
* 1. Block element
|
* 1. Block element
|
||||||
|
@ -778,4 +703,16 @@ export default class Paste extends Module {
|
||||||
|
|
||||||
return children.reduce(reducer, []);
|
return children.reduce(reducer, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compose paste event with passed type and detail
|
||||||
|
*
|
||||||
|
* @param {string} type
|
||||||
|
* @param {PasteEventDetail} detail
|
||||||
|
*/
|
||||||
|
private composePasteEvent(type: string, detail: PasteEventDetail): PasteEvent {
|
||||||
|
return new CustomEvent(type, {
|
||||||
|
detail,
|
||||||
|
}) as PasteEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,7 +298,7 @@ export default class Sanitizer extends Module {
|
||||||
* At least, if there is no config overrides, that API uses Default configuration
|
* At least, if there is no config overrides, that API uses Default configuration
|
||||||
*
|
*
|
||||||
* @uses https://www.npmjs.com/package/html-janitor
|
* @uses https://www.npmjs.com/package/html-janitor
|
||||||
* @licence https://github.com/guardian/html-janitor/blob/master/LICENSE
|
* @license https://github.com/guardian/html-janitor/blob/master/LICENSE
|
||||||
*
|
*
|
||||||
* @param {SanitizerConfig} config - sanitizer extension
|
* @param {SanitizerConfig} config - sanitizer extension
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,10 +6,11 @@
|
||||||
interface Element {
|
interface Element {
|
||||||
matchesSelector: (selector: string) => boolean;
|
matchesSelector: (selector: string) => boolean;
|
||||||
mozMatchesSelector: (selector: string) => boolean;
|
mozMatchesSelector: (selector: string) => boolean;
|
||||||
|
msMatchesSelector: (selector: string) => boolean;
|
||||||
oMatchesSelector: (selector: string) => boolean;
|
oMatchesSelector: (selector: string) => boolean;
|
||||||
|
|
||||||
prepend: (nodes: Node|Node[]|any) => void;
|
prepend: (nodes: Node|Node[]|DocumentFragment) => void;
|
||||||
append: (nodes: Node|Node[]|DocumentFragment|void) => void;
|
append: (nodes: Node|Node[]|DocumentFragment) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 053e9a4885bca063ee1c571e1dacfc5bbe8fea76
|
Subproject commit bf229afc88e682530c82c8fa12aadc85c8a41c8b
|
|
@ -4,6 +4,6 @@
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"moduleResolution": "node", // This resolution strategy attempts to mimic the Node.js module resolution mechanism at runtime
|
"moduleResolution": "node", // This resolution strategy attempts to mimic the Node.js module resolution mechanism at runtime
|
||||||
"lib": ["es2017", "dom"]
|
"lib": ["dom", "es2017", "es2018"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"import-sources-order": "any",
|
"import-sources-order": "any",
|
||||||
"named-imports-order": "case-insensitive"
|
"named-imports-order": "case-insensitive"
|
||||||
}],
|
}],
|
||||||
|
"no-string-literal": false,
|
||||||
"no-empty": false,
|
"no-empty": false,
|
||||||
"no-namespace": false,
|
"no-namespace": false,
|
||||||
"variable-name": [true, "allow-leading-underscore", "allow-pascal-case"]
|
"variable-name": [true, "allow-leading-underscore", "allow-pascal-case"]
|
||||||
|
|
8
types/index.d.ts
vendored
8
types/index.d.ts
vendored
|
@ -20,6 +20,14 @@ export {
|
||||||
BlockToolData,
|
BlockToolData,
|
||||||
ToolSettings,
|
ToolSettings,
|
||||||
ToolConfig,
|
ToolConfig,
|
||||||
|
PasteEvent,
|
||||||
|
PasteEventDetail,
|
||||||
|
PatternPasteEvent,
|
||||||
|
PatternPasteEventDetail,
|
||||||
|
HTMLPasteEvent,
|
||||||
|
HTMLPasteEventDetail,
|
||||||
|
FilePasteEvent,
|
||||||
|
FilePasteEventDetail,
|
||||||
} from './tools';
|
} from './tools';
|
||||||
export {BlockTune, BlockTuneConstructable} from './block-tunes';
|
export {BlockTune, BlockTuneConstructable} from './block-tunes';
|
||||||
export {EditorConfig, SanitizerConfig, PasteConfig} from './configs';
|
export {EditorConfig, SanitizerConfig, PasteConfig} from './configs';
|
||||||
|
|
8
types/tools/block-tool.d.ts
vendored
8
types/tools/block-tool.d.ts
vendored
|
@ -3,6 +3,7 @@ import {BlockToolData} from './block-tool-data';
|
||||||
import {Tool, ToolConstructable} from './tool';
|
import {Tool, ToolConstructable} from './tool';
|
||||||
import {ToolConfig} from './tool-config';
|
import {ToolConfig} from './tool-config';
|
||||||
import {API} from '../index';
|
import {API} from '../index';
|
||||||
|
import {PasteEvent} from './paste-events';
|
||||||
/**
|
/**
|
||||||
* Describe Block Tool object
|
* Describe Block Tool object
|
||||||
* @see {@link docs/tools.md}
|
* @see {@link docs/tools.md}
|
||||||
|
@ -50,6 +51,8 @@ export interface BlockTool extends Tool {
|
||||||
* @param {BlockToolData} blockData
|
* @param {BlockToolData} blockData
|
||||||
*/
|
*/
|
||||||
merge?(blockData: BlockToolData): void;
|
merge?(blockData: BlockToolData): void;
|
||||||
|
|
||||||
|
onPaste?(event: PasteEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockToolConstructable extends ToolConstructable {
|
export interface BlockToolConstructable extends ToolConstructable {
|
||||||
|
@ -68,5 +71,10 @@ export interface BlockToolConstructable extends ToolConstructable {
|
||||||
*/
|
*/
|
||||||
onPaste?: PasteConfig;
|
onPaste?: PasteConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste substitutions configuration
|
||||||
|
*/
|
||||||
|
pasteConfig: PasteConfig;
|
||||||
|
|
||||||
new (config: {api: API, config: ToolConfig, data: BlockToolData}): BlockTool;
|
new (config: {api: API, config: ToolConfig, data: BlockToolData}): BlockTool;
|
||||||
}
|
}
|
||||||
|
|
1
types/tools/index.d.ts
vendored
1
types/tools/index.d.ts
vendored
|
@ -4,3 +4,4 @@ export * from './inline-tool';
|
||||||
export * from './tool';
|
export * from './tool';
|
||||||
export * from './tool-config';
|
export * from './tool-config';
|
||||||
export * from './tool-settings';
|
export * from './tool-settings';
|
||||||
|
export * from './paste-events';
|
||||||
|
|
52
types/tools/paste-events.d.ts
vendored
Normal file
52
types/tools/paste-events.d.ts
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* Event detail for tag substitution on paste
|
||||||
|
*/
|
||||||
|
export interface HTMLPasteEventDetail {
|
||||||
|
/**
|
||||||
|
* Pasted element
|
||||||
|
*/
|
||||||
|
data: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste event for tag substitution
|
||||||
|
*/
|
||||||
|
export interface HTMLPasteEvent extends CustomEvent {
|
||||||
|
readonly detail: HTMLPasteEventDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event detail for file substitution on paste
|
||||||
|
*/
|
||||||
|
export interface FilePasteEventDetail {
|
||||||
|
/**
|
||||||
|
* Pasted file
|
||||||
|
*/
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilePasteEvent extends CustomEvent {
|
||||||
|
readonly detail: FilePasteEventDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event detail for pattern substitution on paste
|
||||||
|
*/
|
||||||
|
export interface PatternPasteEventDetail {
|
||||||
|
/**
|
||||||
|
* Pattern key
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pasted string
|
||||||
|
*/
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PatternPasteEvent extends CustomEvent {
|
||||||
|
readonly detail: PatternPasteEventDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PasteEvent = HTMLPasteEvent | FilePasteEvent | PatternPasteEvent;
|
||||||
|
export type PasteEventDetail = HTMLPasteEventDetail | FilePasteEventDetail | PatternPasteEventDetail;
|
10
yarn.lock
10
yarn.lock
|
@ -3199,7 +3199,7 @@ html-comment-regex@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
|
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
|
||||||
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
|
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
|
||||||
|
|
||||||
html-janitor@^2.0.2:
|
html-janitor@^2.0.4:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/html-janitor/-/html-janitor-2.0.4.tgz#ae5a115cdf3331cd5501edd7b5471b18ea44cdbb"
|
resolved "https://registry.yarnpkg.com/html-janitor/-/html-janitor-2.0.4.tgz#ae5a115cdf3331cd5501edd7b5471b18ea44cdbb"
|
||||||
integrity sha512-92J5h9jNZRk30PMHapjHEJfkrBWKCOy0bq3oW2pBungky6lzYSoboBGPMvxl1XRKB2q+kniQmsLsPbdpY7RM2g==
|
integrity sha512-92J5h9jNZRk30PMHapjHEJfkrBWKCOy0bq3oW2pBungky6lzYSoboBGPMvxl1XRKB2q+kniQmsLsPbdpY7RM2g==
|
||||||
|
@ -6873,10 +6873,10 @@ typedarray@^0.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
typescript@^2.9.2:
|
typescript@^3.1.6:
|
||||||
version "2.9.2"
|
version "3.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68"
|
||||||
integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
|
integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==
|
||||||
|
|
||||||
uglify-es@^3.3.4:
|
uglify-es@^3.3.4:
|
||||||
version "3.3.9"
|
version "3.3.9"
|
||||||
|
|
Loading…
Reference in a new issue