Revert "Release: 2.19 (#1341)" (#1363)

This reverts commit 78775703c9.
This commit is contained in:
George Berezhnoy 2020-10-13 00:03:00 +03:00 committed by GitHub
parent 78775703c9
commit b223d63c59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
97 changed files with 757 additions and 2340 deletions

View file

@ -40,7 +40,6 @@
"DataTransfer": true,
"DOMRect": true,
"ClientRect": true,
"ArrayLike": true,
"unknown": true
"ArrayLike": true
}
}

3
.gitmodules vendored
View file

@ -46,6 +46,3 @@
[submodule "example/tools/warning"]
path = example/tools/warning
url = https://github.com/editor-js/warning
[submodule "example/tools/underline"]
path = example/tools/underline
url = https://github.com/editor-js/underline

View file

@ -4,7 +4,7 @@
[![](https://flat.badgen.net/bundlephobia/min/@editorjs/editorjs?color=cyan)](https://www.npmjs.com/package/@editorjs/editorjs)
[![](https://flat.badgen.net/bundlephobia/minzip/@editorjs/editorjs?color=green)](https://www.npmjs.com/package/@editorjs/editorjs)
[![Backers on Open Collective](https://opencollective.com/editorjs/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/editorjs/sponsors/badge.svg)](#sponsors)
[![Sponsors on Open Collective](https://opencollective.com/editorjs/sponsors/badge.svg)](#sponsors)
[![](https://flat.badgen.net/npm/license/@editorjs/editorjs)](https://www.npmjs.com/package/@editorjs/editorjs)
[![Join the chat at https://gitter.im/codex-team/editor.js](https://badges.gitter.im/codex-team/editor.js.svg)](https://gitter.im/codex-team/editor.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@ -17,24 +17,24 @@
If you like Editor.js you can support project improvements and development of new features with a donation to our collective.
👉 [https://opencollective.com/editorjs](https://opencollective.com/editorjs)
### Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/editorjs#sponsor)]
<a href="https://opencollective.com/editorjs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/editorjs/sponsor/0/avatar.svg"></a>
### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/editorjs#backer)]
<a href="https://opencollective.com/editorjs#backers" target="_blank"><img src="https://opencollective.com/editorjs/backers.svg?width=890"></a>
### Contributors
This project exists thanks to all the people who contribute. <img src="https://opencollective.com/editorjs/contributors.svg?width=890&button=false" />
We really welcome new contributors. If you want to make some code with us, please take a look at the [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22). You can write to us on `team@codex.so` or via special [Telegram chat](https://t.me/editorjsdev), or any other way.
We really welcome new contributors. If you want to make some code with us, please take a look at the [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22). You can write to us on `team@codex.so` or via special [Telegram chat](https://t.me/editorjsdev), or any other way.
## Documentation
@ -142,7 +142,7 @@ npm i @editorjs/editorjs
Include module in your application
```javascript
import EditorJS from '@editorjs/editorjs';
const EditorJS = require('@editorjs/editorjs');
```
##### Option B. Use a CDN
@ -240,11 +240,11 @@ Take a look at the [example.html](example/example.html) to view more detailed ex
## Credits and references
- We use [HTMLJanitor](https://github.com/guardian/html-janitor) module in our Sanitizer module.
- We use [HTMLJanitor](https://github.com/guardian/html-janitor) module in our Sanitizer module.
## About team
We are CodeX and we build products for developers and makers.
We are CodeX and we build products for developers and makers.
Follow us on Twitter: [twitter.com/codex_team](https://twitter.com/codex_team)

2
dist/editor.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,27 +1,5 @@
# Changelog
### 2.19
- `New` - Read-only mode 🥳 [#837](https://github.com/codex-team/editor.js/issues/837)
- `New` - RTL mode added [#670](https://github.com/codex-team/editor.js/issues/670)
- `New` - Allows users to provide common `inlineToolbar` property which will be used for all tools whose `inlineToolbar` property is set to `true`. It can be overridden by the tool's own `inlineToolbar` property. Also, inline tools will be ordered according to the order of the inline tools in array provided in the `inlineToolbar` property. [#1056](https://github.com/codex-team/editor.js/issues/1056)
- `New` - Tool's `reset` static method added to the API to clean up any data added by Tool on initialization
- `Improvements` - The `initialBlock` property of Editor config is deprecated. Use the `defaultBlock` instead. [#993](https://github.com/codex-team/editor.js/issues/993)
- `Improvements` - BlockAPI `call()` method now returns the result of calling method, thus allowing it to expose arbitrary data as needed [#1205](https://github.com/codex-team/editor.js/pull/1205)
- `Improvements` - Unuseful log about missed i18n section has been removed [#1269](https://github.com/codex-team/editor.js/issues/1269)
- `Improvements` - Allowed to set `false` as `toolbox` config in order to hide Toolbox button [#1221](https://github.com/codex-team/editor.js/issues/1221)
- `Fix` — Fix problem with types usage [#1183](https://github.com/codex-team/editor.js/issues/1183)
- `Fix` - Fixed issue with Spam clicking the "Click to tune" button duplicates the icons on FireFox. [#1273](https://github.com/codex-team/editor.js/issues/1273)
- `Fix` - Fixed issue with `editor.blocks.delete(index)` method which throws an error when Editor.js is not focused, even after providing a valid index. [#1182](https://github.com/codex-team/editor.js/issues/1182)
- `Fix` - Fixed the issue of toolbar not disappearing on entering input in Chinese, Hindi and some other languages. [#1196](https://github.com/codex-team/editor.js/issues/1196)
- `Fix` - Do not stop events propagation if not needed (essential for React synthetic events) [#1051](https://github.com/codex-team/editor.js/issues/1051) [#946](https://github.com/codex-team/editor.js/issues/946)
- `Fix` - Tool's `destroy` method is not invoked when `editor.destroy()` is called. [#1047](https://github.com/codex-team/editor.js/issues/1047)
- `Fix` - Fixed issue with enter key in inputs and textareas [#920](https://github.com/codex-team/editor.js/issues/920)
- `Fix` - blocks.getBlockByIndex() API method now returns void for indexes out of range [#1270](https://github.com/codex-team/editor.js/issues/1270)
- `Fix` - Fixed the `Tab` key behavior when the caret is not set inside contenteditable element, but the block is selected [#1302](https://github.com/codex-team/editor.js/issues/1302).
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called for native inputs before some contentedtable element changed [#843](https://github.com/codex-team/editor.js/issues/843)
- `Fix` - Fixed the `onChange` callback issue. This method didn't be called after the callback throws an exception [#1339](https://github.com/codex-team/editor.js/issues/1339)
### 2.18
- `New` *I18n API* — Ability to provide internalization for Editor.js core and tools. [#751](https://github.com/codex-team/editor.js/issues/751)
@ -46,6 +24,7 @@
> *Breaking changes* `blocks.getBlockByIndex` method now returns BlockAPI object. To access old value, use BlockAPI.holder property
### 2.17
- `Improvements` - Editor's [onchange callback](https://editorjs.io/configuration#editor-modifications-callback) now accepts an API as a parameter

View file

@ -24,10 +24,10 @@ Install the package via NPM or Yarn
npm i @editorjs/editorjs
```
Include module at your application
Include module at your application
```javascript
import EditorJS from '@editorjs/editorjs';
const EditorJS = require('@editorjs/editorjs');
```
### Use from CDN
@ -68,7 +68,7 @@ Check [Editor.js's community](https://github.com/editor-js/) to see Tools exampl
## Create Editor instance
Create an instance of Editor.js and pass [Configuration Object](../src/types-internal/editor-config.ts).
Create an instance of Editor.js and pass [Configuration Object](../src/types-internal/editor-config.ts).
At least the `holderId` option is required.
```html
@ -119,7 +119,7 @@ Editor.js needs a bit of time to initialize. It is an asynchronous action so it
If you need to know when the editor instance is ready you can use one of the following ways:
##### Pass `onReady` property to the configuration object.
##### Pass `onReady` property to the configuration object.
It must be a function:
@ -189,7 +189,7 @@ var editor = new EditorJS({
* onReady callback
*/
onReady: () => {console.log('Editor.js is ready to work!')},
/**
* onChange callback
*/

View file

@ -78,7 +78,7 @@ var editor = new EditorJS({
},
header: Header
},
defaultBlock : 'text',
initialBlock : 'text',
});
```
@ -89,36 +89,6 @@ There are few options available by Editor.js.
| `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.
## Tool prepare and reset
If you need to prepare some data for Tool (eg. load external script, create HTML nodes in the document, etc) you can use static prepare method.
It accepts tools config passed on Editor's initialization as an argument:
```javascript
class Tool {
static prepare(config) {
loadScript();
insertNodes();
...
}
}
```
On Editor destroy you can use an opposite method `reset` to clean up all prepared data:
```javascript
class Tool {
static reset() {
cleanUpScripts();
deleteNodes();
...
}
}
```
Both methods might be async.
## Paste handling
Editor.js handles paste on Blocks and provides API for Tools to process the pasted data.
@ -148,7 +118,7 @@ To handle pasted HTML elements object returned from `pasteConfig` getter should
| -- | -- | -- |
| `tags` | `String[]` | _Optional_. Should contain all tag names you want to be extracted from pasted data and processed by your `onPaste` method |
For correct work you MUST provide `onPaste` handler at least for `defaultBlock` Tool.
For correct work you MUST provide `onPaste` handler at least for `initialBlock` Tool.
> Example
@ -174,7 +144,7 @@ Your Tool can analyze text by RegExp patterns to substitute pasted string with d
**Note** Editor will check pattern's full match, so don't forget to handle all available chars in there.
Pattern will be processed only if paste was on `defaultBlock` Tool and pasted string length is less than 450 characters.
Pattern will be processed only if paste was on `initialBlock` Tool and pasted string length is less than 450 characters.
> Example

View file

@ -120,32 +120,6 @@ body {
text-decoration: none;
}
.ce-example__statusbar {
position: fixed;
bottom: 10px;
right: 10px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
font-size: 12px;
padding: 8px 15px;
z-index: 1;
}
.ce-example__statusbar-button {
display: inline-flex;
margin-left: 10px;
background: #4A9DF8;
padding: 6px 12px;
box-shadow: 0 7px 8px -4px rgba(137, 207, 255, 0.77);
transition: all 150ms ease;
cursor: pointer;
border-radius: 31px;
color: #fff;
font-family: 'PT Mono', Menlo, Monaco, Consolas, Courier New, monospace;
text-align: center;
}
@media all and (max-width: 730px){
.ce-example__header,
.ce-example__content{

View file

@ -34,15 +34,6 @@
<div class="ce-example__button" id="saveButton">
editor.save()
</div>
<div class="ce-example__statusbar">
Readonly:
<b id="readonly-state">
Off
</b>
<div class="ce-example__statusbar-button" id="toggleReadOnlyButton">
toggle
</div>
</div>
</div>
<div class="ce-example__output">
<pre class="ce-example__output-content" id="output"></pre>
@ -83,29 +74,21 @@
<!-- Initialization -->
<script>
/**
* Saving button
*/
const saveButton = document.getElementById('saveButton');
/**
* To initialize the Editor, create a new instance with configuration object
* @see docs/installation.md for mode details
*/
var editor = new EditorJS({
/**
* Enable/Disable the read only mode
*/
readOnly: false,
/**
* Wrapper of Editor
*/
holder: 'editorjs',
/**
* Common Inline Toolbar settings
* - if true (or not specified), the order from 'tool' property will be used
* - if an array of tool names, this order will be used
*/
// inlineToolbar: ['link', 'marker', 'bold', 'italic'],
// inlineToolbar: true,
/**
* Tools list
*/
@ -115,7 +98,7 @@
*/
header: {
class: Header,
inlineToolbar: ['marker', 'link'],
inlineToolbar: ['link'],
config: {
placeholder: 'Header'
},
@ -184,7 +167,7 @@
/**
* This Tool will be used as default
*/
// defaultBlock: 'paragraph',
// initialBlock: 'paragraph',
/**
* Initial Editor data
@ -296,37 +279,14 @@
},
});
/**
* Saving button
*/
const saveButton = document.getElementById('saveButton');
/**
* Toggle read-only button
*/
const toggleReadOnlyButton = document.getElementById('toggleReadOnlyButton');
const readOnlyIndicator = document.getElementById('readonly-state');
/**
* Saving example
*/
saveButton.addEventListener('click', function () {
editor.save()
.then((savedData) => {
cPreview.show(savedData, document.getElementById("output"));
})
.catch((error) => {
console.error('Saving error', error);
});
});
/**
* Toggle read-only example
*/
toggleReadOnlyButton.addEventListener('click', async () => {
const readOnlyState = await editor.readOnly.toggle();
readOnlyIndicator.textContent = readOnlyState ? 'On' : 'Off';
editor.save().then((savedData) => {
cPreview.show(savedData, document.getElementById("output"));
});
});
</script>
</body>

View file

@ -44,7 +44,7 @@
<!-- Load Tools -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script><!-- Header -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@latest"></script><!-- Image -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/simple-image@latest"></script><!-- Image -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest"></script><!-- Delimiter -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest"></script><!-- List -->
<script src="https://cdn.jsdelivr.net/npm/@editorjs/checklist@latest"></script><!-- Checklist -->
@ -81,11 +81,6 @@
* Tools list
*/
tools: {
paragraph: {
config: {
placeholder: "Enter something"
}
},
/**
* Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md}
*/
@ -101,7 +96,7 @@
/**
* Or pass class directly without any configuration
*/
image: ImageTool,
image: SimpleImage,
list: {
class: List,
@ -214,7 +209,6 @@
"Bold": "Полужирный",
"Italic": "Курсив",
"InlineCode": "Моноширинный",
"Image": "Картинка"
},
/**
@ -241,32 +235,6 @@
*/
"stub": {
'The block can not be displayed correctly.': 'Блок не может быть отображен'
},
"image": {
"Caption": "Подпись",
"Select an Image": "Выберите файл",
"With border": "Добавить рамку",
"Stretch image": "Растянуть",
"With background": "Добавить подложку",
},
"code": {
"Enter a code": "Код",
},
"linkTool": {
"Link": "Ссылка",
"Couldn't fetch the link data": "Не удалось получить данные",
"Couldn't get this link data, try the other one": "Не удалось получить данные по ссылке, попробуйте другую",
"Wrong response format from the server": "Неполадки на сервере",
},
"header": {
"Header": "Заголовок",
},
"paragraph": {
"Enter something": "Введите текст"
},
"list": {
"Ordered": "Нумерованный",
"Unordered": "Маркированный",
}
},
@ -386,9 +354,7 @@
{
type: 'image',
data: {
file : {
url: 'assets/codex2x.png',
},
url: 'assets/codex2x.png',
caption: '',
stretched: false,
withBorder: true,

View file

@ -1,235 +0,0 @@
<!--
Use this page for debugging purposes.
Editor Tools are loaded as git-submodules.
You can pull modules by running `yarn pull_tools` and start experimenting.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Editor.js 🤩🧦🤨 example</title>
<link href="https://fonts.googleapis.com/css?family=PT+Mono" rel="stylesheet">
<link href="assets/demo.css" rel="stylesheet">
<script src="assets/json-preview.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
</head>
<body>
<div class="ce-example">
<div class="ce-example__header">
<a class="ce-example__header-logo" href="https://codex.so/editor">Editor.js 🤩🧦🤨</a>
<div class="ce-example__header-menu">
<a href="https://github.com/editor-js" target="_blank">Plugins</a>
<a href="https://editorjs.io/usage" target="_blank">Usage</a>
<a href="https://editorjs.io/configuration" target="_blank">Configuration</a>
<a href="https://editorjs.io/creating-a-block-tool" target="_blank">API</a>
</div>
</div>
<div class="ce-example__content _ce-example__content--small">
<div id="editorjs"></div>
<div id="hint" style="text-align: center;">
No submodules found. Run <code class="inline-code">yarn pull_tools</code>
</div>
<div class="ce-example__button" id="saveButton">
editor.save()
</div>
</div>
<div class="ce-example__output">
<pre class="ce-example__output-content" id="output"></pre>
<div class="ce-example__output-footer">
<a href="https://codex.so" style="font-weight: bold;">Made by CodeX</a>
</div>
</div>
</div>
<!-- Load Tools -->
<!--
You can upload Tools to your project's directory and use as in example below.
Also you can load each Tool from CDN or use NPM/Yarn packages.
Read more in Tool's README file. For example:
https://github.com/editor-js/header#installation
-->
<script src="./tools/header/dist/bundle.js" onload="document.getElementById('hint').hidden = true"></script><!-- Header -->
<script src="./tools/simple-image/dist/bundle.js"></script><!-- Image -->
<script src="./tools/delimiter/dist/bundle.js"></script><!-- Delimiter -->
<script src="./tools/list/dist/bundle.js"></script><!-- List -->
<script src="./tools/checklist/dist/bundle.js"></script><!-- Checklist -->
<script src="./tools/quote/dist/bundle.js"></script><!-- Quote -->
<script src="./tools/code/dist/bundle.js"></script><!-- Code -->
<script src="./tools/embed/dist/bundle.js"></script><!-- Embed -->
<script src="./tools/table/dist/bundle.js"></script><!-- Table -->
<script src="./tools/link/dist/bundle.js"></script><!-- Link -->
<script src="./tools/raw/dist/bundle.js"></script><!-- Raw -->
<script src="./tools/warning/dist/bundle.js"></script><!-- Warning -->
<script src="./tools/marker/dist/bundle.js"></script><!-- Marker -->
<script src="./tools/inline-code/dist/bundle.js"></script><!-- Inline Code -->
<!-- Load Editor.js's Core -->
<script src="../dist/editor.js"></script>
<!-- Initialization -->
<script>
/**
* Saving button
*/
const saveButton = document.getElementById('saveButton');
/**
* To initialize the Editor, create a new instance with configuration object
* @see docs/installation.md for mode details
*/
var editor = new EditorJS({
/**
* Wrapper of Editor
*/
holder: 'editorjs',
i18n: {
/**
* Text direction
*/
direction: 'rtl',
},
/**
* Tools list
*/
tools: {
/**
* Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md}
*/
header: {
class: Header,
inlineToolbar: ['link'],
config: {
placeholder: 'Header'
},
shortcut: 'CMD+SHIFT+H'
},
/**
* Or pass class directly without any configuration
*/
image: {
class: SimpleImage,
inlineToolbar: ['link'],
},
list: {
class: List,
inlineToolbar: true,
shortcut: 'CMD+SHIFT+L'
},
checklist: {
class: Checklist,
inlineToolbar: true,
},
quote: {
class: Quote,
inlineToolbar: true,
config: {
quotePlaceholder: 'Enter a quote',
captionPlaceholder: 'Quote\'s author',
},
shortcut: 'CMD+SHIFT+O'
},
warning: Warning,
marker: {
class: Marker,
shortcut: 'CMD+SHIFT+M'
},
code: {
class: CodeTool,
shortcut: 'CMD+SHIFT+C'
},
delimiter: Delimiter,
inlineCode: {
class: InlineCode,
shortcut: 'CMD+SHIFT+C'
},
linkTool: LinkTool,
raw: RawTool,
embed: Embed,
table: {
class: Table,
inlineToolbar: true,
shortcut: 'CMD+ALT+T'
},
},
/**
* This Tool will be used as default
*/
// initialBlock: 'paragraph',
/**
* Initial Editor data
*/
data: {
blocks: [
{
type: "header",
data: {
text: "محرر.js",
level: 2
}
},
{
type : 'paragraph',
data : {
text : 'مرحبا! تعرف على المحرر الجديد. في هذه الصفحة ، يمكنك رؤيتها في العمل - حاول تحرير هذا النص.'
}
},
{
type: "header",
data: {
text: "دلائل الميزات",
level: 3
}
},
{
type : 'list',
data : {
items : [
'وهو محرر بنمط الكتلة',
'تقوم بإرجاع إخراج بيانات نظيفة في JSON',
'مصممة لتكون قابلة للتوسيع والتوصيل مع واجهة برمجة تطبيقات بسيطة'
],
style: 'unordered'
}
}
]
},
onReady: function(){
saveButton.click();
},
onChange: function() {
console.log('something changed');
}
});
/**
* Saving example
*/
saveButton.addEventListener('click', function () {
editor.save().then((savedData) => {
cPreview.show(savedData, document.getElementById("output"));
});
});
</script>
</body>
</html>

View file

@ -156,7 +156,7 @@
/**
* This Tool will be used as default
*/
// defaultBlock: 'paragraph',
// initialBlock: 'paragraph',
/**
* Initial Editor data

@ -1 +1 @@
Subproject commit 197d5d53e0c7d869d76afc4eabae566d391e6d0e
Subproject commit df6dcedb92ef586901b04cf72946aced9e36e58d

@ -1 +1 @@
Subproject commit 83d2d9d3136d48ab67b52bc034370c9a26c82c8c
Subproject commit 1297b8c280ff34efaca8f3a2a3d263ec4201077d

@ -1 +1 @@
Subproject commit 1f2ec8c709a94c5f4f499bb1aaba7f4930e10484
Subproject commit 6819831b7166c1cdfa31df77ab569274d9910aac

@ -1 +1 @@
Subproject commit 9d3d4b5216dce1a933b1e92b45b7b8c404d889b7
Subproject commit 44473de4c60dd836ccb61b4dbcf4cc00088acd19

@ -1 +1 @@
Subproject commit 2b21da39b57d0abfcd4979444fb0c3d2d435af7d
Subproject commit 93e0b6d6418034f4e7ee704aba090cc25ca16ac2

@ -1 +1 @@
Subproject commit a983c4e62135c88d6cfd926527e6fc92c304451b
Subproject commit 1d6f474c14613c60344d30ebd930a18ca123e4a4

@ -1 +1 @@
Subproject commit 051b8e9e03e2f6bea30875da8253f6c0d858b231
Subproject commit 37a5e8d1db305cf75acd6622d9b82e2f308f71c6

@ -1 +1 @@
Subproject commit 6a5563630977f223ebafaa03a7df3bf85797437b
Subproject commit 33ffba6f9104d163c69c963ca06fed329a819238

@ -1 +1 @@
Subproject commit 458a5fe364e33ad5b5913155d665f335ab742e0f
Subproject commit f537cf6ecb26fece34c56a85b51e79b07451e69e

@ -1 +1 @@
Subproject commit 6708697c1af79abbf6650f0f14e1cedc45eb5213
Subproject commit c68375288c40774e8d8ceff79aa559d562078aaa

@ -1 +1 @@
Subproject commit 07881fc1020fde79ab9468f740227a99935abb2a
Subproject commit 58bf8bd571ae259e3d150ac0c12d1676e5706470

@ -1 +1 @@
Subproject commit 3f40a9cfdb0086c94ee2c7295a96d69c9b266dec
Subproject commit 7c6d41603797ebfd00d59fc7ff623342b8f5a48c

@ -1 +1 @@
Subproject commit 1883b28d8aac863d3907c21d5fda231c8e6f799a
Subproject commit 0fd96a70b371af0cc0720b8c2c0d0888b8a44bc5

@ -1 +1 @@
Subproject commit 5c1a73a8022c18ac1c15ee8d0134caae029bfbe9
Subproject commit af9dc3885077ab2ea1b0ae8ae0d145ff1a40fc40

@ -1 +1 @@
Subproject commit c0507d91014f9b4fdb514f0499348d7619e3e1a2
Subproject commit 293109d03d9ff3cdecc52ec959866662d80dd0ce

View file

@ -2,12 +2,6 @@ import { EditorModules } from '../types-internal/editor-modules';
import { EditorConfig } from '../../types';
import { ModuleConfig } from '../types-internal/module-config';
/**
* The type <T> of the Module generic.
* It describes the structure of nodes used in modules.
*/
export type ModuleNodes = object;
/**
* @abstract
* @class Module
@ -17,13 +11,7 @@ export type ModuleNodes = object;
* @property {object} config - Editor user settings
* @property {EditorModules} Editor - List of Editor modules
*/
export default class Module<T extends ModuleNodes = {}> {
/**
* Each module can provide some UI elements that will be stored in this property
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public nodes: T = {} as any;
export default class Module {
/**
* Editor modules list
*
@ -38,50 +26,6 @@ export default class Module<T extends ModuleNodes = {}> {
*/
protected config: EditorConfig;
/**
* This object provides methods to push into set of listeners that being dropped when read-only mode is enabled
*/
protected readOnlyMutableListeners = {
/**
* Assigns event listener on DOM element and pushes into special array that might be removed
*
* @param {EventTarget} element - DOM Element
* @param {string} eventType - Event name
* @param {Function} handler - Event handler
* @param {boolean|AddEventListenerOptions} options - Listening options
*/
on: (
element: EventTarget,
eventType: string,
handler: (event: Event) => void,
options: boolean | AddEventListenerOptions = false
): void => {
const { Listeners } = this.Editor;
this.mutableListenerIds.push(
Listeners.on(element, eventType, handler, options)
);
},
/**
* Clears all mutable listeners
*/
clearAll: (): void => {
const { Listeners } = this.Editor;
for (const id of this.mutableListenerIds) {
Listeners.offById(id);
}
this.mutableListenerIds = [];
},
};
/**
* The set of listener identifiers which will be dropped in read-only mode
*/
private mutableListenerIds: string[] = [];
/**
* @class
* @param {EditorConfig} config - Editor's config
@ -102,24 +46,4 @@ export default class Module<T extends ModuleNodes = {}> {
public set state(Editor: EditorModules) {
this.Editor = Editor;
}
/**
* Remove memorized nodes
*/
public removeAllNodes(): void {
for (const key in this.nodes) {
const node = this.nodes[key];
if (node instanceof HTMLElement) {
node.remove();
}
}
}
/**
* Returns true if current direction is RTL (Right-To-Left)
*/
protected get isRtl(): boolean {
return this.config.i18n.direction === 'rtl';
}
}

View file

@ -84,11 +84,6 @@ export default class MoveDownTune implements BlockTune {
}
const nextBlock = this.api.blocks.getBlockByIndex(currentBlockIndex + 1);
if (!nextBlock) {
return;
}
const nextBlockElement = nextBlock.holder;
const nextBlockCoords = nextBlockElement.getBoundingClientRect();

View file

@ -82,13 +82,8 @@ export default class MoveUpTune implements BlockTune {
}
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
const previousBlock = this.api.blocks.getBlockByIndex(currentBlockIndex - 1);
if (!currentBlock || !previousBlock) {
return;
}
const currentBlockElement = currentBlock.holder;
const previousBlock = this.api.blocks.getBlockByIndex(currentBlockIndex - 1);
const previousBlockElement = previousBlock.holder;
/**

View file

@ -1,6 +1,6 @@
import Block from './index';
import { BlockToolData, ToolConfig } from '../../../types/tools';
import { SavedData } from '../../../types/data-formats';
import { SavedData } from '../../types-internal/block-data';
import { BlockAPI as BlockAPIInterface } from '../../../types/api';
/**
@ -10,9 +10,7 @@ import { BlockAPI as BlockAPIInterface } from '../../../types/api';
*
* @param {Block} block - Block to expose
*/
function BlockAPI(
block: Block
): void {
function BlockAPI(block: Block): void {
const blockAPI: BlockAPIInterface = {
/**
* Tool name
@ -83,10 +81,10 @@ function BlockAPI(
* @param {string} methodName - method to call
* @param {object} param - object with parameters
*
* @returns {unknown}
* @returns {void}
*/
call(methodName: string, param?: object): unknown {
return block.call(methodName, param);
call(methodName: string, param?: object): void {
block.call(methodName, param);
},
/**

View file

@ -10,10 +10,11 @@ import {
ToolSettings
} from '../../../types';
import { SavedData } from '../../../types/data-formats';
import { SavedData } from '../../types-internal/block-data';
import $ from '../dom';
import * as _ from '../utils';
import ApiModules from '../modules/api';
import ApiModule from '../modules/api';
import SelectionUtils from '../selection';
import BlockAPI from './api';
import { ToolType } from '../modules/tools';
@ -21,7 +22,6 @@ import { ToolType } from '../modules/tools';
import MoveUpTune from '../block-tunes/block-tune-move-up';
import DeleteTune from '../block-tunes/block-tune-delete';
import MoveDownTune from '../block-tunes/block-tune-move-down';
import SelectionUtils from '../selection';
/**
* Interface describes Block class constructor argument
@ -50,12 +50,7 @@ interface BlockConstructorOptions {
/**
* Editor's API methods
*/
api: ApiModules;
/**
* This flag indicates that the Block should be constructed in the read-only mode.
*/
readOnly: boolean;
api: ApiModule;
}
/**
@ -152,7 +147,7 @@ export default class Block {
/**
* Editor`s API module
*/
private readonly api: ApiModules;
private readonly api: ApiModule;
/**
* Focused input index
@ -203,8 +198,7 @@ export default class Block {
* @param {BlockToolData} options.data - Tool's initial data
* @param {BlockToolConstructable} options.Tool Tool's class
* @param {ToolSettings} options.settings - default tool's config
* @param {Module} options.api - Editor API module for pass it to the Block Tunes
* @param {boolean} options.readOnly - Read-Only flag
* @param {ApiModule} options.api - Editor API module for pass it to the Block Tunes
*/
constructor({
name,
@ -212,7 +206,6 @@ export default class Block {
Tool,
settings,
api,
readOnly,
}: BlockConstructorOptions) {
this.name = name;
this.class = Tool;
@ -228,7 +221,6 @@ export default class Block {
config: this.config,
api: this.api.getMethodsForTool(name, ToolType.Block),
block: this.blockAPI,
readOnly: readOnly,
});
this.holder = this.compose();
@ -629,15 +621,7 @@ export default class Block {
* Update current input index with selection anchor node
*/
public updateCurrentInput(): void {
/**
* If activeElement is native input, anchorNode points to its parent.
* So if it is native input use it instead of anchorNode
*
* If anchorNode is undefined, also use activeElement
*/
this.currentInput = $.isNativeInput(document.activeElement) || !SelectionUtils.anchorNode
? document.activeElement
: SelectionUtils.anchorNode;
this.currentInput = SelectionUtils.anchorNode;
}
/**
@ -656,12 +640,6 @@ export default class Block {
attributes: true,
}
);
/**
* Mutation observer doesn't track changes in "<input>" and "<textarea>"
* so we need to track focus events
*/
this.addInputEvents();
}
/**
@ -669,7 +647,6 @@ export default class Block {
*/
public willUnselect(): void {
this.mutationObserver.disconnect();
this.removeInputEvents();
}
/**
@ -687,37 +664,4 @@ export default class Block {
return wrapper;
}
/**
* Is fired when text input or contentEditable is focused
*/
private handleFocus = (): void => {
/**
* Drop cache
*/
this.cachedInputs = [];
/**
* Update current input
*/
this.updateCurrentInput();
}
/**
* Adds focus event listeners to all inputs and contentEditables
*/
private addInputEvents(): void {
this.inputs.forEach(input => {
input.addEventListener('focus', this.handleFocus);
});
}
/**
* removes focus event listeners from all inputs and contentEditables
*/
private removeInputEvents(): void {
this.inputs.forEach(input => {
input.removeEventListener('focus', this.handleFocus);
});
}
}

View file

@ -1,9 +1,11 @@
import $ from './dom';
// eslint-disable-next-line import/no-duplicates
import * as _ from './utils';
// eslint-disable-next-line import/no-duplicates
import { LogLevels } from './utils';
import { EditorConfig, OutputData, SanitizerConfig } from '../../types';
import { EditorModules } from '../types-internal/editor-modules';
import I18n from './i18n';
import { CriticalError } from './errors/critical';
/**
* @typedef {Core} Core - editor core class
@ -128,10 +130,11 @@ export default class Core {
/**
* If holderId is preset, assign him to holder property and work next only with holder
*/
_.deprecationAssert(!!config.holderId, 'config.holderId', 'config.holder');
if (config.holderId && !config.holder) {
config.holder = config.holderId;
config.holderId = null;
_.log('holderId property is deprecated and will be removed in the next major release. ' +
'Use holder property instead.', 'warn');
}
/**
@ -149,16 +152,15 @@ export default class Core {
}
if (!this.config.logLevel) {
this.config.logLevel = _.LogLevels.VERBOSE;
this.config.logLevel = LogLevels.VERBOSE;
}
_.setLogLevel(this.config.logLevel);
/**
* If default Block's Tool was not passed, use the Paragraph Tool
* If initial Block's Tool was not passed, use the Paragraph Tool
*/
_.deprecationAssert(Boolean(this.config.initialBlock), 'config.initialBlock', 'config.defaultBlock');
this.config.defaultBlock = this.config.defaultBlock || this.config.initialBlock || 'paragraph';
this.config.initialBlock = this.config.initialBlock || 'paragraph';
/**
* Height of Editor's bottom area that allows to set focus on the last Block
@ -168,13 +170,13 @@ export default class Core {
this.config.minHeight = this.config.minHeight !== undefined ? this.config.minHeight : 300;
/**
* Default block type
* Initial block type
* Uses in case when there is no blocks passed
*
* @type {{type: (*), data: {text: null}}}
*/
const defaultBlockData = {
type: this.config.defaultBlock,
const initialBlockData = {
type: this.config.initialBlock,
data: {},
};
@ -187,44 +189,30 @@ export default class Core {
this.config.hideToolbar = this.config.hideToolbar ? this.config.hideToolbar : false;
this.config.tools = this.config.tools || {};
this.config.i18n = this.config.i18n || {};
this.config.data = this.config.data || {} as OutputData;
// eslint-disable-next-line @typescript-eslint/no-empty-function
this.config.onReady = this.config.onReady || ((): void => {});
// eslint-disable-next-line @typescript-eslint/no-empty-function
this.config.onChange = this.config.onChange || ((): void => {});
this.config.inlineToolbar = this.config.inlineToolbar !== undefined ? this.config.inlineToolbar : true;
/**
* Initialize default Block to pass data to the Renderer
* Initialize Blocks to pass data to the Renderer
*/
if (_.isEmpty(this.config.data)) {
this.config.data = {} as OutputData;
this.config.data.blocks = [ defaultBlockData ];
this.config.data.blocks = [ initialBlockData ];
} else {
if (!this.config.data.blocks || this.config.data.blocks.length === 0) {
this.config.data.blocks = [ defaultBlockData ];
this.config.data.blocks = [ initialBlockData ];
}
}
this.config.readOnly = this.config.readOnly as boolean || false;
this.config.i18n = {};
/**
* Adjust i18n
*/
if (config.i18n?.messages) {
if (config.i18n && config.i18n.messages) {
I18n.setDictionary(config.i18n.messages);
}
/**
* Text direction. If not set, uses ltr
*/
if (config.i18n?.direction) {
this.config.i18n = {
direction: config.i18n?.direction || 'ltr',
};
}
}
/**
@ -288,16 +276,12 @@ export default class Core {
const modulesToPrepare = [
'Tools',
'UI',
'Toolbar',
'InlineToolbar',
'BlockManager',
'Paste',
'DragNDrop',
'ModificationsObserver',
'BlockSelection',
'RectangleSelection',
'CrossBlockSelection',
'ReadOnly',
];
await modulesToPrepare.reduce(
@ -307,13 +291,6 @@ export default class Core {
try {
await this.moduleInstances[module].prepare();
} catch (e) {
/**
* CriticalError's will not be caught
* It is used when Editor is rendering in read-only mode with unsupported plugin
*/
if (e instanceof CriticalError) {
throw new Error(e.message);
}
_.log(`Module ${module} was skipped because of %o`, 'warn', e);
}
// _.log(`Preparing ${module} module`, 'timeEnd');

View file

@ -296,7 +296,7 @@ export default class Dom {
}
/**
* Check if object is DocumentFragment node
* Check if object is DocumentFragmemt node
*
* @param {object} node - object to check
* @returns {boolean}

View file

@ -1,5 +0,0 @@
/**
* This type of exception will destroy the Editor! Be careful when using it
*/
export class CriticalError extends Error {
}

View file

@ -1,4 +1,5 @@
import defaultDictionary from './locales/en/messages.json';
import * as _ from '../utils';
import { I18nDictionary, Dictionary } from '../../../types/configs';
import { LeavesDictKeys } from '../../types-internal/i18n-internal-namespace';
@ -59,12 +60,9 @@ export default class I18n {
private static _t(namespace: string, dictKey: string): string {
const section = I18n.getNamespace(namespace);
/**
* For Console Message to Check Section is defined or not
* if (section === undefined) {
* _.logLabeled('I18n: section %o was not found in current dictionary', 'log', namespace);
* }
*/
if (section === undefined) {
_.logLabeled('I18n: section %o was not found in current dictionary', 'log', namespace);
}
if (!section || !section[dictKey]) {
return dictKey;

View file

@ -1,8 +1,9 @@
import Module from '../../__module';
import { BlockAPI as BlockAPIInterface, Blocks } from '../../../../types/api';
import { BlockToolData, OutputData, ToolConfig } from '../../../../types';
import * as _ from './../../utils';
import BlockAPI from '../../block/api';
import Module from '../../__module';
/**
* @class BlocksAPI
@ -22,7 +23,7 @@ export default class BlocksAPI extends Module {
delete: (index?: number): void => this.delete(index),
swap: (fromIndex: number, toIndex: number): void => this.swap(fromIndex, toIndex),
move: (toIndex: number, fromIndex?: number): void => this.move(toIndex, fromIndex),
getBlockByIndex: (index: number): BlockAPIInterface | void => this.getBlockByIndex(index),
getBlockByIndex: (index: number): BlockAPIInterface => this.getBlockByIndex(index),
getCurrentBlockIndex: (): number => this.getCurrentBlockIndex(),
getBlocksCount: (): number => this.getBlocksCount(),
stretchBlock: (index: number, status = true): void => this.stretchBlock(index, status),
@ -50,19 +51,15 @@ export default class BlocksAPI extends Module {
}
/**
* Returns BlockAPI object by Block index
* Returns Block holder by Block index
*
* @param {number} index - index to get
*
* @returns {HTMLElement}
*/
public getBlockByIndex(index: number): BlockAPIInterface | void {
public getBlockByIndex(index: number): BlockAPIInterface {
const block = this.Editor.BlockManager.getBlockByIndex(index);
if (block === undefined) {
_.log('There is no block at index `' + index + '`', 'warn');
return;
}
return new BlockAPI(block);
}
@ -121,7 +118,7 @@ export default class BlocksAPI extends Module {
/**
* in case of last block deletion
* Insert the new default empty block
* Insert new initial empty block
*/
if (this.Editor.BlockManager.blocks.length === 0) {
this.Editor.BlockManager.insert();
@ -130,9 +127,7 @@ export default class BlocksAPI extends Module {
/**
* After Block deletion currentBlock is updated
*/
if (this.Editor.BlockManager.currentBlock) {
this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock, this.Editor.Caret.positions.END);
}
this.Editor.Caret.setToBlock(this.Editor.BlockManager.currentBlock, this.Editor.Caret.positions.END);
this.Editor.Toolbar.close();
}
@ -177,10 +172,10 @@ export default class BlocksAPI extends Module {
* @deprecated Use BlockAPI interface to stretch Blocks
*/
public stretchBlock(index: number, status = true): void {
_.deprecationAssert(
true,
'blocks.stretchBlock()',
'BlockAPI'
_.log(
'`blocks.stretchBlock()` method is deprecated and will be removed in the next major release. ' +
'Use BlockAPI interface instead',
'warn'
);
const block = this.Editor.BlockManager.getBlockByIndex(index);
@ -202,7 +197,7 @@ export default class BlocksAPI extends Module {
* @param {boolean?} needToFocus - flag to focus inserted Block
*/
public insert = (
type: string = this.config.defaultBlock,
type: string = this.config.initialBlock,
data: BlockToolData = {},
config: ToolConfig = {},
index?: number,

View file

@ -1,5 +1,5 @@
import { Caret } from '../../../../types/api';
import Module from '../../__module';
import { Caret } from '../../../../types/api';
/**
* @class CaretAPI

View file

@ -1,5 +1,5 @@
import { Events } from '../../../../types/api';
import Module from '../../__module';
import { Events } from '../../../../types/api';
/**
* @class EventsAPI

View file

@ -1,8 +1,8 @@
import Module from '../../__module';
import { I18n } from '../../../../types/api';
import I18nInternal from '../../i18n';
import { ToolType } from '../tools';
import { logLabeled } from '../../utils';
import Module from '../../__module';
/**
* Provides methods for working with i18n

View file

@ -31,8 +31,7 @@ export default class API extends Module {
inlineToolbar: this.Editor.InlineToolbarAPI.methods,
tooltip: this.Editor.TooltipAPI.methods,
i18n: this.Editor.I18nAPI.methods,
readOnly: this.Editor.ReadOnlyAPI.methods,
};
} as APIInterfaces;
}
/**

View file

@ -1,5 +1,5 @@
import { InlineToolbar } from '../../../../types/api/inline-toolbar';
import Module from '../../__module';
import { InlineToolbar } from '../../../../types/api/inline-toolbar';
/**
* @class InlineToolbarAPI

View file

@ -1,5 +1,5 @@
import { Listeners } from '../../../../types/api';
import Module from '../../__module';
import { Listeners } from '../../../../types/api';
/**
* @class ListenersAPI

View file

@ -1,6 +1,6 @@
import Module from '../../__module';
import { Notifier } from '../../../../types/api';
import { ConfirmNotifierOptions, NotifierOptions, PromptNotifierOptions } from 'codex-notifier';
import Module from '../../__module';
/**
*

View file

@ -1,28 +0,0 @@
import { ReadOnly } from '../../../../types/api';
import Module from '../../__module';
/**
* @class ReadOnlyAPI
* @classdesc ReadOnly API
*/
export default class ReadOnlyAPI extends Module {
/**
* Available methods
*/
public get methods(): ReadOnly {
return {
toggle: (state): Promise<boolean> => this.toggle(state),
};
}
/**
* Set or toggle read-only state
*
* @param {boolean|undefined} state - set or toggle state
*
* @returns {boolean} current value
*/
public toggle(state?: boolean): Promise<boolean> {
return this.Editor.ReadOnly.toggle(state);
}
}

View file

@ -1,6 +1,6 @@
import Module from '../../__module';
import { Sanitizer } from '../../../../types/api';
import { SanitizerConfig } from '../../../../types/configs';
import Module from '../../__module';
/**
* @class SanitizerAPI

View file

@ -1,7 +1,6 @@
import Module from '../../__module';
import { Saver } from '../../../../types/api';
import { OutputData } from '../../../../types';
import * as _ from '../../utils';
import Module from '../../__module';
/**
* @class SaverAPI
@ -21,18 +20,8 @@ export default class SaverAPI extends Module {
/**
* Return Editor's data
*
* @returns {OutputData}
*/
public save(): Promise<OutputData> {
const errorText = 'Editor\'s content can not be saved in read-only mode';
if (this.Editor.ReadOnly.isEnabled) {
_.logLabeled(errorText, 'warn');
return Promise.reject(new Error(errorText));
}
return this.Editor.Saver.save();
}
}

View file

@ -1,6 +1,6 @@
import Module from '../../__module';
import SelectionUtils from '../../selection';
import { Selection as SelectionAPIInterface } from '../../../../types/api';
import Module from '../../__module';
/**
* @class SelectionAPI

View file

@ -1,5 +1,5 @@
import { Styles } from '../../../../types/api';
import Module from '../../__module';
import { Styles } from '../../../../types/api';
/**
*

View file

@ -1,5 +1,5 @@
import { Toolbar } from '../../../../types/api';
import Module from '../../__module';
import { Toolbar } from '../../../../types/api';
/**
* @class ToolbarAPI

View file

@ -1,6 +1,6 @@
import Module from '../../__module';
import { Tooltip } from '../../../../types/api';
import { TooltipContent, TooltipOptions } from 'codex-tooltip';
import Module from '../../__module';
/**
* @class TooltipAPI

View file

@ -93,7 +93,7 @@ export default class BlockEvents extends Module {
*
* @param {KeyboardEvent} event - keyup event
*/
public keyup(event: KeyboardEvent): void {
public keyup(event): void {
/**
* If shift key was pressed some special shortcut is used (eg. cross block selection via shift + arrows)
*/
@ -107,6 +107,21 @@ export default class BlockEvents extends Module {
this.Editor.UI.checkEmptiness();
}
/**
* Set up mouse selection handlers
*
* @param {MouseEvent} event - mouse down event
*/
public mouseDown(event: MouseEvent): void {
/**
* Each mouse down on Block must disable selectAll state
*/
if (!SelectionUtils.isCollapsed) {
this.Editor.BlockSelection.clearSelection(event);
}
this.Editor.CrossBlockSelection.watchSelection(event);
}
/**
* Open Toolbox to leaf Tools
*
@ -125,12 +140,12 @@ export default class BlockEvents extends Module {
return;
}
const canOpenToolbox = Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty;
const canOpenToolbox = Tools.isInitial(currentBlock.tool) && currentBlock.isEmpty;
const conversionToolbarOpened = !currentBlock.isEmpty && ConversionToolbar.opened;
const inlineToolbarOpened = !currentBlock.isEmpty && !SelectionUtils.isCollapsed && InlineToolbar.opened;
/**
* For empty Blocks we show Plus button via Toolbox only for default Blocks
* For empty Blocks we show Plus button via Toolbox only for initial Blocks
*/
if (canOpenToolbox) {
this.activateToolbox();
@ -142,10 +157,10 @@ export default class BlockEvents extends Module {
/**
* Add drop target styles
*
* @param {DragEvent} event - drag over event
* @param {DragEvent} e - drag over event
*/
public dragOver(event: DragEvent): void {
const block = this.Editor.BlockManager.getBlockByChildNode(event.target as Node);
public dragOver(e: DragEvent): void {
const block = this.Editor.BlockManager.getBlockByChildNode(e.target as Node);
block.dropTarget = true;
}
@ -153,10 +168,10 @@ export default class BlockEvents extends Module {
/**
* Remove drop target style
*
* @param {DragEvent} event - drag leave event
* @param {DragEvent} e - drag leave event
*/
public dragLeave(event: DragEvent): void {
const block = this.Editor.BlockManager.getBlockByChildNode(event.target as Node);
public dragLeave(e: DragEvent): void {
const block = this.Editor.BlockManager.getBlockByChildNode(e.target as Node);
block.dropTarget = false;
}
@ -194,7 +209,7 @@ export default class BlockEvents extends Module {
const selectionPositionIndex = BlockManager.removeSelectedBlocks();
Caret.setToBlock(BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
/** Clear selection */
BlockSelection.clearSelection(event);
@ -239,7 +254,7 @@ export default class BlockEvents extends Module {
* If enter has been pressed at the start of the text, just insert paragraph Block above
*/
if (this.Editor.Caret.isAtStart && !this.Editor.BlockManager.currentBlock.hasMedia) {
this.Editor.BlockManager.insertDefaultBlockAtIndex(this.Editor.BlockManager.currentBlockIndex);
this.Editor.BlockManager.insertInitialBlockAtIndex(this.Editor.BlockManager.currentBlockIndex);
} else {
/**
* Split the Current Block into two blocks
@ -253,7 +268,7 @@ export default class BlockEvents extends Module {
/**
* If new Block is empty
*/
if (this.Editor.Tools.isDefault(newCurrent.tool) && newCurrent.isEmpty) {
if (this.Editor.Tools.isInitial(newCurrent.tool) && newCurrent.isEmpty) {
/**
* Show Toolbar
*/
@ -411,10 +426,7 @@ export default class BlockEvents extends Module {
return;
}
const navigateNext = event.keyCode === _.keyCodes.DOWN || (event.keyCode === _.keyCodes.RIGHT && !this.isRtl);
const isNavigated = navigateNext ? this.Editor.Caret.navigateNext() : this.Editor.Caret.navigatePrevious();
if (isNavigated) {
if (this.Editor.Caret.navigateNext()) {
/**
* Default behaviour moves cursor by 1 character, we need to prevent it
*/
@ -469,10 +481,7 @@ export default class BlockEvents extends Module {
return;
}
const navigatePrevious = event.keyCode === _.keyCodes.UP || (event.keyCode === _.keyCodes.LEFT && !this.isRtl);
const isNavigated = navigatePrevious ? this.Editor.Caret.navigatePrevious() : this.Editor.Caret.navigateNext();
if (isNavigated) {
if (this.Editor.Caret.navigatePrevious()) {
/**
* Default behaviour moves cursor by 1 character, we need to prevent it
*/

View file

@ -160,9 +160,12 @@ export default class BlockManager extends Module {
/**
* Should be called after Editor.UI preparation
* Define this._blocks property
*
* @returns {Promise}
*/
public prepare(): void {
public async prepare(): Promise<void> {
const blocks = new Blocks(this.Editor.UI.nodes.redactor);
const { BlockEvents, Listeners } = this.Editor;
/**
* We need to use Proxy to overload set/get [] operator.
@ -184,30 +187,18 @@ export default class BlockManager extends Module {
});
/** Copy event */
this.Editor.Listeners.on(
Listeners.on(
document,
'copy',
(e: ClipboardEvent) => this.Editor.BlockEvents.handleCommandC(e)
(e: ClipboardEvent) => BlockEvents.handleCommandC(e)
);
}
/**
* Toggle read-only state
*
* If readOnly is true:
* - Unbind event handlers from created Blocks
*
* if readOnly is false:
* - Bind event handlers to all existing Blocks
*
* @param {boolean} readOnlyEnabled - "read only" state
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (!readOnlyEnabled) {
this.enableModuleBindings();
} else {
this.disableModuleBindings();
}
/** Copy and cut */
Listeners.on(
document,
'cut',
(e: ClipboardEvent) => BlockEvents.handleCommandX(e)
);
}
/**
@ -220,7 +211,6 @@ export default class BlockManager extends Module {
* @returns {Block}
*/
public composeBlock({ tool, data = {} }: {tool: string; data?: BlockToolData}): Block {
const readOnly = this.Editor.ReadOnly.isEnabled;
const settings = this.Editor.Tools.getToolSettings(tool);
const Tool = this.Editor.Tools.available[tool] as BlockToolConstructable;
const block = new Block({
@ -229,12 +219,9 @@ export default class BlockManager extends Module {
Tool,
settings,
api: this.Editor.API,
readOnly,
});
if (!readOnly) {
this.bindBlockEvents(block);
}
this.bindEvents(block);
return block;
}
@ -243,7 +230,7 @@ export default class BlockManager extends Module {
* Insert new block into _blocks
*
* @param {object} options - insert options
* @param {string} options.tool - plugin name, by default method inserts the default block type
* @param {string} options.tool - plugin name, by default method inserts initial block type
* @param {object} options.data - plugin data
* @param {number} options.index - index where to insert new Block
* @param {boolean} options.needToFocus - flag shows if needed to update current Block index
@ -252,7 +239,7 @@ export default class BlockManager extends Module {
* @returns {Block}
*/
public insert({
tool = this.config.defaultBlock,
tool = this.config.initialBlock,
data = {},
index,
needToFocus = true,
@ -296,7 +283,7 @@ export default class BlockManager extends Module {
* @returns {Block}
*/
public replace({
tool = this.config.defaultBlock,
tool = this.config.initialBlock,
data = {},
}): Block {
return this.insert({
@ -334,7 +321,7 @@ export default class BlockManager extends Module {
}
/**
* Insert new default block at passed index
* Insert new initial block at passed index
*
* @param {number} index - index where Block should be inserted
* @param {boolean} needToFocus - if true, updates current Block index
@ -343,8 +330,8 @@ export default class BlockManager extends Module {
*
* @returns {Block} inserted Block
*/
public insertDefaultBlockAtIndex(index: number, needToFocus = false): Block {
const block = this.composeBlock({ tool: this.config.defaultBlock });
public insertInitialBlockAtIndex(index: number, needToFocus = false): Block {
const block = this.composeBlock({ tool: this.config.initialBlock });
this._blocks[index] = block;
@ -369,7 +356,7 @@ export default class BlockManager extends Module {
this.currentBlockIndex = this.blocks.length - 1;
/**
* Insert the default typed block
* Insert initial typed block
*/
return this.insert();
}
@ -456,7 +443,7 @@ export default class BlockManager extends Module {
/**
* Attention!
* After removing insert the new default typed Block and focus on it
* After removing insert new initial typed Block and focus on it
* Removes all blocks
*/
public removeAllBlocks(): void {
@ -581,11 +568,6 @@ export default class BlockManager extends Module {
*/
this.currentBlockIndex = this._blocks.nodes.indexOf(parentFirstLevelBlock as HTMLElement);
/**
* Update current block active input
*/
this.currentBlock.updateCurrentInput();
return this.currentBlock;
} else {
throw new Error('Can not find a Block from this child Node');
@ -667,15 +649,15 @@ export default class BlockManager extends Module {
/**
* Clears Editor
*
* @param {boolean} needToAddDefaultBlock - 1) in internal calls (for example, in api.blocks.render)
* we don't need to add an empty default block
* @param {boolean} needAddInitialBlock - 1) in internal calls (for example, in api.blocks.render)
* we don't need to add empty initial block
* 2) in api.blocks.clear we should add empty block
*/
public clear(needToAddDefaultBlock = false): void {
public clear(needAddInitialBlock = false): void {
this._blocks.removeAll();
this.dropPointer();
if (needToAddDefaultBlock) {
if (needAddInitialBlock) {
this.insert();
}
@ -686,63 +668,18 @@ export default class BlockManager extends Module {
}
/**
* Cleans up all the block tools' resources
* This is called when editor is destroyed
*/
public async destroy(): Promise<void> {
await Promise.all(this.blocks.map((block) => {
if (_.isFunction(block.tool.destroy)) {
return block.tool.destroy();
}
}));
}
/**
* Bind Block events
* Bind Events
*
* @param {Block} block - Block to which event should be bound
*/
private bindBlockEvents(block: Block): void {
const { BlockEvents } = this.Editor;
private bindEvents(block: Block): void {
const { BlockEvents, Listeners } = this.Editor;
this.readOnlyMutableListeners.on(block.holder, 'keydown', (event: KeyboardEvent) => {
BlockEvents.keydown(event);
}, true);
this.readOnlyMutableListeners.on(block.holder, 'keyup', (event: KeyboardEvent) => {
BlockEvents.keyup(event);
});
this.readOnlyMutableListeners.on(block.holder, 'dragover', (event: DragEvent) => {
BlockEvents.dragOver(event);
});
this.readOnlyMutableListeners.on(block.holder, 'dragleave', (event: DragEvent) => {
BlockEvents.dragLeave(event);
});
}
/**
* Disable mutable handlers and bindings
*/
private disableModuleBindings(): void {
this.readOnlyMutableListeners.clearAll();
}
/**
* Enables all module handlers and bindings for all Blocks
*/
private enableModuleBindings(): void {
/** Cut event */
this.readOnlyMutableListeners.on(
document,
'cut',
(e: ClipboardEvent) => this.Editor.BlockEvents.handleCommandX(e)
);
this.blocks.forEach((block: Block) => {
this.bindBlockEvents(block);
});
Listeners.on(block.holder, 'keydown', (event) => BlockEvents.keydown(event as KeyboardEvent), false);
Listeners.on(block.holder, 'mousedown', (event: MouseEvent) => BlockEvents.mouseDown(event));
Listeners.on(block.holder, 'keyup', (event) => BlockEvents.keyup(event));
Listeners.on(block.holder, 'dragover', (event) => BlockEvents.dragOver(event as DragEvent));
Listeners.on(block.holder, 'dragleave', (event) => BlockEvents.dragLeave(event as DragEvent));
}
/**

View file

@ -17,15 +17,6 @@ import { SanitizerConfig } from '../../../types/configs';
*
*/
export default class BlockSelection extends Module {
/**
* Sometimes .anyBlockSelected can be called frequently,
* for example at ui@selectionChange (to clear native browser selection in CBS)
* We use cache to prevent multiple iterations through all the blocks
*
* @private
*/
private anyBlockSelectedCache: boolean | null = null;
/**
* Sanitizer Config
*
@ -80,8 +71,6 @@ export default class BlockSelection extends Module {
BlockManager.blocks.forEach((block) => {
block.selected = state;
});
this.clearCache();
}
/**
@ -92,11 +81,7 @@ export default class BlockSelection extends Module {
public get anyBlockSelected(): boolean {
const { BlockManager } = this.Editor;
if (this.anyBlockSelectedCache === null) {
this.anyBlockSelectedCache = BlockManager.blocks.some((block) => block.selected === true);
}
return this.anyBlockSelectedCache;
return BlockManager.blocks.some((block) => block.selected === true);
}
/**
@ -147,30 +132,15 @@ export default class BlockSelection extends Module {
public prepare(): void {
const { Shortcuts } = this.Editor;
this.selection = new SelectionUtils();
/**
* CMD/CTRL+A selection shortcut
*/
/** Selection shortcut */
Shortcuts.add({
name: 'CMD+A',
handler: (event) => {
const { BlockManager, ReadOnly } = this.Editor;
/**
* We use Editor's Block selection on CMD+A ShortCut instead of Browsers
*/
if (ReadOnly.isEnabled) {
event.preventDefault();
this.selectAllBlocks();
return;
}
const { BlockManager } = this.Editor;
/**
* When one page consist of two or more EditorJS instances
* Shortcut module tries to handle all events.
* Thats why Editor's selection works inside the target Editor, but
* Shortcut module tries to handle all events. Thats why Editor's selection works inside the target Editor, but
* for others error occurs because nothing to select.
*
* Prevent such actions if focus is not inside the Editor
@ -182,21 +152,8 @@ export default class BlockSelection extends Module {
this.handleCommandA(event);
},
});
}
/**
* Toggle read-only state
*
* - Remove all ranges
* - Unselect all Blocks
*
* @param {boolean} readOnlyEnabled - "read only" state
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
SelectionUtils.get()
.removeAllRanges();
this.allBlocksSelected = false;
this.selection = new SelectionUtils();
}
/**
@ -216,8 +173,6 @@ export default class BlockSelection extends Module {
}
block.selected = false;
this.clearCache();
}
/**
@ -243,18 +198,10 @@ export default class BlockSelection extends Module {
if (this.anyBlockSelected && isKeyboard && isPrintableKey && !SelectionUtils.isSelectionExists) {
const indexToInsert = BlockManager.removeSelectedBlocks();
BlockManager.insertDefaultBlockAtIndex(indexToInsert, true);
BlockManager.insertInitialBlockAtIndex(indexToInsert, true);
Caret.setToBlock(BlockManager.currentBlock);
_.delay(() => {
const eventKey = (reason as KeyboardEvent).key;
/**
* If event.key length >1 that means key is special (e.g. Enter or Dead or Unidentifier).
* So we use empty string
*
* @see https://developer.mozilla.org/ru/docs/Web/API/KeyboardEvent/key
*/
Caret.insertContentAtCaretPosition(eventKey.length > 1 ? '' : eventKey);
Caret.insertContentAtCaretPosition((reason as KeyboardEvent).key);
}, 20)();
}
@ -343,19 +290,10 @@ export default class BlockSelection extends Module {
block.selected = true;
this.clearCache();
/** close InlineToolbar when we selected any Block */
this.Editor.InlineToolbar.close();
}
/**
* Clear anyBlockSelected cache
*/
public clearCache(): void {
this.anyBlockSelectedCache = null;
}
/**
* Module destruction
* De-registers Shortcut CMD+A

View file

@ -330,10 +330,10 @@ export default class Caret extends Module {
}
/**
* If last block is empty and it is an defaultBlock, set to that.
* If last block is empty and it is an initialBlock, set to that.
* Otherwise, append new empty block and set to that
*/
if (this.Editor.Tools.isDefault(lastBlock.tool) && lastBlock.isEmpty) {
if (this.Editor.Tools.isInitial(lastBlock.tool) && lastBlock.isEmpty) {
this.setToBlock(lastBlock);
} else {
const newBlock = this.Editor.BlockManager.insertAtEnd();
@ -355,30 +355,12 @@ export default class Caret extends Module {
selectRange.deleteContents();
if (currentBlockInput) {
if ($.isNativeInput(currentBlockInput)) {
/**
* If input is native text input we need to use it's value
* Text before the caret stays in the input,
* while text after the caret is returned as a fragment to be inserted after the block.
*/
const input = currentBlockInput as HTMLInputElement | HTMLTextAreaElement;
const newFragment = document.createDocumentFragment();
const range = selectRange.cloneRange();
const inputRemainingText = input.value.substring(0, input.selectionStart);
const fragmentText = input.value.substring(input.selectionStart);
range.selectNodeContents(currentBlockInput);
range.setStart(selectRange.endContainer, selectRange.endOffset);
newFragment.textContent = fragmentText;
input.value = inputRemainingText;
return newFragment;
} else {
const range = selectRange.cloneRange();
range.selectNodeContents(currentBlockInput);
range.setStart(selectRange.endContainer, selectRange.endOffset);
return range.extractContents();
}
return range.extractContents();
}
}
}
@ -401,15 +383,15 @@ export default class Caret extends Module {
if (!nextBlock && !nextInput) {
/**
* If there is no nextBlock and currentBlock is default, do not navigate
* If there is no nextBlock and currentBlock is initial, do not navigate
*/
if (Tools.isDefault(currentBlock.tool)) {
if (Tools.isInitial(currentBlock.tool)) {
return false;
}
/**
* If there is no nextBlock, but currentBlock is not default,
* insert new default block at the end and navigate to it
* If there is no nextBlock, but currentBlock is not initial,
* insert new initial block at the end and navigate to it
*/
nextBlock = BlockManager.insertAtEnd();
}
@ -523,13 +505,6 @@ export default class Caret extends Module {
Array.from(wrapper.childNodes).forEach((child: Node) => fragment.appendChild(child));
/**
* If there is no child node, append empty one
*/
if (fragment.childNodes.length === 0) {
fragment.appendChild(new Text(''));
}
const lastChild = fragment.lastChild;
range.deleteContents();

View file

@ -17,19 +17,6 @@ export default class CrossBlockSelection extends Module {
*/
private lastSelectedBlock: Block;
/**
* Module preparation
*
* @returns {Promise}
*/
public async prepare(): Promise<void> {
const { Listeners } = this.Editor;
Listeners.on(document, 'mousedown', (event: MouseEvent) => {
this.enableCrossBlockSelection(event);
});
}
/**
* Sets up listeners
*
@ -64,7 +51,7 @@ export default class CrossBlockSelection extends Module {
* @param {boolean} next - if true, toggle next block. Previous otherwise
*/
public toggleBlockSelectedState(next = true): void {
const { BlockManager, BlockSelection } = this.Editor;
const { BlockManager } = this.Editor;
if (!this.lastSelectedBlock) {
this.lastSelectedBlock = this.firstSelectedBlock = BlockManager.currentBlock;
@ -72,8 +59,6 @@ export default class CrossBlockSelection extends Module {
if (this.firstSelectedBlock === this.lastSelectedBlock) {
this.firstSelectedBlock.selected = true;
BlockSelection.clearCache();
SelectionUtils.get().removeAllRanges();
}
@ -86,12 +71,8 @@ export default class CrossBlockSelection extends Module {
if (this.lastSelectedBlock.selected !== nextBlock.selected) {
nextBlock.selected = true;
BlockSelection.clearCache();
} else {
this.lastSelectedBlock.selected = false;
BlockSelection.clearCache();
}
this.lastSelectedBlock = nextBlock;
@ -139,34 +120,6 @@ export default class CrossBlockSelection extends Module {
this.firstSelectedBlock = this.lastSelectedBlock = null;
}
/**
* Enables Cross Block Selection
*
* @param {MouseEvent} event - mouse down event
*/
private enableCrossBlockSelection(event: MouseEvent): void {
const { UI } = this.Editor;
/**
* Each mouse down on must disable selectAll state
*/
if (!SelectionUtils.isCollapsed) {
this.Editor.BlockSelection.clearSelection(event);
}
/**
* If mouse down is performed inside the editor, we should watch CBS
*/
if (UI.nodes.redactor.contains(event.target as Node)) {
this.watchSelection(event);
} else {
/**
* Otherwise, clear selection
*/
this.Editor.BlockSelection.clearSelection(event);
}
}
/**
* Mouse up event handler.
* Removes the listeners
@ -185,7 +138,7 @@ export default class CrossBlockSelection extends Module {
* @param {MouseEvent} event - mouse over event
*/
private onMouseOver = (event: MouseEvent): void => {
const { BlockManager, BlockSelection } = this.Editor;
const { BlockManager } = this.Editor;
const relatedBlock = BlockManager.getBlockByChildNode(event.relatedTarget as Node) || this.lastSelectedBlock;
const targetBlock = BlockManager.getBlockByChildNode(event.target as Node);
@ -204,8 +157,6 @@ export default class CrossBlockSelection extends Module {
relatedBlock.selected = true;
targetBlock.selected = true;
BlockSelection.clearCache();
return;
}
@ -213,8 +164,6 @@ export default class CrossBlockSelection extends Module {
relatedBlock.selected = false;
targetBlock.selected = false;
BlockSelection.clearCache();
return;
}
@ -231,7 +180,7 @@ export default class CrossBlockSelection extends Module {
* @param {Block} lastBlock - last block in range
*/
private toggleBlocksSelectedState(firstBlock: Block, lastBlock: Block): void {
const { BlockManager, BlockSelection } = this.Editor;
const { BlockManager } = this.Editor;
const fIndex = BlockManager.blocks.indexOf(firstBlock);
const lIndex = BlockManager.blocks.indexOf(lastBlock);
@ -250,8 +199,6 @@ export default class CrossBlockSelection extends Module {
block !== (shouldntSelectFirstBlock ? firstBlock : lastBlock)
) {
BlockManager.blocks[i].selected = !BlockManager.blocks[i].selected;
BlockSelection.clearCache();
}
}
}

View file

@ -14,60 +14,30 @@ export default class DragNDrop extends Module {
private isStartedAtEditor = false;
/**
* Bind module. Enable all bindings if it is not read-only mode
* Bind events
*/
public prepare(): void {
if (!this.Editor.ReadOnly.isEnabled) {
this.enableModuleBindings();
}
}
/**
* Toggle read-only state
*
* if state is true:
* - disable all drag-n-drop event handlers
*
* if state is false:
* - restore drag-n-drop event handlers
*
* @param {boolean} readOnlyEnabled - "read only" state
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (readOnlyEnabled) {
this.disableModuleBindings();
} else {
this.enableModuleBindings();
}
this.bindEvents();
}
/**
* Add drag events listeners to editor zone
*
* @private
*/
private enableModuleBindings(): void {
const { UI } = this.Editor;
private bindEvents(): void {
this.Editor.Listeners.on(this.Editor.UI.nodes.holder, 'drop', this.processDrop, true);
this.readOnlyMutableListeners.on(UI.nodes.holder, 'drop', async (dropEvent: DragEvent) => {
await this.processDrop(dropEvent);
}, true);
this.Editor.Listeners.on(this.Editor.UI.nodes.holder, 'dragstart', (dragEvent: DragEvent) => {
if (SelectionUtils.isAtEditor && !SelectionUtils.isCollapsed) {
this.isStartedAtEditor = true;
}
this.readOnlyMutableListeners.on(UI.nodes.holder, 'dragstart', () => {
this.processDragStart();
this.Editor.InlineToolbar.close();
});
/**
* Prevent default browser behavior to allow drop on non-contenteditable elements
*/
this.readOnlyMutableListeners.on(UI.nodes.holder, 'dragover', (dragEvent: DragEvent) => {
this.processDragOver(dragEvent);
}, true);
}
/**
* Unbind drag-n-drop event handlers
*/
private disableModuleBindings(): void {
this.readOnlyMutableListeners.clearAll();
/* Prevent default browser behavior to allow drop on non-contenteditable elements */
this.Editor.Listeners.on(this.Editor.UI.nodes.holder, 'dragover', (e) => e.preventDefault(), true);
}
/**
@ -75,7 +45,7 @@ export default class DragNDrop extends Module {
*
* @param {DragEvent} dropEvent - drop event
*/
private async processDrop(dropEvent: DragEvent): Promise<void> {
private processDrop = async (dropEvent: DragEvent): Promise<void> => {
const {
BlockManager,
Caret,
@ -108,24 +78,6 @@ export default class DragNDrop extends Module {
this.Editor.Caret.setToBlock(targetBlock, Caret.positions.END);
}
await Paste.processDataTransfer(dropEvent.dataTransfer, true);
}
/**
* Handle drag start event
*/
private processDragStart(): void {
if (SelectionUtils.isAtEditor && !SelectionUtils.isCollapsed) {
this.isStartedAtEditor = true;
}
this.Editor.InlineToolbar.close();
}
/**
* @param {DragEvent} dragEvent - drag event
*/
private processDragOver(dragEvent: DragEvent): void {
dragEvent.preventDefault();
Paste.processDataTransfer(dropEvent.dataTransfer, true);
}
}

View file

@ -1,5 +1,4 @@
import Module from '../__module';
import * as _ from '../utils';
/**
* Event listener information
@ -7,11 +6,6 @@ import * as _ from '../utils';
* @interface ListenerData
*/
export interface ListenerData {
/**
* Listener unique identifier
*/
id: string;
/**
* Element where to listen to dispatched events
*/
@ -59,24 +53,20 @@ export default class Listeners extends Module {
private allListeners: ListenerData[] = [];
/**
* Assigns event listener on element and returns unique identifier
* Assigns event listener on element
*
* @param {EventTarget} element - DOM element that needs to be listened
* @param {string} eventType - event type
* @param {Function} handler - method that will be fired on event
* @param {boolean|AddEventListenerOptions} options - useCapture or {capture, passive, once}
*
* @returns {string}
*/
public on(
element: EventTarget,
eventType: string,
handler: (event: Event) => void,
options: boolean | AddEventListenerOptions = false
): string {
const id = _.generateId('l');
): void {
const assignedEventData = {
id,
element,
eventType,
handler,
@ -91,8 +81,6 @@ export default class Listeners extends Module {
this.allListeners.push(assignedEventData);
element.addEventListener(eventType, handler, options);
return id;
}
/**
@ -122,21 +110,6 @@ export default class Listeners extends Module {
});
}
/**
* Removes listener by id
*
* @param {string} id - listener identifier
*/
public offById(id: string): void {
const listener = this.findById(id);
if (!listener) {
return;
}
listener.element.removeEventListener(listener.eventType, listener.handler, listener.options);
}
/**
* Finds and returns first listener by passed params
*
@ -238,15 +211,4 @@ export default class Listeners extends Module {
}
});
}
/**
* Returns listener data found by id
*
* @param {string} id - listener identifier
*
* @returns {ListenerData}
*/
private findById(id: string): ListenerData {
return this.allListeners.find((listener) => listener.id === id);
}
}

View file

@ -28,7 +28,7 @@ export default class ModificationsObserver extends Module {
/**
* Allows to temporary disable mutations handling
*/
private disabled = false;
private disabled: boolean;
/**
* Used to prevent several mutation callback execution
@ -37,10 +37,7 @@ export default class ModificationsObserver extends Module {
*/
private mutationDebouncer = _.debounce(() => {
this.updateNativeInputs();
if (typeof this.config.onChange === 'function') {
this.config.onChange(this.Editor.API.methods);
}
this.config.onChange(this.Editor.API.methods);
}, ModificationsObserver.DebounceTimer);
/**
@ -59,7 +56,6 @@ export default class ModificationsObserver extends Module {
}
this.observer = null;
this.nativeInputs.forEach((input) => this.Editor.Listeners.off(input, 'input', this.mutationDebouncer));
this.mutationDebouncer = null;
}
/**
@ -68,22 +64,12 @@ export default class ModificationsObserver extends Module {
* @returns {Promise<void>}
*/
public async prepare(): Promise<void> {
if (!this.Editor.ReadOnly.isEnabled) {
this.enableModule();
}
}
/**
* Set read-only state
*
* @param {boolean} readOnlyEnabled - read only flag value
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (readOnlyEnabled) {
this.disableModule();
} else {
this.enableModule();
}
/**
* wait till Browser render Editor's Blocks
*/
window.setTimeout(() => {
this.setObserver();
}, 1000);
}
/**
@ -130,7 +116,7 @@ export default class ModificationsObserver extends Module {
* @param {MutationRecord[]} mutationList - list of mutations
* @param {MutationObserver} observer - observer instance
*/
private mutationHandler(mutationList: MutationRecord[], observer: MutationObserver): void {
private mutationHandler(mutationList: MutationRecord[], observer): void {
/**
* Skip mutations in stealth mode
*/
@ -182,25 +168,4 @@ export default class ModificationsObserver extends Module {
this.nativeInputs.forEach((input) => this.Editor.Listeners.on(input, 'input', this.mutationDebouncer));
}
/**
* Sets observer and enables it
*/
private enableModule(): void {
/**
* wait till Browser render Editor's Blocks
*/
window.setTimeout(() => {
this.setObserver();
this.updateNativeInputs();
this.enable();
}, 1000);
}
/**
* Disables observer
*/
private disableModule(): void {
this.disable();
}
}

View file

@ -9,7 +9,7 @@ import {
PasteEventDetail
} from '../../../types';
import Block from '../block';
import { SavedData } from '../../../types/data-formats';
import { SavedData } from '../../types-internal/block-data';
/**
* Tag substitute object.
@ -141,25 +141,12 @@ export default class Paste extends Module {
/**
* Set onPaste callback and collect tools` paste configurations
*
* @public
*/
public async prepare(): Promise<void> {
this.setCallback();
this.processTools();
if (!this.Editor.ReadOnly.isEnabled) {
this.setCallback();
}
}
/**
* Set read-only state
*
* @param {boolean} readOnlyEnabled - read only flag value
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (!readOnlyEnabled) {
this.setCallback();
} else {
this.unsetCallback();
}
}
/**
@ -250,8 +237,8 @@ export default class Paste extends Module {
return;
}
const isCurrentBlockDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool);
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
const isCurrentBlockInitial = BlockManager.currentBlock && Tools.isInitial(BlockManager.currentBlock.tool);
const needToReplaceCurrentBlock = isCurrentBlockInitial && BlockManager.currentBlock.isEmpty;
dataToInsert.map(
async (content, i) => this.insertBlock(content, i === 0 && needToReplaceCurrentBlock)
@ -271,15 +258,6 @@ export default class Paste extends Module {
Listeners.on(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
}
/**
* Unset onPaste callback handler
*/
private unsetCallback(): void {
const { Listeners } = this.Editor;
Listeners.off(this.Editor.UI.nodes.holder, 'paste', this.handlePasteEvent);
}
/**
* Get and process tool`s paste configs
*/
@ -298,7 +276,6 @@ export default class Paste extends Module {
api: this.Editor.API.getMethodsForTool(name),
config: {},
data: {},
readOnly: false,
}) as BlockTool;
if (tool.pasteConfig === false) {
@ -450,7 +427,7 @@ export default class Paste extends Module {
}
/**
* If Tools is in list of errors, skip processing of paste event
* If Tools is in list of exceptions, skip processing of paste event
*/
if (BlockManager.currentBlock && this.exceptionList.includes(BlockManager.currentBlock.name)) {
return;
@ -480,8 +457,8 @@ export default class Paste extends Module {
);
dataToInsert = dataToInsert.filter((data) => !!data);
const isCurrentBlockDefault = Tools.isDefault(BlockManager.currentBlock.tool);
const needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
const isCurrentBlockInitial = Tools.isInitial(BlockManager.currentBlock.tool);
const needToReplaceCurrentBlock = isCurrentBlockInitial && BlockManager.currentBlock.isEmpty;
dataToInsert.forEach(
(data, i) => {
@ -537,7 +514,7 @@ export default class Paste extends Module {
*/
private processHTML(innerHTML: string): PasteData[] {
const { Tools, Sanitizer } = this.Editor;
const initialTool = this.config.defaultBlock;
const initialTool = this.config.initialBlock;
const wrapper = $.make('DIV');
wrapper.innerHTML = innerHTML;
@ -599,13 +576,13 @@ export default class Paste extends Module {
* @returns {PasteData[]}
*/
private processPlain(plain: string): PasteData[] {
const { defaultBlock } = this.config as {defaultBlock: string};
const { initialBlock } = this.config as {initialBlock: string};
if (!plain) {
return [];
}
const tool = defaultBlock;
const tool = initialBlock;
return plain
.split(/\r?\n/)
@ -645,7 +622,7 @@ export default class Paste extends Module {
dataToInsert.tool !== currentBlock.name ||
!$.containsOnlyInlineElements(dataToInsert.content.innerHTML)
) {
this.insertBlock(dataToInsert, currentBlock && Tools.isDefault(currentBlock.tool) && currentBlock.isEmpty);
this.insertBlock(dataToInsert, currentBlock && Tools.isInitial(currentBlock.tool) && currentBlock.isEmpty);
return;
}
@ -665,14 +642,14 @@ export default class Paste extends Module {
const { BlockManager, Caret, Sanitizer, Tools } = this.Editor;
const { content } = dataToInsert;
const currentBlockIsDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool);
const currentBlockIsInitial = BlockManager.currentBlock && Tools.isInitial(BlockManager.currentBlock.tool);
if (currentBlockIsDefault && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
if (currentBlockIsInitial && content.textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH) {
const blockData = await this.processPattern(content.textContent);
if (blockData) {
const needToReplaceCurrentBlock = BlockManager.currentBlock &&
Tools.isDefault(BlockManager.currentBlock.tool) &&
Tools.isInitial(BlockManager.currentBlock.tool) &&
BlockManager.currentBlock.isEmpty;
const insertedBlock = BlockManager.paste(blockData.tool, blockData.event, needToReplaceCurrentBlock);
@ -769,9 +746,9 @@ export default class Paste extends Module {
let needToReplaceCurrentBlock = false;
if (i === 0) {
const isCurrentBlockDefault = BlockManager.currentBlock && Tools.isDefault(BlockManager.currentBlock.tool);
const isCurrentBlockInitial = BlockManager.currentBlock && Tools.isInitial(BlockManager.currentBlock.tool);
needToReplaceCurrentBlock = isCurrentBlockDefault && BlockManager.currentBlock.isEmpty;
needToReplaceCurrentBlock = isCurrentBlockInitial && BlockManager.currentBlock.isEmpty;
}
BlockManager.insert({

View file

@ -1,113 +0,0 @@
import Module from '../__module';
import { CriticalError } from '../errors/critical';
/**
* @module ReadOnly
*
* Has one important method:
* - {Function} toggleReadonly - Set read-only mode or toggle current state
*
* @version 1.0.0
*
* @typedef {ReadOnly} ReadOnly
* @property {boolean} readOnlyEnabled - read-only state
*/
export default class ReadOnly extends Module {
/**
* Array of tools name which don't support read-only mode
*/
private toolsDontSupportReadOnly: string[] = [];
/**
* Value to track read-only state
*
* @type {boolean}
*/
private readOnlyEnabled = false;
/**
* Returns state of read only mode
*/
public get isEnabled(): boolean {
return this.readOnlyEnabled;
}
/**
* Set initial state
*/
public async prepare(): Promise<void> {
const { Tools } = this.Editor;
const { blockTools } = Tools;
const toolsDontSupportReadOnly: string[] = [];
Object.entries(blockTools).forEach(([name, tool]) => {
if (!Tools.isReadOnlySupported(tool)) {
toolsDontSupportReadOnly.push(name);
}
});
this.toolsDontSupportReadOnly = toolsDontSupportReadOnly;
if (this.config.readOnly && toolsDontSupportReadOnly.length > 0) {
this.throwCriticalError();
}
this.toggle(this.config.readOnly);
}
/**
* Set read-only mode or toggle current state
* Call all Modules `toggleReadOnly` method and re-render Editor
*
* @param {boolean} state - (optional) read-only state or toggle
*/
public async toggle(state = !this.readOnlyEnabled): Promise<boolean> {
if (state && this.toolsDontSupportReadOnly.length > 0) {
this.throwCriticalError();
}
const oldState = this.readOnlyEnabled;
this.readOnlyEnabled = state;
for (const name in this.Editor) {
/**
* Verify module has method `toggleReadOnly` method
*/
if (!this.Editor[name].toggleReadOnly) {
continue;
}
/**
* set or toggle read-only state
*/
this.Editor[name].toggleReadOnly(state);
}
/**
* If new state equals old one, do not re-render blocks
*/
if (oldState === state) {
return this.readOnlyEnabled;
}
/**
* Save current Editor Blocks and render again
*/
const savedBlocks = await this.Editor.Saver.save();
await this.Editor.BlockManager.clear();
await this.Editor.Renderer.render(savedBlocks.blocks);
return this.readOnlyEnabled;
}
/**
* Throws an error about tools which don't support read-only mode
*/
private throwCriticalError(): never {
throw new CriticalError(
`To enable read-only mode all connected tools should support it. Tools ${this.toolsDontSupportReadOnly.join(', ')} don't support read-only mode.`
);
}
}

View file

@ -96,17 +96,38 @@ export default class RectangleSelection extends Module {
*/
private overlayRectangle: HTMLDivElement;
/**
* Listener identifiers
*/
private listenerIds: string[] = [];
/**
* Module Preparation
* Creating rect and hang handlers
*/
public prepare(): void {
this.enableModuleBindings();
const { Listeners } = this.Editor;
const { container } = this.genHTML();
Listeners.on(container, 'mousedown', (event: MouseEvent) => {
if (event.button !== this.MAIN_MOUSE_BUTTON) {
return;
}
this.startSelection(event.pageX, event.pageY);
}, false);
Listeners.on(document.body, 'mousemove', (event: MouseEvent) => {
this.changingRectangle(event);
this.scrollByZones(event.clientY);
}, false);
Listeners.on(document.body, 'mouseleave', () => {
this.clearSelection();
this.endSelection();
});
Listeners.on(window, 'scroll', (event) => {
this.changingRectangle(event);
}, false);
Listeners.on(document.body, 'mouseup', () => {
this.endSelection();
}, false);
}
/**
@ -175,78 +196,6 @@ export default class RectangleSelection extends Module {
this.isRectSelectionActivated = false;
}
/**
* Sets Module necessary event handlers
*/
private enableModuleBindings(): void {
const { Listeners } = this.Editor;
const { container } = this.genHTML();
Listeners.on(container, 'mousedown', (mouseEvent: MouseEvent) => {
this.processMouseDown(mouseEvent);
}, false);
Listeners.on(document.body, 'mousemove', (mouseEvent: MouseEvent) => {
this.processMouseMove(mouseEvent);
}, false);
Listeners.on(document.body, 'mouseleave', () => {
this.processMouseLeave();
});
Listeners.on(window, 'scroll', (mouseEvent: MouseEvent) => {
this.processScroll(mouseEvent);
}, false);
Listeners.on(document.body, 'mouseup', () => {
this.processMouseUp();
}, false);
}
/**
* Handle mouse down events
*
* @param {MouseEvent} mouseEvent - mouse event payload
*/
private processMouseDown(mouseEvent: MouseEvent): void {
if (mouseEvent.button !== this.MAIN_MOUSE_BUTTON) {
return;
}
this.startSelection(mouseEvent.pageX, mouseEvent.pageY);
}
/**
* Handle mouse move events
*
* @param {MouseEvent} mouseEvent - mouse event payload
*/
private processMouseMove(mouseEvent: MouseEvent): void {
this.changingRectangle(mouseEvent);
this.scrollByZones(mouseEvent.clientY);
}
/**
* Handle mouse leave
*/
private processMouseLeave(): void {
this.clearSelection();
this.endSelection();
}
/**
* @param {MouseEvent} mouseEvent - mouse event payload
*/
private processScroll(mouseEvent: MouseEvent): void {
this.changingRectangle(mouseEvent);
}
/**
* Handle mouse up
*/
private processMouseUp(): void {
this.endSelection();
}
/**
* Scroll If mouse in scroll zone
*
@ -321,7 +270,7 @@ export default class RectangleSelection extends Module {
*
* @param {MouseEvent} event - mouse event
*/
private changingRectangle(event: MouseEvent): void {
private changingRectangle(event): void {
if (!this.mousedown) {
return;
}

View file

@ -1,5 +1,7 @@
import Module from '../__module';
/* eslint-disable import/no-duplicates */
import * as _ from '../utils';
import { ChainData } from '../utils';
import { BlockToolConstructable, OutputBlockData } from '../../../types';
/**
@ -45,7 +47,7 @@ export default class Renderer extends Module {
public async render(blocks: OutputBlockData[]): Promise<void> {
const chainData = blocks.map((block) => ({ function: (): Promise<void> => this.insertBlock(block) }));
const sequence = await _.sequence(chainData as _.ChainData[]);
const sequence = await _.sequence(chainData as ChainData[]);
this.Editor.UI.checkEmptiness();
@ -58,7 +60,6 @@ export default class Renderer extends Module {
* Insert block to working zone
*
* @param {object} item - Block data to insert
*
* @returns {Promise<void>}
*/
public async insertBlock(item: OutputBlockData): Promise<void> {
@ -90,7 +91,7 @@ export default class Renderer extends Module {
const toolToolboxSettings = (Tools.unavailable[tool] as BlockToolConstructable).toolbox;
const userToolboxSettings = Tools.getToolSettings(tool).toolbox;
stubData.title = toolToolboxSettings.title || (userToolboxSettings && userToolboxSettings.title) || stubData.title;
stubData.title = toolToolboxSettings.title || userToolboxSettings.title || stubData.title;
}
const stub = BlockManager.insert({

View file

@ -37,7 +37,7 @@ import * as _ from '../utils';
import HTMLJanitor from 'html-janitor';
import { BlockToolData, InlineToolConstructable, SanitizerConfig } from '../../../types';
import { SavedData } from '../../../types/data-formats';
import { SavedData } from '../../types-internal/block-data';
/**
*

View file

@ -7,7 +7,7 @@
*/
import Module from '../__module';
import { OutputData } from '../../../types';
import { ValidatedData } from '../../../types/data-formats';
import { ValidatedData } from '../../types-internal/block-data';
import Block from '../block';
import * as _ from '../utils';
@ -36,18 +36,16 @@ export default class Saver extends Module {
*/
ModificationsObserver.disable();
try {
blocks.forEach((block: Block) => {
chainData.push(this.getSavedData(block));
});
blocks.forEach((block: Block) => {
chainData.push(this.getSavedData(block));
});
const extractedData = await Promise.all(chainData);
const sanitizedData = await Sanitizer.sanitizeBlocks(extractedData);
const extractedData = await Promise.all(chainData);
const sanitizedData = await Sanitizer.sanitizeBlocks(extractedData);
return this.makeOutput(sanitizedData);
} finally {
ModificationsObserver.enable();
}
ModificationsObserver.enable();
return this.makeOutput(sanitizedData);
}
/**

View file

@ -64,10 +64,6 @@ export default class Shortcuts extends Module {
public remove(shortcut: string): void {
const index = this.registeredShortcuts.findIndex((shc) => shc.name === shortcut);
if (index === -1 || !this.registeredShortcuts[index]) {
return;
}
this.registeredShortcuts[index].remove();
this.registeredShortcuts.splice(index, 1);
}

View file

@ -4,15 +4,6 @@ import Flipper, { FlipperOptions } from '../../flipper';
import * as _ from '../../utils';
import SelectionUtils from '../../selection';
/**
* HTML Elements that used for BlockSettings
*/
interface BlockSettingsNodes {
wrapper: HTMLElement;
toolSettings: HTMLElement;
defaultSettings: HTMLElement;
}
/**
* Block Settings
*
@ -24,7 +15,7 @@ interface BlockSettingsNodes {
* | ...................... |
* |________________________|
*/
export default class BlockSettings extends Module<BlockSettingsNodes> {
export default class BlockSettings extends Module {
/**
* Module Events
*
@ -66,6 +57,15 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
return this.nodes.wrapper.classList.contains(this.CSS.wrapperOpened);
}
/**
* Block settings UI HTML elements
*/
public nodes: {[key: string]: HTMLElement} = {
wrapper: null,
toolSettings: null,
defaultSettings: null,
};
/**
* List of buttons
*/
@ -103,15 +103,6 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
this.enableFlipper();
}
/**
* Destroys module
*/
public destroy(): void {
this.flipper.deactivate();
this.flipper = null;
this.removeAllNodes();
}
/**
* Open Block Settings pane
*/
@ -128,7 +119,6 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
* Highlight content of a Block we are working with
*/
this.Editor.BlockManager.currentBlock.selected = true;
this.Editor.BlockSelection.clearCache();
/**
* Fill Tool's settings

View file

@ -2,23 +2,15 @@ import Module from '../../__module';
import $ from '../../dom';
import { BlockToolConstructable } from '../../../../types';
import * as _ from '../../utils';
import { SavedData } from '../../../../types/data-formats';
import { SavedData } from '../../../types-internal/block-data';
import Flipper from '../../flipper';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
/**
* HTML Elements used for ConversionToolbar
*/
interface ConversionToolbarNodes {
wrapper: HTMLElement;
tools: HTMLElement;
}
/**
* Block Converter
*/
export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
export default class ConversionToolbar extends Module {
/**
* CSS getter
*/
@ -37,6 +29,14 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
};
}
/**
* HTML Elements used for UI
*/
public nodes: { [key: string]: HTMLElement } = {
wrapper: null,
tools: null,
};
/**
* Conversion Toolbar open/close state
*
@ -65,10 +65,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
* Create UI of Conversion Toolbar
*/
public make(): HTMLElement {
this.nodes.wrapper = $.make('div', [
ConversionToolbar.CSS.conversionToolbarWrapper,
...(this.isRtl ? [ this.Editor.UI.CSS.editorRtlFix ] : []),
]);
this.nodes.wrapper = $.make('div', ConversionToolbar.CSS.conversionToolbarWrapper);
this.nodes.tools = $.make('div', ConversionToolbar.CSS.conversionToolbarTools);
const label = $.make('div', ConversionToolbar.CSS.conversionToolbarLabel, {
@ -91,15 +88,6 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
return this.nodes.wrapper;
}
/**
* Deactivates flipper and removes all nodes
*/
public destroy(): void {
this.flipper.deactivate();
this.flipper = null;
this.removeAllNodes();
}
/**
* Toggle conversion dropdown visibility
*
@ -161,7 +149,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
public hasTools(): boolean {
const tools = Object.keys(this.tools); // available tools in array representation
return !(tools.length === 1 && tools.shift() === this.config.defaultBlock);
return !(tools.length === 1 && tools.shift() === this.config.initialBlock);
}
/**
@ -170,7 +158,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
*
* @param {string} replacingToolName - name of Tool which replaces current
*/
public async replaceWithBlock(replacingToolName: string): Promise<void> {
public async replaceWithBlock(replacingToolName: string): Promise <void> {
/**
* At first, we get current Block data
*
@ -184,10 +172,10 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
/**
* When current Block name is equals to the replacing tool Name,
* than convert this Block back to the default Block
* than convert this Block back to the initial Block
*/
if (currentBlockName === replacingToolName) {
replacingToolName = this.config.defaultBlock;
replacingToolName = this.config.initialBlock;
}
/**
@ -276,15 +264,10 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
const toolToolboxSettings = toolClass[internalSettings.TOOLBOX];
const conversionConfig = toolClass[internalSettings.CONVERSION_CONFIG];
const userSettings = this.Editor.Tools.USER_SETTINGS;
const userToolboxSettings = this.Editor.Tools.getToolSettings(toolName)[userSettings.TOOLBOX];
const toolboxSettings = userToolboxSettings ?? toolToolboxSettings;
/**
* Skip tools that don't pass 'toolbox' property
*/
if (_.isEmpty(toolboxSettings) || !toolboxSettings.icon) {
if (_.isEmpty(toolToolboxSettings) || !toolToolboxSettings.icon) {
continue;
}
@ -295,7 +278,7 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
continue;
}
this.addTool(toolName, toolboxSettings.icon, toolboxSettings.title);
this.addTool(toolName, toolToolboxSettings.icon, toolToolboxSettings.title);
}
}

View file

@ -4,21 +4,6 @@ import * as _ from '../../utils';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
/**
* HTML Elements used for Toolbar UI
*/
interface ToolbarNodes {
wrapper: HTMLElement;
content: HTMLElement;
actions: HTMLElement;
// Content Zone
plusButton: HTMLElement;
// Actions Zone
blockActionsButtons: HTMLElement;
settingsToggler: HTMLElement;
}
/**
*
* «Toolbar» is the node that moves up/down over current block
@ -71,7 +56,23 @@ interface ToolbarNodes {
* @property {Element} nodes.pluginSettings - Plugin Settings section of Settings Panel
* @property {Element} nodes.defaultSettings - Default Settings section of Settings Panel
*/
export default class Toolbar extends Module<ToolbarNodes> {
export default class Toolbar extends Module {
/**
* HTML Elements used for Toolbar UI
*/
public nodes: {[key: string]: HTMLElement} = {
wrapper: null,
content: null,
actions: null,
// Content Zone
plusButton: null,
// Actions Zone
blockActionsButtons: null,
settingsToggler: null,
};
/**
* CSS styles
*
@ -98,76 +99,89 @@ export default class Toolbar extends Module<ToolbarNodes> {
}
/**
* Returns the Toolbar opening state
*
* @returns {boolean}
* Makes toolbar
*/
public get opened(): boolean {
return this.nodes.wrapper.classList.contains(this.CSS.toolbarOpened);
}
public make(): void {
this.nodes.wrapper = $.make('div', this.CSS.toolbar);
/**
* Plus Button public methods
*
* @returns {{hide: function(): void, show: function(): void}}
*/
public get plusButton(): {hide: () => void; show: () => void} {
return {
hide: (): void => this.nodes.plusButton.classList.add(this.CSS.plusButtonHidden),
show: (): void => {
if (this.Editor.Toolbox.isEmpty) {
return;
}
this.nodes.plusButton.classList.remove(this.CSS.plusButtonHidden);
},
};
}
/**
* Make Content Zone and Actions Zone
*/
['content', 'actions'].forEach((el) => {
this.nodes[el] = $.make('div', this.CSS[el]);
});
/**
* Block actions appearance manipulations
*
* @returns {{hide: function(): void, show: function(): void}}
*/
private get blockActions(): {hide: () => void; show: () => void} {
return {
hide: (): void => {
this.nodes.actions.classList.remove(this.CSS.actionsOpened);
},
show: (): void => {
this.nodes.actions.classList.add(this.CSS.actionsOpened);
},
};
}
/**
* Actions will be included to the toolbar content so we can align in to the right of the content
*/
$.append(this.nodes.wrapper, this.nodes.content);
$.append(this.nodes.content, this.nodes.actions);
/**
* Fill Content Zone:
* - Plus Button
* - Toolbox
*/
this.nodes.plusButton = $.make('div', this.CSS.plusButton);
$.append(this.nodes.plusButton, $.svg('plus', 14, 14));
$.append(this.nodes.content, this.nodes.plusButton);
this.Editor.Listeners.on(this.nodes.plusButton, 'click', () => this.plusButtonClicked(), false);
/**
* Add events to show/hide tooltip for plus button
*/
const tooltipContent = $.make('div');
tooltipContent.appendChild(document.createTextNode(I18n.ui(I18nInternalNS.ui.toolbar.toolbox, 'Add')));
tooltipContent.appendChild($.make('div', this.CSS.plusButtonShortcut, {
textContent: '⇥ Tab',
}));
this.Editor.Tooltip.onHover(this.nodes.plusButton, tooltipContent);
/**
* Make a Toolbox
*/
this.Editor.Toolbox.make();
/**
* Fill Actions Zone:
* - Settings Toggler
* - Remove Block Button
* - Settings Panel
*/
this.nodes.blockActionsButtons = $.make('div', this.CSS.blockActionsButtons);
this.nodes.settingsToggler = $.make('span', this.CSS.settingsToggler);
const settingsIcon = $.svg('dots', 8, 8);
$.append(this.nodes.settingsToggler, settingsIcon);
$.append(this.nodes.blockActionsButtons, this.nodes.settingsToggler);
$.append(this.nodes.actions, this.nodes.blockActionsButtons);
this.Editor.Tooltip.onHover(
this.nodes.settingsToggler,
I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune'),
{
placement: 'top',
}
);
/**
* Make and append Settings Panel
*/
this.Editor.BlockSettings.make();
$.append(this.nodes.actions, this.Editor.BlockSettings.nodes.wrapper);
/**
* Append toolbar to the Editor
*/
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
/**
* Module preparation method
* Steps:
* - Make Toolbar dependent components like BlockSettings, Toolbox and so on
* - Make itself and append dependent nodes to itself
*/
public async prepare(): Promise<void> {
/**
* Bind events on the Toolbar elements
*/
if (!this.Editor.ReadOnly.isEnabled) {
this.drawUI();
this.enableModuleBindings();
}
}
/**
* Toggles read-only mode
*
* @param {boolean} readOnlyEnabled - read-only mode
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (!readOnlyEnabled) {
this.drawUI();
this.enableModuleBindings();
} else {
this.destroy();
this.disableModuleBindings();
}
this.bindEvents();
}
/**
@ -237,6 +251,15 @@ export default class Toolbar extends Module<ToolbarNodes> {
}, 50)();
}
/**
* returns toolbar opened state
*
* @returns {boolean}
*/
public get opened(): boolean {
return this.nodes.wrapper.classList.contains(this.CSS.toolbarOpened);
}
/**
* Close the Toolbar
*/
@ -250,81 +273,36 @@ export default class Toolbar extends Module<ToolbarNodes> {
}
/**
* Draws Toolbar elements
* Plus Button public methods
*
* @returns {{hide: function(): void, show: function(): void}}
*/
private make(): void {
this.nodes.wrapper = $.make('div', this.CSS.toolbar);
public get plusButton(): {hide: () => void; show: () => void} {
return {
hide: (): void => this.nodes.plusButton.classList.add(this.CSS.plusButtonHidden),
show: (): void => {
if (this.Editor.Toolbox.isEmpty) {
return;
}
this.nodes.plusButton.classList.remove(this.CSS.plusButtonHidden);
},
};
}
/**
* Make Content Zone and Actions Zone
*/
['content', 'actions'].forEach((el) => {
this.nodes[el] = $.make('div', this.CSS[el]);
});
/**
* Actions will be included to the toolbar content so we can align in to the right of the content
*/
$.append(this.nodes.wrapper, this.nodes.content);
$.append(this.nodes.content, this.nodes.actions);
/**
* Fill Content Zone:
* - Plus Button
* - Toolbox
*/
this.nodes.plusButton = $.make('div', this.CSS.plusButton);
$.append(this.nodes.plusButton, $.svg('plus', 14, 14));
$.append(this.nodes.content, this.nodes.plusButton);
this.readOnlyMutableListeners.on(this.nodes.plusButton, 'click', () => {
this.plusButtonClicked();
}, false);
/**
* Add events to show/hide tooltip for plus button
*/
const tooltipContent = $.make('div');
tooltipContent.appendChild(document.createTextNode(I18n.ui(I18nInternalNS.ui.toolbar.toolbox, 'Add')));
tooltipContent.appendChild($.make('div', this.CSS.plusButtonShortcut, {
textContent: '⇥ Tab',
}));
this.Editor.Tooltip.onHover(this.nodes.plusButton, tooltipContent);
/**
* Fill Actions Zone:
* - Settings Toggler
* - Remove Block Button
* - Settings Panel
*/
this.nodes.blockActionsButtons = $.make('div', this.CSS.blockActionsButtons);
this.nodes.settingsToggler = $.make('span', this.CSS.settingsToggler);
const settingsIcon = $.svg('dots', 8, 8);
$.append(this.nodes.settingsToggler, settingsIcon);
$.append(this.nodes.blockActionsButtons, this.nodes.settingsToggler);
$.append(this.nodes.actions, this.nodes.blockActionsButtons);
this.Editor.Tooltip.onHover(
this.nodes.settingsToggler,
I18n.ui(I18nInternalNS.ui.blockTunes.toggler, 'Click to tune'),
{
placement: 'top',
}
);
/**
* Appending Toolbar components to itself
*/
$.append(this.nodes.content, this.Editor.Toolbox.nodes.toolbox);
$.append(this.nodes.actions, this.Editor.BlockSettings.nodes.wrapper);
/**
* Append toolbar to the Editor
*/
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
/**
* Block actions appearance manipulations
*
* @returns {{hide: function(): void, show: function(): void}}
*/
private get blockActions(): {hide: () => void; show: () => void} {
return {
hide: (): void => {
this.nodes.actions.classList.remove(this.CSS.actionsOpened);
},
show: (): void => {
this.nodes.actions.classList.add(this.CSS.actionsOpened);
},
};
}
/**
@ -335,22 +313,14 @@ export default class Toolbar extends Module<ToolbarNodes> {
}
/**
* Enable bindings
* Bind events on the Toolbar Elements:
* - Block Settings
*/
private enableModuleBindings(): void {
private bindEvents(): void {
/**
* Settings toggler
*/
this.readOnlyMutableListeners.on(this.nodes.settingsToggler, 'click', () => {
this.settingsTogglerClicked();
});
}
/**
* Disable bindings
*/
private disableModuleBindings(): void {
this.readOnlyMutableListeners.clearAll();
this.Editor.Listeners.on(this.nodes.settingsToggler, 'click', () => this.settingsTogglerClicked());
}
/**
@ -363,37 +333,4 @@ export default class Toolbar extends Module<ToolbarNodes> {
this.Editor.BlockSettings.open();
}
}
/**
* Draws Toolbar UI
*
* Toolbar contains BlockSettings and Toolbox.
* Thats why at first we draw its components and then Toolbar itself
*/
private drawUI(): void {
/**
* Make BlockSettings Panel
*/
this.Editor.BlockSettings.make();
/**
* Make Toolbox
*/
this.Editor.Toolbox.make();
/**
* Make Toolbar
*/
this.make();
}
/**
* Removes all created and saved HTMLElements
* It is used in Read-Only mode
*/
private destroy(): void {
this.Editor.Toolbox.destroy();
this.Editor.BlockSettings.destroy();
this.removeAllNodes();
}
}

View file

@ -7,22 +7,6 @@ import Flipper from '../../flipper';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
/**
* Inline Toolbar elements
*/
interface InlineToolbarNodes {
wrapper: HTMLElement;
togglerAndButtonsWrapper: HTMLElement;
buttons: HTMLElement;
conversionToggler: HTMLElement;
conversionTogglerContent: HTMLElement;
/**
* Zone below the buttons where Tools can create additional actions by 'renderActions()' method
* For example, input for the 'link' tool or textarea for the 'comment' tool
*/
actions: HTMLElement;
}
/**
* Inline toolbar with actions that modifies selected text fragment
*
@ -30,7 +14,7 @@ interface InlineToolbarNodes {
* | B i [link] [mark] |
* |________________________|
*/
export default class InlineToolbar extends Module<InlineToolbarNodes> {
export default class InlineToolbar extends Module {
/**
* CSS styles
*/
@ -43,12 +27,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
buttonsWrapper: 'ce-inline-toolbar__buttons',
actionsWrapper: 'ce-inline-toolbar__actions',
inlineToolButton: 'ce-inline-tool',
inlineToolButtonLast: 'ce-inline-tool--last',
inputField: 'cdx-input',
focusedButton: 'ce-inline-tool--focused',
conversionToggler: 'ce-inline-toolbar__dropdown',
conversionTogglerHidden: 'ce-inline-toolbar__dropdown--hidden',
conversionTogglerContent: 'ce-inline-toolbar__dropdown-content',
togglerAndButtonsWrapper: 'ce-inline-toolbar__toggler-and-button-wrapper',
};
/**
@ -58,13 +42,34 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
*/
public opened = false;
/**
* Inline Toolbar elements
*/
private nodes: {
wrapper: HTMLElement;
buttons: HTMLElement;
conversionToggler: HTMLElement;
conversionTogglerContent: HTMLElement;
actions: HTMLElement;
} = {
wrapper: null,
buttons: null,
conversionToggler: null,
conversionTogglerContent: null,
/**
* Zone below the buttons where Tools can create additional actions by 'renderActions()' method
* For example, input for the 'link' tool or textarea for the 'comment' tool
*/
actions: null,
};
/**
* Margin above/below the Toolbar
*/
private readonly toolbarVerticalMargin: number = 5;
/**
* Currently visible tools instances
* Tools instances
*/
private toolsInstances: Map<string, InlineTool>;
@ -88,25 +93,76 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
private flipper: Flipper = null;
/**
* Module preparation method
* Inline Toolbar Tools
*
* @returns {Map<string, InlineTool>}
*/
public async prepare(): Promise<void> {
if (!this.Editor.ReadOnly.isEnabled) {
this.make();
public get tools(): Map<string, InlineTool> {
if (!this.toolsInstances || this.toolsInstances.size === 0) {
const allTools = this.inlineTools;
this.toolsInstances = new Map();
for (const tool in allTools) {
if (Object.prototype.hasOwnProperty.call(allTools, tool)) {
this.toolsInstances.set(tool, allTools[tool]);
}
}
}
return this.toolsInstances;
}
/**
* Toggles read-only mode
*
* @param {boolean} readOnlyEnabled - read-only mode
* Making DOM
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (!readOnlyEnabled) {
this.make();
} else {
this.destroy();
}
public make(): void {
this.nodes.wrapper = $.make('div', this.CSS.inlineToolbar);
this.nodes.buttons = $.make('div', this.CSS.buttonsWrapper);
this.nodes.actions = $.make('div', this.CSS.actionsWrapper);
// To prevent reset of a selection when click on the wrapper
this.Editor.Listeners.on(this.nodes.wrapper, 'mousedown', (event) => {
const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`);
// If click is on actions wrapper,
// do not prevent default behaviour because actions might include interactive elements
if (!isClickedOnActionsWrapper) {
event.preventDefault();
}
});
/**
* Append Inline Toolbar to the Editor
*/
$.append(this.nodes.wrapper, [this.nodes.buttons, this.nodes.actions]);
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
/**
* Add button that will allow switching block type
*/
this.addConversionToggler();
/**
* Append Inline Toolbar Tools
*/
this.addTools();
/**
* Prepare conversion toolbar.
* If it has any conversion tool then it will be enabled in the Inline Toolbar
*/
this.prepareConversionToolbar();
/**
* Recalculate initial width with all buttons
*/
this.recalculateWidth();
/**
* Allow to leaf buttons by arrows / tab
* Buttons will be filled on opening
*/
this.enableFlipper();
}
/**
@ -132,6 +188,9 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
this.move();
this.open();
this.Editor.Toolbar.close();
/** Check Tools state for selected fragment */
this.checkToolsState();
}
/**
@ -184,19 +243,8 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
* Hides Inline Toolbar
*/
public close(): void {
if (!this.opened) {
return;
}
if (this.Editor.ReadOnly.isEnabled) {
return;
}
this.nodes.wrapper.classList.remove(this.CSS.inlineToolbarShowed);
this.toolsInstances.forEach((toolInstance) => {
/**
* @todo replace 'clear' with 'destroy'
*/
this.tools.forEach((toolInstance) => {
if (typeof toolInstance.clear === 'function') {
toolInstance.clear();
}
@ -212,19 +260,25 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
* Shows Inline Toolbar
*/
public open(): void {
if (this.opened) {
return;
}
/**
* Filter inline-tools and show only allowed by Block's Tool
*/
this.addToolsFiltered();
this.filterTools();
/**
* Show Inline Toolbar
*/
this.nodes.wrapper.classList.add(this.CSS.inlineToolbarShowed);
/**
* Call 'clear' method for Inline Tools (for example, 'link' want to clear input)
*/
this.tools.forEach((toolInstance: InlineTool) => {
if (typeof toolInstance.clear === 'function') {
toolInstance.clear();
}
});
this.buttonsList = this.nodes.buttons.querySelectorAll(`.${this.CSS.inlineToolButton}`);
this.opened = true;
@ -260,154 +314,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
return this.nodes.wrapper.contains(node);
}
/**
* Removes UI and its components
*/
public destroy(): void {
this.flipper.deactivate();
this.flipper = null;
this.Editor.ConversionToolbar.destroy();
}
/**
* Returns inline toolbar settings for a particular tool
*
* @param {string} toolName - user specified name of tool
* @returns - array of ordered tool names or false
*/
private getInlineToolbarSettings(toolName): string[]|boolean {
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
/**
* InlineToolbar property of a particular tool
*/
const settingsForTool = toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS];
/**
* Whether to enable IT for a particular tool is the decision of the editor user.
* He can enable it by the inlineToolbar settings for this tool. To enable, he should pass true or strings[]
*/
const enabledForTool = settingsForTool === true || Array.isArray(settingsForTool);
/**
* Disabled by user
*/
if (!enabledForTool) {
return false;
}
/**
* 1st priority.
*
* If user pass the list of inline tools for the particular tool, return it.
*/
if (Array.isArray(settingsForTool)) {
return settingsForTool;
}
/**
* 2nd priority.
*
* If user pass just 'true' for tool, get common inlineToolbar settings
* - if common settings is an array, use it
* - if common settings is 'true' or not specified, get default order
*/
/**
* Common inlineToolbar settings got from the root of EditorConfig
*/
const commonInlineToolbarSettings = this.config.inlineToolbar;
/**
* If common settings is an array, use it
*/
if (Array.isArray(commonInlineToolbarSettings)) {
return commonInlineToolbarSettings;
}
/**
* If common settings is 'true' or not specified (will be set as true at core.ts), get the default order
*/
if (commonInlineToolbarSettings === true) {
const defaultToolsOrder: string[] = Object.entries(this.Editor.Tools.available)
.filter(([name, tool]) => {
return tool[this.Editor.Tools.INTERNAL_SETTINGS.IS_INLINE];
})
.map(([name, tool]) => {
return name;
});
return defaultToolsOrder;
}
return false;
}
/**
* Making DOM
*/
private make(): void {
this.nodes.wrapper = $.make('div', [
this.CSS.inlineToolbar,
...(this.isRtl ? [ this.Editor.UI.CSS.editorRtlFix ] : []),
]);
/**
* Creates a different wrapper for toggler and buttons.
*/
this.nodes.togglerAndButtonsWrapper = $.make('div', this.CSS.togglerAndButtonsWrapper);
this.nodes.buttons = $.make('div', this.CSS.buttonsWrapper);
this.nodes.actions = $.make('div', this.CSS.actionsWrapper);
// To prevent reset of a selection when click on the wrapper
this.Editor.Listeners.on(this.nodes.wrapper, 'mousedown', (event) => {
const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`);
// If click is on actions wrapper,
// do not prevent default behaviour because actions might include interactive elements
if (!isClickedOnActionsWrapper) {
event.preventDefault();
}
});
/**
* Append the intermediary wrapper which contains toggler and buttons and button actions.
*/
$.append(this.nodes.wrapper, [this.nodes.togglerAndButtonsWrapper, this.nodes.actions]);
/**
* Append the inline toolbar to the editor.
*/
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
/**
* Add button that will allow switching block type
*/
this.addConversionToggler();
/**
* Wrapper for the inline tools
* Will be appended after the Conversion Toolbar toggler
*/
$.append(this.nodes.togglerAndButtonsWrapper, this.nodes.buttons);
/**
* Prepare conversion toolbar.
* If it has any conversion tool then it will be enabled in the Inline Toolbar
*/
this.prepareConversionToolbar();
/**
* Recalculate initial width with all buttons
*/
this.recalculateWidth();
/**
* Allow to leaf buttons by arrows / tab
* Buttons will be filled on opening
*/
this.enableFlipper();
}
/**
* Need to show Inline Toolbar or not
*/
@ -452,12 +358,59 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
return false;
}
/**
* getInlineToolbarSettings could return an string[] (order of tools) or false (Inline Toolbar disabled).
*/
const inlineToolbarSettings = this.getInlineToolbarSettings(currentBlock.name);
const toolSettings = this.Editor.Tools.getToolSettings(currentBlock.name);
return inlineToolbarSettings !== false;
return toolSettings && toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS];
}
/**
* Show only allowed Tools
*/
private filterTools(): void {
const currentSelection = SelectionUtils.get(),
currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode as HTMLElement);
const toolSettings = this.Editor.Tools.getToolSettings(currentBlock.name),
inlineToolbarSettings = toolSettings && toolSettings[this.Editor.Tools.USER_SETTINGS.ENABLED_INLINE_TOOLS];
/**
* All Inline Toolbar buttons
*
* @type {HTMLElement[]}
*/
const buttons = Array.from(this.nodes.buttons.querySelectorAll(`.${this.CSS.inlineToolButton}`)) as HTMLElement[];
/**
* Show previously hided
*/
buttons.forEach((button) => {
button.hidden = false;
button.classList.remove(this.CSS.inlineToolButtonLast);
});
/**
* Filter buttons if Block Tool pass config like inlineToolbar=['link']
*/
if (Array.isArray(inlineToolbarSettings)) {
buttons.forEach((button) => {
button.hidden = !inlineToolbarSettings.includes(button.dataset.tool);
});
}
/**
* Tick for removing right-margin from last visible button.
* Current generation of CSS does not allow to filter :visible elements
*/
const lastVisibleButton = buttons.filter((button) => !button.hidden).pop();
if (lastVisibleButton) {
lastVisibleButton.classList.add(this.CSS.inlineToolButtonLast);
}
/**
* Recalculate width because some buttons can be hidden
*/
this.recalculateWidth();
}
/**
@ -480,7 +433,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
this.nodes.conversionToggler.appendChild(this.nodes.conversionTogglerContent);
this.nodes.conversionToggler.appendChild(icon);
this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler);
this.nodes.buttons.appendChild(this.nodes.conversionToggler);
this.Editor.Listeners.on(this.nodes.conversionToggler, 'click', () => {
this.Editor.ConversionToolbar.toggle((conversionToolbarOpened) => {
@ -553,40 +506,12 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
*/
/**
* Append only allowed Tools
* Fill Inline Toolbar with Tools
*/
private addToolsFiltered(): void {
const currentSelection = SelectionUtils.get();
const currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode as HTMLElement);
/**
* Clear buttons list
*/
this.nodes.buttons.innerHTML = '';
this.nodes.actions.innerHTML = '';
this.toolsInstances = new Map();
/**
* Filter buttons if Block Tool pass config like inlineToolbar=['link']
* Else filter them according to the default inlineToolbar property.
*
* For this moment, inlineToolbarOrder could not be 'false'
* because this method will be called only if the Inline Toolbar is enabled
*/
const inlineToolbarOrder = this.getInlineToolbarSettings(currentBlock.name) as string[];
inlineToolbarOrder.forEach((toolName) => {
const toolSettings = this.Editor.Tools.getToolSettings(toolName);
const tool = this.Editor.Tools.constructInline(this.Editor.Tools.inline[toolName], toolName, toolSettings);
this.addTool(toolName, tool);
tool.checkState(SelectionUtils.get());
private addTools(): void {
this.tools.forEach((toolInstance, toolName) => {
this.addTool(toolName, toolInstance);
});
/**
* Recalculate width because some buttons can be hidden
*/
this.recalculateWidth();
}
/**
@ -612,7 +537,6 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
button.dataset.tool = toolName;
this.nodes.buttons.appendChild(button);
this.toolsInstances.set(toolName, tool);
if (typeof tool.renderActions === 'function') {
const actions = tool.renderActions();
@ -741,7 +665,7 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
* Check Tools` state by selection
*/
private checkToolsState(): void {
this.toolsInstances.forEach((toolInstance) => {
this.tools.forEach((toolInstance) => {
toolInstance.checkState(SelectionUtils.get());
});
}

View file

@ -7,14 +7,6 @@ import { BlockToolAPI } from '../../block';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
/**
* HTMLElements used for Toolbox UI
*/
interface ToolboxNodes {
toolbox: HTMLElement;
buttons: HTMLElement[];
}
/**
* @class Toolbox
* @classdesc Holder for Tools
@ -25,15 +17,7 @@ interface ToolboxNodes {
* @property {object} CSS - CSS class names
*
*/
export default class Toolbox extends Module<ToolboxNodes> {
/**
* Current module HTML Elements
*/
public nodes = {
toolbox: null,
buttons: [],
}
export default class Toolbox extends Module {
/**
* CSS styles
*
@ -68,6 +52,17 @@ export default class Toolbox extends Module<ToolboxNodes> {
*/
public opened = false;
/**
* HTMLElements used for Toolbox UI
*/
public nodes: {
toolbox: HTMLElement;
buttons: HTMLElement[];
} = {
toolbox: null,
buttons: [],
};
/**
* How many tools displayed in Toolbox
*
@ -87,20 +82,12 @@ export default class Toolbox extends Module<ToolboxNodes> {
*/
public make(): void {
this.nodes.toolbox = $.make('div', this.CSS.toolbox);
$.append(this.Editor.Toolbar.nodes.content, this.nodes.toolbox);
this.addTools();
this.enableFlipper();
}
/**
* Destroy Module
*/
public destroy(): void {
this.flipper.deactivate();
this.flipper = null;
this.removeAllNodes();
}
/**
* Toolbox Tool's button click handler
*
@ -196,19 +183,12 @@ export default class Toolbox extends Module<ToolboxNodes> {
// return;
// }
const userToolboxSettings = this.Editor.Tools.getToolSettings(toolName)[userSettings.TOOLBOX];
/**
* Hide Toolbox button if Toolbox settings is false
*/
if ((userToolboxSettings ?? toolToolboxSettings) === false) {
return;
}
const userToolboxSettings = this.Editor.Tools.getToolSettings(toolName)[userSettings.TOOLBOX] || {};
const button = $.make('li', [ this.CSS.toolboxButton ]);
button.dataset.tool = toolName;
button.innerHTML = (userToolboxSettings && userToolboxSettings.icon) || toolToolboxSettings.icon;
button.innerHTML = userToolboxSettings.icon || toolToolboxSettings.icon;
$.append(this.nodes.toolbox, button);

View file

@ -43,9 +43,9 @@ export default class Tools extends Module {
/**
* Returns available Tools
*
* @returns {object<Tool>}
* @returns {Tool[]}
*/
public get available(): { [name: string]: ToolConstructable } {
public get available(): {[name: string]: ToolConstructable} {
return this.toolsAvailable;
}
@ -54,7 +54,7 @@ export default class Tools extends Module {
*
* @returns {Tool[]}
*/
public get unavailable(): { [name: string]: ToolConstructable } {
public get unavailable(): {[name: string]: ToolConstructable} {
return this.toolsUnavailable;
}
@ -63,7 +63,7 @@ export default class Tools extends Module {
*
* @returns {object} - object of Inline Tool's classes
*/
public get inline(): { [name: string]: InlineToolConstructable } {
public get inline(): {[name: string]: ToolConstructable} {
if (this._inlineTools) {
return this._inlineTools;
}
@ -112,7 +112,7 @@ export default class Tools extends Module {
/**
* Return editor block tools
*/
public get blockTools(): { [name: string]: BlockToolConstructable } {
public get blockTools(): {[name: string]: BlockToolConstructable} {
const tools = Object.entries(this.available).filter(([, tool]) => {
return !tool[this.INTERNAL_SETTINGS.IS_INLINE];
});
@ -134,7 +134,7 @@ export default class Tools extends Module {
*
* @returns {object}
*/
public get INTERNAL_SETTINGS(): { [name: string]: string } {
public get INTERNAL_SETTINGS(): {[name: string]: string} {
return {
IS_ENABLED_LINE_BREAKS: 'enableLineBreaks',
IS_INLINE: 'isInline',
@ -143,7 +143,6 @@ export default class Tools extends Module {
TOOLBOX: 'toolbox',
SANITIZE_CONFIG: 'sanitize',
CONVERSION_CONFIG: 'conversionConfig',
IS_READ_ONLY_SUPPORTED: 'isReadOnlySupported',
};
}
@ -152,7 +151,7 @@ export default class Tools extends Module {
*
* return {object}
*/
public get USER_SETTINGS(): { [name: string]: string } {
public get USER_SETTINGS(): {[name: string]: string} {
return {
SHORTCUT: 'shortcut',
TOOLBOX: 'toolbox',
@ -167,24 +166,24 @@ export default class Tools extends Module {
*
* @type {object}
*/
public readonly toolsClasses: { [name: string]: ToolConstructable } = {};
public readonly toolsClasses: {[name: string]: ToolConstructable} = {};
/**
* Tools` classes available to use
*/
private readonly toolsAvailable: { [name: string]: ToolConstructable } = {};
private readonly toolsAvailable: {[name: string]: ToolConstructable} = {};
/**
* Tools` classes not available to use because of preparation failure
*/
private readonly toolsUnavailable: { [name: string]: ToolConstructable } = {};
private readonly toolsUnavailable: {[name: string]: ToolConstructable} = {};
/**
* Tools settings in a map {name: settings, ...}
*
* @type {object}
*/
private readonly toolsSettings: { [name: string]: ToolSettings } = {};
private readonly toolsSettings: {[name: string]: ToolSettings} = {};
/**
* Cache for the prepared inline tools
@ -192,7 +191,7 @@ export default class Tools extends Module {
* @type {null|object}
* @private
*/
private _inlineTools: { [name: string]: ToolConstructable } = {};
private _inlineTools: {[name: string]: ToolConstructable} = {};
/**
* @class
@ -301,9 +300,9 @@ export default class Tools extends Module {
/**
* to see how it works {@link '../utils.ts#sequence'}
*/
return _.sequence(sequenceData, (data: { toolName: string }) => {
return _.sequence(sequenceData, (data: {toolName: string}) => {
this.success(data);
}, (data: { toolName: string }) => {
}, (data: {toolName: string}) => {
this.fallback(data);
});
}
@ -313,7 +312,7 @@ export default class Tools extends Module {
*
* @param {object} data - append tool to available list
*/
public success(data: { toolName: string }): void {
public success(data: {toolName: string}): void {
this.toolsAvailable[data.toolName] = this.toolsClasses[data.toolName];
}
@ -322,7 +321,7 @@ export default class Tools extends Module {
*
* @param {object} data - append tool to unavailable list
*/
public fallback(data: { toolName: string }): void {
public fallback(data: {toolName: string}): void {
this.toolsUnavailable[data.toolName] = this.toolsClasses[data.toolName];
}
@ -335,11 +334,7 @@ export default class Tools extends Module {
*
* @returns {InlineTool} instance
*/
public constructInline(
tool: InlineToolConstructable,
name: string,
toolSettings: ToolSettings = {} as ToolSettings
): InlineTool {
public constructInline(tool: InlineToolConstructable, name: string, toolSettings: ToolSettings = {} as ToolSettings): InlineTool {
const constructorOptions = {
api: this.Editor.API.getMethodsForTool(name),
config: (toolSettings[this.USER_SETTINGS.CONFIG] || {}) as ToolSettings,
@ -350,14 +345,14 @@ export default class Tools extends Module {
}
/**
* Check if passed Tool is an instance of Default Block Tool
* Check if passed Tool is an instance of Initial Block Tool
*
* @param {Tool} tool - Tool to check
*
* @returns {boolean}
*/
public isDefault(tool): boolean {
return tool instanceof this.available[this.config.defaultBlock];
public isInitial(tool): boolean {
return tool instanceof this.available[this.config.initialBlock];
}
/**
@ -371,8 +366,8 @@ export default class Tools extends Module {
const settings = this.toolsSettings[toolName];
const config = settings[this.USER_SETTINGS.CONFIG] || {};
// Pass placeholder to default Block config
if (toolName === this.config.defaultBlock && !config.placeholder) {
// Pass placeholder to initial Block config
if (toolName === this.config.initialBlock && !config.placeholder) {
config.placeholder = this.config.placeholder;
settings[this.USER_SETTINGS.CONFIG] = config;
}
@ -384,7 +379,7 @@ export default class Tools extends Module {
* Returns internal tools
* Includes Bold, Italic, Link and Paragraph
*/
public get internalTools(): { [toolName: string]: ToolConstructable | ToolSettings } {
public get internalTools(): {[toolName: string]: ToolConstructable|ToolSettings} {
return {
bold: { class: BoldInlineTool },
italic: { class: ItalicInlineTool },
@ -397,40 +392,19 @@ export default class Tools extends Module {
};
}
/**
* Returns true if tool supports read-only mode
*
* @param tool - tool to check
*/
public isReadOnlySupported(tool: BlockToolConstructable): boolean {
return tool[this.INTERNAL_SETTINGS.IS_READ_ONLY_SUPPORTED] === true;
}
/**
* Calls each Tool reset method to clean up anything set by Tool
*/
public destroy(): void {
Object.values(this.available).forEach(async tool => {
if (_.isFunction(tool.reset)) {
await tool.reset();
}
});
}
/**
* Binds prepare function of plugins with user or default config
*
* @returns {Array} list of functions that needs to be fired sequentially
*/
private getListOfPrepareFunctions(): Array<{
function: (data: { toolName: string; config: ToolConfig }) => void;
data: { toolName: string; config: ToolConfig };
function: (data: {toolName: string; config: ToolConfig}) => void;
data: {toolName: string; config: ToolConfig};
}> {
const toolPreparationList: Array<{
function: (data: { toolName: string; config: ToolConfig }) => void;
data: { toolName: string; config: ToolConfig };
}
> = [];
function: (data: {toolName: string; config: ToolConfig}) => void;
data: {toolName: string; config: ToolConfig};}
> = [];
for (const toolName in this.toolsClasses) {
if (Object.prototype.hasOwnProperty.call(this.toolsClasses, toolName)) {
@ -444,7 +418,7 @@ export default class Tools extends Module {
*/
toolPreparationList.push({
// eslint-disable-next-line @typescript-eslint/no-empty-function
function: typeof toolClass.prepare === 'function' ? toolClass.prepare : (): void => { },
function: typeof toolClass.prepare === 'function' ? toolClass.prepare : (): void => {},
data: {
toolName,
config: toolConfig,

View file

@ -17,16 +17,6 @@ import Selection from '../selection';
import Block from '../block';
import Flipper from '../flipper';
/**
* HTML Elements used for UI
*/
interface UINodes {
holder: HTMLElement;
wrapper: HTMLElement;
redactor: HTMLElement;
loader: HTMLElement;
}
/**
* @class
*
@ -45,7 +35,7 @@ interface UINodes {
* @property {Element} nodes.wrapper - <codex-editor>
* @property {Element} nodes.redactor - <ce-redactor>
*/
export default class UI extends Module<UINodes> {
export default class UI extends Module {
/**
* Editor.js UI CSS class names
*
@ -53,7 +43,7 @@ export default class UI extends Module<UINodes> {
*/
public get CSS(): {
editorWrapper: string; editorWrapperNarrow: string; editorZone: string; editorZoneHidden: string;
editorLoader: string; editorEmpty: string; editorRtlFix: string;
editorLoader: string; editorEmpty: string;
} {
return {
editorWrapper: 'codex-editor',
@ -62,7 +52,6 @@ export default class UI extends Module<UINodes> {
editorZoneHidden: 'codex-editor__redactor--hidden',
editorLoader: 'codex-editor__loader',
editorEmpty: 'codex-editor--empty',
editorRtlFix: 'codex-editor--rtl',
};
}
@ -101,6 +90,15 @@ export default class UI extends Module<UINodes> {
*/
public isMobile = false;
/**
* HTML Elements used for UI
*/
public nodes: { [key: string]: HTMLElement } = {
holder: null,
wrapper: null,
redactor: null,
};
/**
* Cache for center column rectangle info
* Invalidates on window resize
@ -147,7 +145,7 @@ export default class UI extends Module<UINodes> {
/**
* Make main UI elements
*/
this.make();
await this.make();
/**
* Loader for rendering process
@ -157,47 +155,27 @@ export default class UI extends Module<UINodes> {
/**
* Append SVG sprite
*/
this.appendSVGSprite();
await this.appendSVGSprite();
/**
* Make toolbar
*/
await this.Editor.Toolbar.make();
/**
* Make the Inline toolbar
*/
await this.Editor.InlineToolbar.make();
/**
* Load and append CSS
*/
this.loadStyles();
await this.loadStyles();
/**
* Prepare with read-only state from config
* Bind events for the UI elements
*/
if (!this.Editor.ReadOnly.isEnabled) {
this.enableModuleBindings();
}
}
/**
* Toggle read-only state
*
* If readOnly is true:
* - removes all listeners from main UI module elements
*
* if readOnly is false:
* - enables all listeners to UI module elements
*
* @param {boolean} readOnlyEnabled - "read only" state
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
/**
* Prepare components based on read-only state
*/
if (!readOnlyEnabled) {
/**
* Unbind all events
*/
this.enableModuleBindings();
} else {
/**
* Bind events for the UI elements
*/
this.disableModuleBindings();
}
await this.bindEvents();
}
/**
@ -261,8 +239,10 @@ export default class UI extends Module<UINodes> {
/**
* Makes Editor.js interface
*
* @returns {Promise<void>}
*/
private make(): void {
private async make(): Promise<void> {
/**
* Element where we need to append Editor.js
*
@ -273,10 +253,7 @@ export default class UI extends Module<UINodes> {
/**
* Create and save main UI elements
*/
this.nodes.wrapper = $.make('div', [
this.CSS.editorWrapper,
...(this.isRtl ? [ this.CSS.editorRtlFix ] : []),
]);
this.nodes.wrapper = $.make('div', this.CSS.editorWrapper);
this.nodes.redactor = $.make('div', this.CSS.editorZone);
/**
@ -330,48 +307,41 @@ export default class UI extends Module<UINodes> {
/**
* Bind events on the Editor.js interface
*/
private enableModuleBindings(): void {
this.readOnlyMutableListeners.on(this.nodes.redactor, 'click', (event: MouseEvent) => {
this.redactorClicked(event);
}, false);
private bindEvents(): void {
this.Editor.Listeners.on(
this.nodes.redactor,
'click',
(event) => this.redactorClicked(event as MouseEvent),
false
);
this.Editor.Listeners.on(this.nodes.redactor,
'mousedown',
(event) => this.documentTouched(event as MouseEvent),
true
);
this.Editor.Listeners.on(this.nodes.redactor,
'touchstart',
(event) => this.documentTouched(event as MouseEvent),
true
);
this.readOnlyMutableListeners.on(this.nodes.redactor, 'mousedown', (event: MouseEvent | TouchEvent) => {
this.documentTouched(event);
}, true);
this.readOnlyMutableListeners.on(this.nodes.redactor, 'touchstart', (event: MouseEvent | TouchEvent) => {
this.documentTouched(event);
}, true);
this.readOnlyMutableListeners.on(document, 'keydown', (event: KeyboardEvent) => {
this.documentKeydown(event);
}, true);
this.readOnlyMutableListeners.on(document, 'click', (event: MouseEvent) => {
this.documentClicked(event);
}, true);
this.Editor.Listeners.on(document, 'keydown', (event) => this.documentKeydown(event as KeyboardEvent), true);
this.Editor.Listeners.on(document, 'click', (event) => this.documentClicked(event as MouseEvent), true);
/**
* Handle selection change to manipulate Inline Toolbar appearance
*/
this.readOnlyMutableListeners.on(document, 'selectionchange', (event: Event) => {
this.Editor.Listeners.on(document, 'selectionchange', (event: Event) => {
this.selectionChanged(event);
}, true);
this.readOnlyMutableListeners.on(window, 'resize', () => {
this.Editor.Listeners.on(window, 'resize', () => {
this.resizeDebouncer();
}, {
passive: true,
});
}
/**
* Unbind events on the Editor.js interface
*/
private disableModuleBindings(): void {
this.readOnlyMutableListeners.clearAll();
}
/**
* Resize window handler
*/
@ -418,19 +388,10 @@ export default class UI extends Module<UINodes> {
* @param {KeyboardEvent} event - keyboard event
*/
private defaultBehaviour(event: KeyboardEvent): void {
const { currentBlock } = this.Editor.BlockManager;
const keyDownOnEditor = (event.target as HTMLElement).closest(`.${this.CSS.editorWrapper}`);
const { currentBlock } = this.Editor.BlockManager;
const isMetaKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
/**
* When some block is selected, but the caret is not set inside the editor, treat such keydowns as keydown on selected block.
*/
if (currentBlock !== undefined && keyDownOnEditor === null) {
this.Editor.BlockEvents.keydown(event);
return;
}
/**
* Ignore keydowns on editor and meta keys
*/
@ -462,7 +423,7 @@ export default class UI extends Module<UINodes> {
if (BlockSelection.anyBlockSelected && !Selection.isSelectionExists) {
const selectionPositionIndex = BlockManager.removeSelectedBlocks();
Caret.setToBlock(BlockManager.insertDefaultBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
/** Clear selection */
BlockSelection.clearSelection(event);
@ -517,6 +478,10 @@ export default class UI extends Module<UINodes> {
* remove selected blocks
*/
if (BlockSelection.anyBlockSelected && !Selection.isSelectionExists) {
const selectionPositionIndex = BlockManager.removeSelectedBlocks();
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
/** Clear selection */
BlockSelection.clearSelection(event);
@ -541,7 +506,7 @@ export default class UI extends Module<UINodes> {
*/
if (!this.someToolbarOpened && hasPointerToBlock && (event.target as HTMLElement).tagName === 'BODY') {
/**
* Insert the default typed Block
* Insert initial typed Block
*/
const newBlock = this.Editor.BlockManager.insert();
@ -601,6 +566,13 @@ export default class UI extends Module<UINodes> {
if (!this.Editor.CrossBlockSelection.isCrossBlockSelectionStarted) {
this.Editor.BlockSelection.clearSelection(event);
}
/**
* Clear Selection if user clicked somewhere
*/
if (!this.Editor.CrossBlockSelection.isCrossBlockSelectionStarted) {
this.Editor.BlockSelection.clearSelection(event);
}
}
/**
@ -675,10 +647,8 @@ export default class UI extends Module<UINodes> {
return;
}
const stopPropagation = (): void => {
event.stopImmediatePropagation();
event.stopPropagation();
};
event.stopImmediatePropagation();
event.stopPropagation();
/**
* case when user clicks on anchor element
@ -688,8 +658,6 @@ export default class UI extends Module<UINodes> {
const ctrlKey = event.metaKey || event.ctrlKey;
if ($.isAnchor(element) && ctrlKey) {
stopPropagation();
const href = element.getAttribute('href');
const validUrl = _.getValidUrl(href);
@ -699,21 +667,17 @@ export default class UI extends Module<UINodes> {
}
if (!this.Editor.BlockManager.currentBlock) {
stopPropagation();
this.Editor.BlockManager.insert();
}
/**
* Show the Plus Button if:
* - Block is an default-block (Text)
* - Block is an initial-block (Text)
* - Block is empty
*/
const isDefaultBlock = this.Editor.Tools.isDefault(this.Editor.BlockManager.currentBlock.tool);
if (isDefaultBlock) {
stopPropagation();
const isInitialBlock = this.Editor.Tools.isInitial(this.Editor.BlockManager.currentBlock.tool);
if (isInitialBlock) {
/**
* Check isEmpty only for paragraphs to prevent unnecessary tree-walking on Tools with many nodes (for ex. Table)
*/
@ -732,16 +696,8 @@ export default class UI extends Module<UINodes> {
* @param {Event} event - selection event
*/
private selectionChanged(event: Event): void {
const { CrossBlockSelection, BlockSelection } = this.Editor;
const focusedElement = Selection.anchorElement as Element;
if (CrossBlockSelection.isCrossBlockSelectionStarted) {
// Removes all ranges when any Block is selected
if (BlockSelection.anyBlockSelected) {
Selection.get().removeAllRanges();
}
}
/**
* Event can be fired on clicks at the Editor elements, for example, at the Inline Toolbar
* We need to skip such firings
@ -757,9 +713,6 @@ export default class UI extends Module<UINodes> {
return;
}
/**
* @todo add debounce
*/
this.Editor.InlineToolbar.tryToShow(true);
}

@ -1 +1 @@
Subproject commit 4832182dcf3874cbaedcb789f682bd61782e49ad
Subproject commit 306ed49135905d56ebb7d55f90f26fcd603ca7f1

View file

@ -1,7 +1,7 @@
import $ from '../../dom';
import { API, BlockTool, BlockToolData, BlockToolConstructorOptions } from '../../../../types';
export interface StubData extends BlockToolData {
export interface StubData extends BlockToolData{
title: string;
savedData: BlockToolData;
}
@ -11,11 +11,6 @@ export interface StubData extends BlockToolData {
* It will store its data inside and pass it back with article saving
*/
export default class Stub implements BlockTool {
/**
* Notify core that tool supports read-only mode
*/
public static isReadOnlySupported = true;
/**
* Stub styles
*

View file

@ -183,7 +183,6 @@ export const logLabeled = _log.bind(window, true);
export function isPrintableKey(keyCode: number): boolean {
return (keyCode > 47 && keyCode < 58) || // number keys
keyCode === 32 || keyCode === 13 || // Spacebar & return key(s)
keyCode === 229 || // processing key input for certain languages — Chinese, Japanese, etc.
(keyCode > 64 && keyCode < 91) || // letter keys
(keyCode > 95 && keyCode < 112) || // Numpad keys
(keyCode > 185 && keyCode < 193) || // ;=,-./` (in order)
@ -557,30 +556,3 @@ export function getValidUrl(url: string): string {
export function openTab(url: string): void {
window.open(url, '_blank');
}
/**
* Returns random generated identifier
*
* @param {string} prefix - identifier prefix
*
* @returns {string}
*/
export function generateId(prefix = ''): string {
// tslint:disable-next-line:no-bitwise
return `${prefix}${(Math.floor(Math.random() * 1e8)).toString(16)}`;
}
/**
* Common method for printing a warning about the usage of deprecated property or method.
*
* @param condition - condition for deprecation.
* @param oldProperty - deprecated property.
* @param newProperty - the property that should be used instead.
*/
export function deprecationAssert(condition: boolean, oldProperty: string, newProperty: string): void {
const message = `«${oldProperty}» is deprecated and will be removed in the next major release. Please use the «${newProperty}» instead.`;
if (condition) {
logLabeled(message, 'warn');
}
}

View file

@ -7,7 +7,6 @@
will-change: transform, opacity;
top: 0;
left: 0;
padding: 0 6px;
&--showed {
opacity: 1;
@ -38,6 +37,7 @@
&__buttons {
display: flex;
padding: 0 6px;
}
&__actions {
@ -80,13 +80,6 @@
word-spacing: -3px;
margin-top: 3px;
}
&__toggler-and-button-wrapper {
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
}
}
.ce-inline-tool {
@ -105,6 +98,10 @@
height: 12px;
}
&--last {
margin-right: 0 !important;
}
&--link {
.icon--unlink {
display: none;

View file

@ -9,4 +9,3 @@
@import './animations.css';
@import './export.css';
@import './stub.css';
@import './rtl.css';

View file

@ -1,82 +0,0 @@
.codex-editor.codex-editor--rtl {
direction: rtl;
.cdx-list {
padding-left: 0;
padding-right: 40px;
}
.ce-toolbar {
&__plus {
right: calc(var(--toolbox-buttons-size) * -1);
left: auto;
}
&__actions {
right: auto;
left: calc(var(--toolbox-buttons-size) * -1);
@media (--mobile){
margin-left: 0;
margin-right: auto;
padding-right: 0;
padding-left: 10px;
}
}
}
.ce-settings {
left: 5px;
right: auto;
&::before{
right: auto;
left: 25px;
}
&__button {
&:not(:nth-child(3n+3)) {
margin-left: 3px;
margin-right: 0;
}
}
}
.ce-conversion-tool {
&__icon {
margin-right: 0px;
margin-left: 10px;
}
}
.ce-inline-toolbar {
&__dropdown {
border-right: 0px solid transparent;
border-left: 1px solid var(--color-gray-border);
margin: 0 -6px 0 6px;
.icon--toggler-down {
margin-left: 0px;
margin-right: 4px;
}
}
}
}
.codex-editor--narrow.codex-editor--rtl {
.ce-toolbar__plus {
@media (--not-mobile) {
left: 0px;
right: 5px;
}
}
.ce-toolbar__actions {
@media (--not-mobile) {
left: -5px;
}
}
}

View file

@ -103,7 +103,6 @@
color: var(--grayText);
cursor: pointer;
background: var(--bg-light);
user-select: none;
&:hover {
color: var(--color-dark);

View file

@ -33,13 +33,6 @@
}
}
&--narrow&--rtl &__redactor {
@media (--not-mobile) {
margin-left: var(--narrow-mode-right-padding);
margin-right: 0;
}
}
&--narrow .ce-toolbar__actions {
@media (--not-mobile) {
right: -5px;

20
src/types-internal/block-data.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
import {BlockToolData} from '../../types/tools';
/**
* Tool's saved data
*/
export interface SavedData {
tool: string;
data: BlockToolData;
time: number;
}
/**
* Tool's data after validation
*/
export interface ValidatedData {
tool?: string;
data?: BlockToolData;
time?: number;
isValid: boolean;
}

View file

@ -35,8 +35,6 @@ import InlineToolbarAPI from '../components/modules/api/inlineToolbar';
import CrossBlockSelection from '../components/modules/crossBlockSelection';
import ConversionToolbar from '../components/modules/toolbar/conversion';
import TooltipAPI from '../components/modules/api/tooltip';
import ReadOnly from '../components/modules/readonly';
import ReadOnlyAPI from '../components/modules/api/readonly';
import I18nAPI from '../components/modules/api/i18n';
export interface EditorModules {
@ -77,7 +75,5 @@ export interface EditorModules {
CrossBlockSelection: CrossBlockSelection;
NotifierAPI: NotifierAPI;
TooltipAPI: TooltipAPI;
ReadOnly: ReadOnly;
ReadOnlyAPI: ReadOnlyAPI;
I18nAPI: I18nAPI;
}

View file

@ -1,5 +1,5 @@
import {BlockToolData, ToolConfig} from '../tools';
import {SavedData} from '../data-formats';
import {SavedData} from '../../src/types-internal/block-data';
/**
* @interface BlockAPI Describes Block API methods and properties

14
types/api/blocks.d.ts vendored
View file

@ -13,13 +13,10 @@ export interface Blocks {
/**
* Render passed data
*
* @param {OutputData} data - saved Block data
* @param {boolean} readOnly - the flag that should be used to render a block in the read-only mode
*
* @returns {Promise<void>}
* @param {OutputData} data
* @return {Promise<void>}
*/
render(data: OutputData, readOnly: boolean): Promise<void>;
render(data: OutputData): Promise<void>;
/**
* Render passed HTML string
@ -50,10 +47,11 @@ export interface Blocks {
move(toIndex: number, fromIndex?: number): void;
/**
* Returns Block API object by passed Block index
* Returns Block holder by Block index
* @param {number} index
* @returns {HTMLElement}
*/
getBlockByIndex(index: number): BlockAPI | void;
getBlockByIndex(index: number): BlockAPI;
/**
* Returns current Block index

View file

@ -11,5 +11,4 @@ export * from './notifier';
export * from './tooltip';
export * from './inline-toolbar';
export * from './block';
export * from './readonly';
export * from './i18n';

View file

@ -1,12 +0,0 @@
/**
* ReadOnly API
*/
export interface ReadOnly {
/**
* Set or toggle read-only state
*
* @param {Boolean|undefined} state - set or toggle state
* @returns {Promise<boolean>} current value
*/
toggle: (state?: boolean) => Promise<boolean>;
}

View file

@ -25,13 +25,6 @@ export interface EditorConfig {
* Name should be equal to one of Tool`s keys of passed tools
* If not specified, Paragraph Tool will be used
*/
defaultBlock?: string;
/**
* @deprecated
* This property will be deprecated in the next major release.
* Use the 'defaultBlock' property instead.
*/
initialBlock?: string;
/**
@ -70,11 +63,6 @@ export interface EditorConfig {
*/
logLevel?: LogLevels;
/**
* Enable read-only mode
*/
readOnly?: boolean;
/**
* Internalization config
*/
@ -90,9 +78,4 @@ export interface EditorConfig {
* @param {API} api - editor.js api
*/
onChange?(api: API): void;
/**
* Defines default toolbar for all tools.
*/
inlineToolbar?: string[]|boolean;
}

View file

@ -7,10 +7,5 @@ export interface I18nConfig {
/**
* Dictionary used for translation
*/
messages?: I18nDictionary;
/**
* Text direction. If not set, uses ltr
*/
direction?: 'ltr' | 'rtl';
messages: I18nDictionary;
}

View file

@ -1,20 +0,0 @@
import {BlockToolData} from '../tools';
/**
* Tool's saved data
*/
export interface SavedData {
tool: string;
data: BlockToolData;
time: number;
}
/**
* Tool's data after validation
*/
export interface ValidatedData {
tool?: string;
data?: BlockToolData;
time?: number;
isValid: boolean;
}

View file

@ -1,2 +0,0 @@
export * from './block-data';
export * from './output-data';

11
types/index.d.ts vendored
View file

@ -5,13 +5,12 @@
*/
import {
EditorConfig,
I18nDictionary,
Dictionary,
DictValue,
EditorConfig,
I18nConfig,
I18nDictionary,
} from './configs';
import {
Blocks,
Caret,
@ -19,7 +18,6 @@ import {
InlineToolbar,
Listeners,
Notifier,
ReadOnly,
Sanitizer,
Saver,
Selection,
@ -28,8 +26,7 @@ import {
Tooltip,
I18n,
} from './api';
import { OutputData } from './data-formats';
import {OutputData} from './data-formats/output-data';
/**
* Interfaces used for development
@ -91,7 +88,6 @@ export interface API {
inlineToolbar: InlineToolbar;
tooltip: Tooltip;
i18n: I18n;
readOnly: ReadOnly;
}
/**
@ -112,7 +108,6 @@ declare class EditorJS {
public styles: Styles;
public toolbar: Toolbar;
public inlineToolbar: InlineToolbar;
public readOnly: ReadOnly;
constructor(configuration?: EditorConfig|string);
/**

View file

@ -16,11 +16,6 @@ export interface BlockTool extends BaseTool {
*/
sanitize?: SanitizerConfig;
/**
* @param {boolean} readOnly - render HTML on readonly mode
*/
render(readOnly?: boolean): HTMLElement;
/**
* Process Tool's element in DOM and return raw data
* @param {HTMLElement} block - element created by {@link BlockTool#render} function
@ -54,12 +49,6 @@ export interface BlockTool extends BaseTool {
*/
onPaste?(event: PasteEvent): void;
/**
* Cleanup resources used by your tool here
* Called when the editor is destroyed
*/
destroy?(): void;
/**
* Lifecycle hooks
*/
@ -93,7 +82,6 @@ export interface BlockToolConstructorOptions<D extends object = any, C extends o
data: BlockToolData<D>;
config?: ToolConfig<C>;
block?: BlockAPI;
readOnly: boolean;
}
export interface BlockToolConstructable extends BaseToolConstructable {
@ -122,11 +110,6 @@ export interface BlockToolConstructable extends BaseToolConstructable {
*/
conversionConfig?: ConversionConfig;
/**
* Is Tool supports read-only mode, this property should return true
*/
isReadOnlySupported?: boolean;
/**
* @constructor
*

View file

@ -31,8 +31,6 @@ export interface InlineTool extends BaseTool {
/**
* Function called with Inline Toolbar closing
* @deprecated 2020 10/02 - The new instance will be created each time the button is rendered. So clear is not needed.
* Better to create the 'destroy' method in a future.
*/
clear?(): void;
}

View file

@ -44,7 +44,6 @@ export interface ToolSettings {
/**
* Tool's Toolbox settings
* It will be hidden from Toolbox when false is specified.
*/
toolbox?: ToolboxConfig | false;
toolbox?: ToolboxConfig;
}

View file

@ -40,9 +40,4 @@ export interface BaseToolConstructable {
* @param data
*/
prepare?(data: {toolName: string, config: ToolConfig}): void | Promise<void>;
/**
* Tool`s reset method to clean up anything set by prepare. Can be async
*/
reset?(): void | Promise<void>;
}

View file

@ -1230,9 +1230,8 @@ bluebird@^3.5.5:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.9"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
boolbase@^1.0.0, boolbase@~1.0.0:
version "1.0.0"
@ -1269,7 +1268,6 @@ braces@^3.0.1:
brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
version "1.2.0"
@ -2159,9 +2157,8 @@ electron-to-chromium@^1.3.413:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.418.tgz#840021191f466b803a873e154113620c9f53cec6"
elliptic@^6.0.0:
version "6.5.3"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
version "6.5.2"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762"
dependencies:
bn.js "^4.4.0"
brorand "^1.0.1"
@ -2930,7 +2927,6 @@ hash-base@^3.0.0:
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
dependencies:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
@ -2942,7 +2938,6 @@ hex-color-regex@^1.1.0:
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
dependencies:
hash.js "^1.0.3"
minimalistic-assert "^1.0.0"
@ -3094,7 +3089,6 @@ inflight@^1.0.4:
inherits@2, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.1:
version "2.0.1"
@ -3796,12 +3790,10 @@ min-indent@^1.0.0:
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
minimatch@^3.0.4:
version "3.0.4"