mirror of
https://github.com/codex-team/editor.js
synced 2024-06-10 09:52:36 +02:00
parent
83131d6251
commit
8a5adefe80
2
dist/editor.js
vendored
2
dist/editor.js
vendored
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,7 @@
|
|||
|
||||
### 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)
|
||||
- `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)
|
||||
|
@ -43,7 +44,6 @@
|
|||
|
||||
> *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
|
||||
|
|
|
@ -120,6 +120,31 @@ 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;
|
||||
}
|
||||
|
||||
.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{
|
||||
|
|
|
@ -34,6 +34,15 @@
|
|||
<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>
|
||||
|
@ -74,16 +83,16 @@
|
|||
|
||||
<!-- 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
|
||||
*/
|
||||
|
@ -279,14 +288,37 @@
|
|||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 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"));
|
||||
});
|
||||
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';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit c4c3f564b30cd1bbdeb0bd75a7bd5e6b7dbef61b
|
||||
Subproject commit 7589eb5e52895593c2c8066b5999c03e4dc42e73
|
|
@ -1 +1 @@
|
|||
Subproject commit 8b74d1bef0a925587841dc79a644f0fef2341a23
|
||||
Subproject commit dae258e5eafd155cd9fe079c85bda57b901cc291
|
|
@ -1 +1 @@
|
|||
Subproject commit 44ec669216315cd064629fe0d5cab4773d8bb8eb
|
||||
Subproject commit ad00bd521c4828e251495431136a3a81bb0c3957
|
|
@ -1 +1 @@
|
|||
Subproject commit 034240d58c3186967a0f5b692d9a52ae9cd584cb
|
||||
Subproject commit 33cd9812ba7c9e14f8ed399e2fd2510b4c51cc29
|
|
@ -1 +1 @@
|
|||
Subproject commit bd5249244eea80d061773aa45a4de877a7829041
|
||||
Subproject commit ca0227f1d4040e252405585b03f8f3282ec7fe52
|
|
@ -1 +1 @@
|
|||
Subproject commit 09bb47c4b4f3102cd7ecdba864d723e5142e00f4
|
||||
Subproject commit 2d0dcf66c15e8fd5009c8472413bec4f382d049e
|
|
@ -1 +1 @@
|
|||
Subproject commit 0a0d73b49c2d8077a3e62096c44741db6295e12f
|
||||
Subproject commit 37ebe0deebaac85ca90bc2a7e411dab0a98316dd
|
|
@ -1 +1 @@
|
|||
Subproject commit 497c0437c5572cb9010988e2edf0d5548d58c441
|
||||
Subproject commit 439e5fcea060f549065f02dcdd8dff029658ecb0
|
|
@ -1 +1 @@
|
|||
Subproject commit 119611a4735eba7069776a2b2494d9e70c74e707
|
||||
Subproject commit 4f7663928b39794e4dfc9bb4071a0112402e54b2
|
|
@ -1 +1 @@
|
|||
Subproject commit 698d1e35a682126befdd381d1963242e6b3b9b82
|
||||
Subproject commit 84310bb638399dc671636c33bb2bb24c8381bb7f
|
|
@ -1 +1 @@
|
|||
Subproject commit 369b7e7c5a7aaef72751a62702ca4fd1222d6c97
|
||||
Subproject commit 5c655372455b679f6953c449cc9f99c8fdf6b6e9
|
|
@ -1 +1 @@
|
|||
Subproject commit b666a0dc2ec24e3a25582f4598ca39954d3b3a34
|
||||
Subproject commit 70f1c1680c90b22d74faf059570a66ab58a90dcd
|
|
@ -1 +1 @@
|
|||
Subproject commit 7a99d65f25c371976266062ff74b5d2d4c648433
|
||||
Subproject commit 1e493039a62bca7c3a05091eab2def339141e604
|
|
@ -2,6 +2,12 @@ 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
|
||||
|
@ -11,7 +17,13 @@ import { ModuleConfig } from '../types-internal/module-config';
|
|||
* @property {object} config - Editor user settings
|
||||
* @property {EditorModules} Editor - List of Editor modules
|
||||
*/
|
||||
export default class Module {
|
||||
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;
|
||||
|
||||
/**
|
||||
* Editor modules list
|
||||
*
|
||||
|
@ -26,6 +38,50 @@ export default class Module {
|
|||
*/
|
||||
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
|
||||
|
@ -47,6 +103,19 @@ export default class Module {
|
|||
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)
|
||||
*/
|
||||
|
|
|
@ -10,7 +10,9 @@ 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
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import { SavedData } from '../../../types/data-formats';
|
||||
import $ from '../dom';
|
||||
import * as _ from '../utils';
|
||||
import ApiModule from '../modules/api';
|
||||
import ApiModules from '../modules/api';
|
||||
import BlockAPI from './api';
|
||||
import { ToolType } from '../modules/tools';
|
||||
|
||||
|
@ -49,7 +49,12 @@ interface BlockConstructorOptions {
|
|||
/**
|
||||
* Editor's API methods
|
||||
*/
|
||||
api: ApiModule;
|
||||
api: ApiModules;
|
||||
|
||||
/**
|
||||
* This flag indicates that the Block should be constructed in the read-only mode.
|
||||
*/
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,7 +151,7 @@ export default class Block {
|
|||
/**
|
||||
* Editor`s API module
|
||||
*/
|
||||
private readonly api: ApiModule;
|
||||
private readonly api: ApiModules;
|
||||
|
||||
/**
|
||||
* Focused input index
|
||||
|
@ -197,7 +202,8 @@ 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 {ApiModule} options.api - Editor API module for pass it to the Block Tunes
|
||||
* @param {Module} options.api - Editor API module for pass it to the Block Tunes
|
||||
* @param {boolean} options.readOnly - Read-Only flag
|
||||
*/
|
||||
constructor({
|
||||
name,
|
||||
|
@ -205,6 +211,7 @@ export default class Block {
|
|||
Tool,
|
||||
settings,
|
||||
api,
|
||||
readOnly,
|
||||
}: BlockConstructorOptions) {
|
||||
this.name = name;
|
||||
this.class = Tool;
|
||||
|
@ -220,6 +227,7 @@ export default class Block {
|
|||
config: this.config,
|
||||
api: this.api.getMethodsForTool(name, ToolType.Block),
|
||||
block: this.blockAPI,
|
||||
readOnly: readOnly,
|
||||
});
|
||||
|
||||
this.holder = this.compose();
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
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
|
||||
|
@ -151,7 +149,7 @@ export default class Core {
|
|||
}
|
||||
|
||||
if (!this.config.logLevel) {
|
||||
this.config.logLevel = LogLevels.VERBOSE;
|
||||
this.config.logLevel = _.LogLevels.VERBOSE;
|
||||
}
|
||||
|
||||
_.setLogLevel(this.config.logLevel);
|
||||
|
@ -208,6 +206,9 @@ export default class Core {
|
|||
}
|
||||
}
|
||||
|
||||
this.config.readOnly = this.config.readOnly as boolean || false;
|
||||
this.config.i18n = {};
|
||||
|
||||
/**
|
||||
* Adjust i18n
|
||||
*/
|
||||
|
@ -285,13 +286,17 @@ export default class Core {
|
|||
public async start(): Promise<void> {
|
||||
const modulesToPrepare = [
|
||||
'Tools',
|
||||
'ReadOnly',
|
||||
'UI',
|
||||
'Toolbar',
|
||||
'InlineToolbar',
|
||||
'BlockManager',
|
||||
'Paste',
|
||||
'DragNDrop',
|
||||
'ModificationsObserver',
|
||||
'BlockSelection',
|
||||
'RectangleSelection',
|
||||
'CrossBlockSelection',
|
||||
];
|
||||
|
||||
await modulesToPrepare.reduce(
|
||||
|
@ -301,6 +306,13 @@ 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');
|
||||
|
|
5
src/components/errors/critical.ts
Normal file
5
src/components/errors/critical.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* This type of exception will destroy the Editor! Be careful when using it
|
||||
*/
|
||||
export class CriticalError extends Error {
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Module from '../../__module';
|
||||
import { Caret } from '../../../../types/api';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
* @class CaretAPI
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Module from '../../__module';
|
||||
import { Events } from '../../../../types/api';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
* @class EventsAPI
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,7 +31,8 @@ export default class API extends Module {
|
|||
inlineToolbar: this.Editor.InlineToolbarAPI.methods,
|
||||
tooltip: this.Editor.TooltipAPI.methods,
|
||||
i18n: this.Editor.I18nAPI.methods,
|
||||
} as APIInterfaces;
|
||||
readOnly: this.Editor.ReadOnlyAPI.methods,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Module from '../../__module';
|
||||
import { InlineToolbar } from '../../../../types/api/inline-toolbar';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
* @class InlineToolbarAPI
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Module from '../../__module';
|
||||
import { Listeners } from '../../../../types/api';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
* @class ListenersAPI
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Module from '../../__module';
|
||||
import { Notifier } from '../../../../types/api';
|
||||
import { ConfirmNotifierOptions, NotifierOptions, PromptNotifierOptions } from 'codex-notifier';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
28
src/components/modules/api/readonly.ts
Normal file
28
src/components/modules/api/readonly.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Module from '../../__module';
|
||||
import { Sanitizer } from '../../../../types/api';
|
||||
import { SanitizerConfig } from '../../../../types/configs';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
* @class SanitizerAPI
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Module from '../../__module';
|
||||
import { Saver } from '../../../../types/api';
|
||||
import { OutputData } from '../../../../types';
|
||||
import * as _ from '../../utils';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
* @class SaverAPI
|
||||
|
@ -20,8 +21,18 @@ 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Module from '../../__module';
|
||||
import { Styles } from '../../../../types/api';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Module from '../../__module';
|
||||
import { Toolbar } from '../../../../types/api';
|
||||
import Module from '../../__module';
|
||||
|
||||
/**
|
||||
* @class ToolbarAPI
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -93,7 +93,7 @@ export default class BlockEvents extends Module {
|
|||
*
|
||||
* @param {KeyboardEvent} event - keyup event
|
||||
*/
|
||||
public keyup(event): void {
|
||||
public keyup(event: KeyboardEvent): void {
|
||||
/**
|
||||
* If shift key was pressed some special shortcut is used (eg. cross block selection via shift + arrows)
|
||||
*/
|
||||
|
@ -107,21 +107,6 @@ 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
|
||||
*
|
||||
|
@ -157,10 +142,10 @@ export default class BlockEvents extends Module {
|
|||
/**
|
||||
* Add drop target styles
|
||||
*
|
||||
* @param {DragEvent} e - drag over event
|
||||
* @param {DragEvent} event - drag over event
|
||||
*/
|
||||
public dragOver(e: DragEvent): void {
|
||||
const block = this.Editor.BlockManager.getBlockByChildNode(e.target as Node);
|
||||
public dragOver(event: DragEvent): void {
|
||||
const block = this.Editor.BlockManager.getBlockByChildNode(event.target as Node);
|
||||
|
||||
block.dropTarget = true;
|
||||
}
|
||||
|
@ -168,10 +153,10 @@ export default class BlockEvents extends Module {
|
|||
/**
|
||||
* Remove drop target style
|
||||
*
|
||||
* @param {DragEvent} e - drag leave event
|
||||
* @param {DragEvent} event - drag leave event
|
||||
*/
|
||||
public dragLeave(e: DragEvent): void {
|
||||
const block = this.Editor.BlockManager.getBlockByChildNode(e.target as Node);
|
||||
public dragLeave(event: DragEvent): void {
|
||||
const block = this.Editor.BlockManager.getBlockByChildNode(event.target as Node);
|
||||
|
||||
block.dropTarget = false;
|
||||
}
|
||||
|
|
|
@ -164,8 +164,8 @@ export default class BlockManager extends Module {
|
|||
* @returns {Promise}
|
||||
*/
|
||||
public async prepare(): Promise<void> {
|
||||
const { Listeners, BlockEvents } = this.Editor;
|
||||
const blocks = new Blocks(this.Editor.UI.nodes.redactor);
|
||||
const { BlockEvents, Listeners } = this.Editor;
|
||||
|
||||
/**
|
||||
* We need to use Proxy to overload set/get [] operator.
|
||||
|
@ -192,13 +192,25 @@ export default class BlockManager extends Module {
|
|||
'copy',
|
||||
(e: ClipboardEvent) => BlockEvents.handleCommandC(e)
|
||||
);
|
||||
}
|
||||
|
||||
/** Copy and cut */
|
||||
Listeners.on(
|
||||
document,
|
||||
'cut',
|
||||
(e: ClipboardEvent) => BlockEvents.handleCommandX(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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,6 +223,7 @@ 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({
|
||||
|
@ -219,9 +232,12 @@ export default class BlockManager extends Module {
|
|||
Tool,
|
||||
settings,
|
||||
api: this.Editor.API,
|
||||
readOnly,
|
||||
});
|
||||
|
||||
this.bindEvents(block);
|
||||
if (!readOnly) {
|
||||
this.bindBlockEvents(block);
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
@ -680,18 +696,51 @@ export default class BlockManager extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* Bind Events
|
||||
* Bind Block events
|
||||
*
|
||||
* @param {Block} block - Block to which event should be bound
|
||||
*/
|
||||
private bindEvents(block: Block): void {
|
||||
const { BlockEvents, Listeners } = this.Editor;
|
||||
private bindBlockEvents(block: Block): void {
|
||||
const { BlockEvents } = this.Editor;
|
||||
|
||||
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));
|
||||
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 {
|
||||
/** Copy and cut */
|
||||
this.readOnlyMutableListeners.on(
|
||||
document,
|
||||
'cut',
|
||||
(e: ClipboardEvent) => this.Editor.BlockEvents.handleCommandX(e)
|
||||
);
|
||||
|
||||
this.blocks.forEach((block: Block) => {
|
||||
this.bindBlockEvents(block);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,16 @@ 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
|
||||
*
|
||||
|
@ -71,6 +81,8 @@ export default class BlockSelection extends Module {
|
|||
BlockManager.blocks.forEach((block) => {
|
||||
block.selected = state;
|
||||
});
|
||||
|
||||
this.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,7 +93,11 @@ export default class BlockSelection extends Module {
|
|||
public get anyBlockSelected(): boolean {
|
||||
const { BlockManager } = this.Editor;
|
||||
|
||||
return BlockManager.blocks.some((block) => block.selected === true);
|
||||
if (this.anyBlockSelectedCache === null) {
|
||||
this.anyBlockSelectedCache = BlockManager.blocks.some((block) => block.selected === true);
|
||||
}
|
||||
|
||||
return this.anyBlockSelectedCache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,15 +148,30 @@ export default class BlockSelection extends Module {
|
|||
public prepare(): void {
|
||||
const { Shortcuts } = this.Editor;
|
||||
|
||||
/** Selection shortcut */
|
||||
this.selection = new SelectionUtils();
|
||||
|
||||
/**
|
||||
* CMD/CTRL+A selection shortcut
|
||||
*/
|
||||
Shortcuts.add({
|
||||
name: 'CMD+A',
|
||||
handler: (event) => {
|
||||
const { BlockManager } = this.Editor;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -152,8 +183,21 @@ export default class BlockSelection extends Module {
|
|||
this.handleCommandA(event);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.selection = new SelectionUtils();
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -173,6 +217,8 @@ export default class BlockSelection extends Module {
|
|||
}
|
||||
|
||||
block.selected = false;
|
||||
|
||||
this.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -290,10 +336,19 @@ 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
|
||||
|
|
|
@ -17,6 +17,19 @@ 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
|
||||
*
|
||||
|
@ -51,7 +64,7 @@ export default class CrossBlockSelection extends Module {
|
|||
* @param {boolean} next - if true, toggle next block. Previous otherwise
|
||||
*/
|
||||
public toggleBlockSelectedState(next = true): void {
|
||||
const { BlockManager } = this.Editor;
|
||||
const { BlockManager, BlockSelection } = this.Editor;
|
||||
|
||||
if (!this.lastSelectedBlock) {
|
||||
this.lastSelectedBlock = this.firstSelectedBlock = BlockManager.currentBlock;
|
||||
|
@ -59,6 +72,8 @@ export default class CrossBlockSelection extends Module {
|
|||
|
||||
if (this.firstSelectedBlock === this.lastSelectedBlock) {
|
||||
this.firstSelectedBlock.selected = true;
|
||||
|
||||
BlockSelection.clearCache();
|
||||
SelectionUtils.get().removeAllRanges();
|
||||
}
|
||||
|
||||
|
@ -71,8 +86,12 @@ 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;
|
||||
|
@ -120,6 +139,34 @@ 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
|
||||
|
@ -138,7 +185,7 @@ export default class CrossBlockSelection extends Module {
|
|||
* @param {MouseEvent} event - mouse over event
|
||||
*/
|
||||
private onMouseOver = (event: MouseEvent): void => {
|
||||
const { BlockManager } = this.Editor;
|
||||
const { BlockManager, BlockSelection } = this.Editor;
|
||||
|
||||
const relatedBlock = BlockManager.getBlockByChildNode(event.relatedTarget as Node) || this.lastSelectedBlock;
|
||||
const targetBlock = BlockManager.getBlockByChildNode(event.target as Node);
|
||||
|
@ -157,6 +204,8 @@ export default class CrossBlockSelection extends Module {
|
|||
relatedBlock.selected = true;
|
||||
targetBlock.selected = true;
|
||||
|
||||
BlockSelection.clearCache();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -164,6 +213,8 @@ export default class CrossBlockSelection extends Module {
|
|||
relatedBlock.selected = false;
|
||||
targetBlock.selected = false;
|
||||
|
||||
BlockSelection.clearCache();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -180,7 +231,7 @@ export default class CrossBlockSelection extends Module {
|
|||
* @param {Block} lastBlock - last block in range
|
||||
*/
|
||||
private toggleBlocksSelectedState(firstBlock: Block, lastBlock: Block): void {
|
||||
const { BlockManager } = this.Editor;
|
||||
const { BlockManager, BlockSelection } = this.Editor;
|
||||
const fIndex = BlockManager.blocks.indexOf(firstBlock);
|
||||
const lIndex = BlockManager.blocks.indexOf(lastBlock);
|
||||
|
||||
|
@ -199,6 +250,8 @@ export default class CrossBlockSelection extends Module {
|
|||
block !== (shouldntSelectFirstBlock ? firstBlock : lastBlock)
|
||||
) {
|
||||
BlockManager.blocks[i].selected = !BlockManager.blocks[i].selected;
|
||||
|
||||
BlockSelection.clearCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,30 +14,60 @@ export default class DragNDrop extends Module {
|
|||
private isStartedAtEditor = false;
|
||||
|
||||
/**
|
||||
* Bind events
|
||||
* Bind module. Enable all bindings if it is not read-only mode
|
||||
*/
|
||||
public prepare(): void {
|
||||
this.bindEvents();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add drag events listeners to editor zone
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private bindEvents(): void {
|
||||
this.Editor.Listeners.on(this.Editor.UI.nodes.holder, 'drop', this.processDrop, true);
|
||||
private enableModuleBindings(): void {
|
||||
const { UI } = this.Editor;
|
||||
|
||||
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, 'drop', async (dropEvent: DragEvent) => {
|
||||
await this.processDrop(dropEvent);
|
||||
}, true);
|
||||
|
||||
this.Editor.InlineToolbar.close();
|
||||
this.readOnlyMutableListeners.on(UI.nodes.holder, 'dragstart', () => {
|
||||
this.processDragStart();
|
||||
});
|
||||
|
||||
/* 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);
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +75,7 @@ export default class DragNDrop extends Module {
|
|||
*
|
||||
* @param {DragEvent} dropEvent - drop event
|
||||
*/
|
||||
private processDrop = async (dropEvent: DragEvent): Promise<void> => {
|
||||
private async processDrop(dropEvent: DragEvent): Promise<void> {
|
||||
const {
|
||||
BlockManager,
|
||||
Caret,
|
||||
|
@ -78,6 +108,24 @@ export default class DragNDrop extends Module {
|
|||
this.Editor.Caret.setToBlock(targetBlock, Caret.positions.END);
|
||||
}
|
||||
|
||||
Paste.processDataTransfer(dropEvent.dataTransfer, true);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Module from '../__module';
|
||||
import * as _ from '../utils';
|
||||
|
||||
/**
|
||||
* Event listener information
|
||||
|
@ -6,6 +7,11 @@ import Module from '../__module';
|
|||
* @interface ListenerData
|
||||
*/
|
||||
export interface ListenerData {
|
||||
/**
|
||||
* Listener unique identifier
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Element where to listen to dispatched events
|
||||
*/
|
||||
|
@ -53,20 +59,24 @@ export default class Listeners extends Module {
|
|||
private allListeners: ListenerData[] = [];
|
||||
|
||||
/**
|
||||
* Assigns event listener on element
|
||||
* Assigns event listener on element and returns unique identifier
|
||||
*
|
||||
* @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
|
||||
): void {
|
||||
): string {
|
||||
const id = _.generateId('l');
|
||||
const assignedEventData = {
|
||||
id,
|
||||
element,
|
||||
eventType,
|
||||
handler,
|
||||
|
@ -81,6 +91,8 @@ export default class Listeners extends Module {
|
|||
|
||||
this.allListeners.push(assignedEventData);
|
||||
element.addEventListener(eventType, handler, options);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,6 +122,21 @@ 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
|
||||
*
|
||||
|
@ -211,4 +238,15 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ export default class ModificationsObserver extends Module {
|
|||
}
|
||||
this.observer = null;
|
||||
this.nativeInputs.forEach((input) => this.Editor.Listeners.off(input, 'input', this.mutationDebouncer));
|
||||
this.mutationDebouncer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,13 +65,22 @@ export default class ModificationsObserver extends Module {
|
|||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async prepare(): Promise<void> {
|
||||
/**
|
||||
* wait till Browser render Editor's Blocks
|
||||
*/
|
||||
window.setTimeout(() => {
|
||||
this.setObserver();
|
||||
this.updateNativeInputs();
|
||||
}, 1000);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,4 +179,25 @@ 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,12 +141,25 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,6 +271,15 @@ 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
|
||||
*/
|
||||
|
@ -276,6 +298,7 @@ export default class Paste extends Module {
|
|||
api: this.Editor.API.getMethodsForTool(name),
|
||||
config: {},
|
||||
data: {},
|
||||
readOnly: false,
|
||||
}) as BlockTool;
|
||||
|
||||
if (tool.pasteConfig === false) {
|
||||
|
@ -427,7 +450,7 @@ export default class Paste extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* If Tools is in list of exceptions, skip processing of paste event
|
||||
* If Tools is in list of errors, skip processing of paste event
|
||||
*/
|
||||
if (BlockManager.currentBlock && this.exceptionList.includes(BlockManager.currentBlock.name)) {
|
||||
return;
|
||||
|
|
86
src/components/modules/readonly.ts
Normal file
86
src/components/modules/readonly.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
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 {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
|
||||
if (toolsDontSupportReadOnly.length > 0) {
|
||||
throw new CriticalError(
|
||||
`To enable read-only mode all connected tools should support it. Tools ${toolsDontSupportReadOnly.join(', ')} don't support read-only mode.`
|
||||
);
|
||||
}
|
||||
|
||||
this.readOnlyEnabled = 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> {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -96,38 +96,17 @@ export default class RectangleSelection extends Module {
|
|||
*/
|
||||
private overlayRectangle: HTMLDivElement;
|
||||
|
||||
/**
|
||||
* Listener identifiers
|
||||
*/
|
||||
private listenerIds: string[] = [];
|
||||
|
||||
/**
|
||||
* Module Preparation
|
||||
* Creating rect and hang handlers
|
||||
*/
|
||||
public prepare(): void {
|
||||
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);
|
||||
this.enableModuleBindings();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,6 +175,78 @@ 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
|
||||
*
|
||||
|
@ -270,7 +321,7 @@ export default class RectangleSelection extends Module {
|
|||
*
|
||||
* @param {MouseEvent} event - mouse event
|
||||
*/
|
||||
private changingRectangle(event): void {
|
||||
private changingRectangle(event: MouseEvent): void {
|
||||
if (!this.mousedown) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import Module from '../__module';
|
||||
/* eslint-disable import/no-duplicates */
|
||||
import * as _ from '../utils';
|
||||
import { ChainData } from '../utils';
|
||||
import { BlockToolConstructable, OutputBlockData } from '../../../types';
|
||||
|
||||
/**
|
||||
|
@ -47,7 +45,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();
|
||||
|
||||
|
@ -60,6 +58,7 @@ 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> {
|
||||
|
|
|
@ -64,6 +64,10 @@ 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);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,15 @@ 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
|
||||
*
|
||||
|
@ -15,7 +24,7 @@ import SelectionUtils from '../../selection';
|
|||
* | ...................... |
|
||||
* |________________________|
|
||||
*/
|
||||
export default class BlockSettings extends Module {
|
||||
export default class BlockSettings extends Module<BlockSettingsNodes> {
|
||||
/**
|
||||
* Module Events
|
||||
*
|
||||
|
@ -57,15 +66,6 @@ export default class BlockSettings extends Module {
|
|||
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,6 +103,15 @@ export default class BlockSettings extends Module {
|
|||
this.enableFlipper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys module
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.flipper.deactivate();
|
||||
this.flipper = null;
|
||||
this.removeAllNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Block Settings pane
|
||||
*/
|
||||
|
@ -119,6 +128,7 @@ export default class BlockSettings extends Module {
|
|||
* Highlight content of a Block we are working with
|
||||
*/
|
||||
this.Editor.BlockManager.currentBlock.selected = true;
|
||||
this.Editor.BlockSelection.clearCache();
|
||||
|
||||
/**
|
||||
* Fill Tool's settings
|
||||
|
|
|
@ -7,10 +7,18 @@ 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 {
|
||||
export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||
/**
|
||||
* CSS getter
|
||||
*/
|
||||
|
@ -29,14 +37,6 @@ export default class ConversionToolbar extends Module {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML Elements used for UI
|
||||
*/
|
||||
public nodes: { [key: string]: HTMLElement } = {
|
||||
wrapper: null,
|
||||
tools: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Conversion Toolbar open/close state
|
||||
*
|
||||
|
@ -91,6 +91,15 @@ export default class ConversionToolbar extends Module {
|
|||
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
|
||||
*
|
||||
|
|
|
@ -4,6 +4,21 @@ 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
|
||||
|
@ -56,23 +71,7 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
|
|||
* @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 {
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
|
||||
export default class Toolbar extends Module<ToolbarNodes> {
|
||||
/**
|
||||
* CSS styles
|
||||
*
|
||||
|
@ -99,89 +98,76 @@ export default class Toolbar extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* Makes toolbar
|
||||
* Returns the Toolbar opening state
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public make(): void {
|
||||
this.nodes.wrapper = $.make('div', this.CSS.toolbar);
|
||||
public get opened(): boolean {
|
||||
return this.nodes.wrapper.classList.contains(this.CSS.toolbarOpened);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make Content Zone and Actions Zone
|
||||
*/
|
||||
['content', 'actions'].forEach((el) => {
|
||||
this.nodes[el] = $.make('div', this.CSS[el]);
|
||||
});
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
this.bindEvents();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -251,15 +237,6 @@ export default class Toolbar extends Module {
|
|||
}, 50)();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns toolbar opened state
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get opened(): boolean {
|
||||
return this.nodes.wrapper.classList.contains(this.CSS.toolbarOpened);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the Toolbar
|
||||
*/
|
||||
|
@ -273,36 +250,81 @@ export default class Toolbar extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* Plus Button public methods
|
||||
*
|
||||
* @returns {{hide: function(): void, show: function(): void}}
|
||||
* Draws Toolbar elements
|
||||
*/
|
||||
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);
|
||||
},
|
||||
};
|
||||
}
|
||||
private make(): void {
|
||||
this.nodes.wrapper = $.make('div', this.CSS.toolbar);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
},
|
||||
};
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -313,14 +335,22 @@ export default class Toolbar extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* Bind events on the Toolbar Elements:
|
||||
* - Block Settings
|
||||
* Enable bindings
|
||||
*/
|
||||
private bindEvents(): void {
|
||||
private enableModuleBindings(): void {
|
||||
/**
|
||||
* Settings toggler
|
||||
*/
|
||||
this.Editor.Listeners.on(this.nodes.settingsToggler, 'click', () => this.settingsTogglerClicked());
|
||||
this.readOnlyMutableListeners.on(this.nodes.settingsToggler, 'click', () => {
|
||||
this.settingsTogglerClicked();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable bindings
|
||||
*/
|
||||
private disableModuleBindings(): void {
|
||||
this.readOnlyMutableListeners.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,4 +363,37 @@ export default class Toolbar extends Module {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,21 @@ import Flipper from '../../flipper';
|
|||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
|
||||
/**
|
||||
* Inline Toolbar elements
|
||||
*/
|
||||
interface InlineToolbarNodes {
|
||||
wrapper: 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
|
||||
*
|
||||
|
@ -14,7 +29,7 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
|
|||
* | B i [link] [mark] |
|
||||
* |________________________|
|
||||
*/
|
||||
export default class InlineToolbar extends Module {
|
||||
export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||
/**
|
||||
* CSS styles
|
||||
*/
|
||||
|
@ -42,27 +57,6 @@ export default class InlineToolbar extends Module {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -113,59 +107,25 @@ export default class InlineToolbar extends Module {
|
|||
}
|
||||
|
||||
/**
|
||||
* Making DOM
|
||||
* Module preparation method
|
||||
*/
|
||||
public make(): void {
|
||||
this.nodes.wrapper = $.make('div', [
|
||||
this.CSS.inlineToolbar,
|
||||
...(this.isRtl ? [ this.Editor.UI.CSS.editorRtlFix ] : []),
|
||||
]);
|
||||
this.nodes.buttons = $.make('div', this.CSS.buttonsWrapper);
|
||||
this.nodes.actions = $.make('div', this.CSS.actionsWrapper);
|
||||
public async prepare(): Promise<void> {
|
||||
if (!this.Editor.ReadOnly.isEnabled) {
|
||||
this.make();
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
/**
|
||||
* Toggles read-only mode
|
||||
*
|
||||
* @param {boolean} readOnlyEnabled - read-only mode
|
||||
*/
|
||||
public toggleReadOnly(readOnlyEnabled: boolean): void {
|
||||
if (!readOnlyEnabled) {
|
||||
this.make();
|
||||
} else {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,6 +206,10 @@ export default class InlineToolbar extends Module {
|
|||
* Hides Inline Toolbar
|
||||
*/
|
||||
public close(): void {
|
||||
if (this.Editor.ReadOnly.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nodes.wrapper.classList.remove(this.CSS.inlineToolbarShowed);
|
||||
this.tools.forEach((toolInstance) => {
|
||||
if (typeof toolInstance.clear === 'function') {
|
||||
|
@ -317,6 +281,72 @@ export default class InlineToolbar extends Module {
|
|||
return this.nodes.wrapper.contains(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes UI and its components
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.flipper.deactivate();
|
||||
this.flipper = null;
|
||||
|
||||
this.Editor.ConversionToolbar.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Making DOM
|
||||
*/
|
||||
private make(): void {
|
||||
this.nodes.wrapper = $.make('div', [
|
||||
this.CSS.inlineToolbar,
|
||||
...(this.isRtl ? [ this.Editor.UI.CSS.editorRtlFix ] : []),
|
||||
]);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to show Inline Toolbar or not
|
||||
*/
|
||||
|
|
|
@ -7,6 +7,14 @@ 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
|
||||
|
@ -17,7 +25,15 @@ import { I18nInternalNS } from '../../i18n/namespace-internal';
|
|||
* @property {object} CSS - CSS class names
|
||||
*
|
||||
*/
|
||||
export default class Toolbox extends Module {
|
||||
export default class Toolbox extends Module<ToolboxNodes> {
|
||||
/**
|
||||
* Current module HTML Elements
|
||||
*/
|
||||
public nodes = {
|
||||
toolbox: null,
|
||||
buttons: [],
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS styles
|
||||
*
|
||||
|
@ -52,17 +68,6 @@ export default class Toolbox extends Module {
|
|||
*/
|
||||
public opened = false;
|
||||
|
||||
/**
|
||||
* HTMLElements used for Toolbox UI
|
||||
*/
|
||||
public nodes: {
|
||||
toolbox: HTMLElement;
|
||||
buttons: HTMLElement[];
|
||||
} = {
|
||||
toolbox: null,
|
||||
buttons: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* How many tools displayed in Toolbox
|
||||
*
|
||||
|
@ -82,12 +87,20 @@ export default class Toolbox extends Module {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
|
|
@ -143,6 +143,7 @@ export default class Tools extends Module {
|
|||
TOOLBOX: 'toolbox',
|
||||
SANITIZE_CONFIG: 'sanitize',
|
||||
CONVERSION_CONFIG: 'conversionConfig',
|
||||
IS_READ_ONLY_SUPPORTED: 'isReadOnlySupported',
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -334,7 +335,11 @@ 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,
|
||||
|
@ -392,6 +397,15 @@ 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
|
||||
*/
|
||||
|
|
|
@ -17,6 +17,16 @@ 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
|
||||
*
|
||||
|
@ -35,7 +45,7 @@ import Flipper from '../flipper';
|
|||
* @property {Element} nodes.wrapper - <codex-editor>
|
||||
* @property {Element} nodes.redactor - <ce-redactor>
|
||||
*/
|
||||
export default class UI extends Module {
|
||||
export default class UI extends Module<UINodes> {
|
||||
/**
|
||||
* Editor.js UI CSS class names
|
||||
*
|
||||
|
@ -91,15 +101,6 @@ export default class UI extends Module {
|
|||
*/
|
||||
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
|
||||
|
@ -146,7 +147,7 @@ export default class UI extends Module {
|
|||
/**
|
||||
* Make main UI elements
|
||||
*/
|
||||
await this.make();
|
||||
this.make();
|
||||
|
||||
/**
|
||||
* Loader for rendering process
|
||||
|
@ -156,27 +157,47 @@ export default class UI extends Module {
|
|||
/**
|
||||
* Append SVG sprite
|
||||
*/
|
||||
await this.appendSVGSprite();
|
||||
|
||||
/**
|
||||
* Make toolbar
|
||||
*/
|
||||
await this.Editor.Toolbar.make();
|
||||
|
||||
/**
|
||||
* Make the Inline toolbar
|
||||
*/
|
||||
await this.Editor.InlineToolbar.make();
|
||||
this.appendSVGSprite();
|
||||
|
||||
/**
|
||||
* Load and append CSS
|
||||
*/
|
||||
await this.loadStyles();
|
||||
this.loadStyles();
|
||||
|
||||
/**
|
||||
* Bind events for the UI elements
|
||||
* Prepare with read-only state from config
|
||||
*/
|
||||
await this.bindEvents();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -240,10 +261,8 @@ export default class UI extends Module {
|
|||
|
||||
/**
|
||||
* Makes Editor.js interface
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private async make(): Promise<void> {
|
||||
private make(): void {
|
||||
/**
|
||||
* Element where we need to append Editor.js
|
||||
*
|
||||
|
@ -311,41 +330,48 @@ export default class UI extends Module {
|
|||
/**
|
||||
* Bind events on the Editor.js interface
|
||||
*/
|
||||
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
|
||||
);
|
||||
private enableModuleBindings(): void {
|
||||
this.readOnlyMutableListeners.on(this.nodes.redactor, 'click', (event: MouseEvent) => {
|
||||
this.redactorClicked(event);
|
||||
}, false);
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
/**
|
||||
* Handle selection change to manipulate Inline Toolbar appearance
|
||||
*/
|
||||
this.Editor.Listeners.on(document, 'selectionchange', (event: Event) => {
|
||||
this.readOnlyMutableListeners.on(document, 'selectionchange', (event: Event) => {
|
||||
this.selectionChanged(event);
|
||||
}, true);
|
||||
|
||||
this.Editor.Listeners.on(window, 'resize', () => {
|
||||
this.readOnlyMutableListeners.on(window, 'resize', () => {
|
||||
this.resizeDebouncer();
|
||||
}, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind events on the Editor.js interface
|
||||
*/
|
||||
private disableModuleBindings(): void {
|
||||
this.readOnlyMutableListeners.clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize window handler
|
||||
*/
|
||||
|
@ -570,13 +596,6 @@ export default class UI extends Module {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -708,8 +727,16 @@ export default class UI extends Module {
|
|||
* @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
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 2fee2bbd157afdfaf175c056b314f7fca6d66fbc
|
||||
Subproject commit 43e5ba9f41c424eaf4809cd1c89e29d8a7810ace
|
|
@ -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,6 +11,11 @@ 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
|
||||
*
|
||||
|
|
|
@ -558,6 +558,18 @@ 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.
|
||||
*
|
||||
|
|
4
src/types-internal/editor-modules.d.ts
vendored
4
src/types-internal/editor-modules.d.ts
vendored
|
@ -35,6 +35,8 @@ 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 {
|
||||
|
@ -75,5 +77,7 @@ export interface EditorModules {
|
|||
CrossBlockSelection: CrossBlockSelection;
|
||||
NotifierAPI: NotifierAPI;
|
||||
TooltipAPI: TooltipAPI;
|
||||
ReadOnly: ReadOnly;
|
||||
ReadOnlyAPI: ReadOnlyAPI;
|
||||
I18nAPI: I18nAPI;
|
||||
}
|
||||
|
|
9
types/api/blocks.d.ts
vendored
9
types/api/blocks.d.ts
vendored
|
@ -13,10 +13,13 @@ export interface Blocks {
|
|||
|
||||
/**
|
||||
* Render passed data
|
||||
* @param {OutputData} data
|
||||
* @return {Promise<void>}
|
||||
*
|
||||
* @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>}
|
||||
*/
|
||||
render(data: OutputData): Promise<void>;
|
||||
render(data: OutputData, readOnly: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Render passed HTML string
|
||||
|
|
1
types/api/index.d.ts
vendored
1
types/api/index.d.ts
vendored
|
@ -11,4 +11,5 @@ export * from './notifier';
|
|||
export * from './tooltip';
|
||||
export * from './inline-toolbar';
|
||||
export * from './block';
|
||||
export * from './readonly';
|
||||
export * from './i18n';
|
||||
|
|
12
types/api/readonly.d.ts
vendored
Normal file
12
types/api/readonly.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* 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>;
|
||||
}
|
7
types/configs/editor-config.d.ts
vendored
7
types/configs/editor-config.d.ts
vendored
|
@ -29,7 +29,7 @@ export interface EditorConfig {
|
|||
|
||||
/**
|
||||
* @deprecated
|
||||
* This property will be deprecated in the next major release.
|
||||
* This property will be deprecated in the next major release.
|
||||
* Use the 'defaultBlock' property instead.
|
||||
*/
|
||||
initialBlock?: string;
|
||||
|
@ -70,6 +70,11 @@ export interface EditorConfig {
|
|||
*/
|
||||
logLevel?: LogLevels;
|
||||
|
||||
/**
|
||||
* Enable read-only mode
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
|
||||
/**
|
||||
* Internalization config
|
||||
*/
|
||||
|
|
9
types/index.d.ts
vendored
9
types/index.d.ts
vendored
|
@ -5,12 +5,13 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EditorConfig,
|
||||
I18nDictionary,
|
||||
Dictionary,
|
||||
DictValue,
|
||||
EditorConfig,
|
||||
I18nConfig,
|
||||
I18nDictionary,
|
||||
} from './configs';
|
||||
|
||||
import {
|
||||
Blocks,
|
||||
Caret,
|
||||
|
@ -18,6 +19,7 @@ import {
|
|||
InlineToolbar,
|
||||
Listeners,
|
||||
Notifier,
|
||||
ReadOnly,
|
||||
Sanitizer,
|
||||
Saver,
|
||||
Selection,
|
||||
|
@ -26,6 +28,7 @@ import {
|
|||
Tooltip,
|
||||
I18n,
|
||||
} from './api';
|
||||
|
||||
import { OutputData } from './data-formats';
|
||||
|
||||
/**
|
||||
|
@ -88,6 +91,7 @@ export interface API {
|
|||
inlineToolbar: InlineToolbar;
|
||||
tooltip: Tooltip;
|
||||
i18n: I18n;
|
||||
readOnly: ReadOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,6 +112,7 @@ declare class EditorJS {
|
|||
public styles: Styles;
|
||||
public toolbar: Toolbar;
|
||||
public inlineToolbar: InlineToolbar;
|
||||
public readOnly: ReadOnly;
|
||||
constructor(configuration?: EditorConfig|string);
|
||||
|
||||
/**
|
||||
|
|
11
types/tools/block-tool.d.ts
vendored
11
types/tools/block-tool.d.ts
vendored
|
@ -16,6 +16,11 @@ 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
|
||||
|
@ -88,6 +93,7 @@ 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 {
|
||||
|
@ -116,6 +122,11 @@ export interface BlockToolConstructable extends BaseToolConstructable {
|
|||
*/
|
||||
conversionConfig?: ConversionConfig;
|
||||
|
||||
/**
|
||||
* Is Tool supports read-only mode, this property should return true
|
||||
*/
|
||||
isReadOnlySupported?: boolean;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue