editor.js/src/components/modules/modificationsObserver.ts
Peter Savchenko ac93017c70
Release 2.16 (#966)
* 2.16.0

* [Refactor] Separate internal and external settings (#845)

* Enable flipping tools via standalone class (#830)

* Enable flipping tools via standalone class

* use flipper to refactor (#842)

* use flipper to refactor

* save changes

* update

* fix flipper on inline toolbar

* ready for testing

* requested changes

* update doc

* updates

* destroy flippers

* some requested changes

* update

* update

* ready

* update

* last changes

* update docs

* Hghl active button of CT, simplify activate/deactivate

* separate dom iterator

* unhardcode directions

* fixed a link in readme.md (#856)

* Fix Block selection via CMD+A (#829)

* Fix Block selection via CMD+A

* Delete editor.js.map

* update

* update

* Update CHANGELOG.md

* Improve style of selected blocks (#858)

* Cross-block-selection style improved

* Update CHANGELOG.md

* Fix case when property 'observer' in modificationObserver is not defined (#866)

* Bump lodash.template from 4.4.0 to 4.5.0 (#885)

Bumps [lodash.template](https://github.com/lodash/lodash) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.4.0...4.5.0)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump eslint-utils from 1.3.1 to 1.4.2 (#886)

Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.3.1 to 1.4.2.
- [Release notes](https://github.com/mysticatea/eslint-utils/releases)
- [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.3.1...v1.4.2)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump mixin-deep from 1.3.1 to 1.3.2 (#887)

Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

* update bundle and readme

* Update README.md

* upd codeowners, fix funding

* Minor Docs Fix according to main Readme (#916)

* Inline Toolbar now contains Conversion Toolbar (#932)

* Block lifecycle hooks (#906)

* [Fix] Arrow selection (#964)

* Fix arrow selection

* Add docs

* [issue-926]: fix dom iterator leafing when items are empty (#958)

* [issue-926]: fix dom iterator leafing when items are empty

* update Changelog

* Issue 869 (#963)

* Fix issue 943 (#965)

* [Draft] Feature/tooltip enhancements (#907)

* initial

* update

* make module standalone

* use tooltips as external module

* update

* build via prod mode

* add tooltips as external module

* add declaration file and options param

* add api tooltip

* update

* removed submodule

* removed due to the incorrect setip

* setup tooltips again

* wip

* update tooltip module

* toolbox, inline toolbar

* Tooltips in block tunes not uses shorthand

* shorthand in a plus and block settings

* fix doc

* Update tools-inline.md

* Delete tooltip.css

* Update CHANGELOG.md

* Update codex.tooltips

* Update api.md

* [issue-779]: Grammarly conflicts (#956)

* grammarly conflicts

* update

* upd bundle

* Submodule Header now on master

* Submodule Marker now on master

* Submodule Paragraph now on master

* Submodule InlineCode now on master

* Submodule Simple Image now on master

* [issue-868]: Deleting multiple blocks triggers back button in Firefox (#967)

* Deleting multiple blocks triggers back button in Firefox

@evgenusov

* Update editor.js

* Update CHANGELOG.md

* pass options on removeEventListener (#904)

* pass options on removeEventListener by removeAll

* rebuild

* Merge branch 'release/2.16' into pr/904

* Update CHANGELOG.md

* Update inline.ts

* [Fix] Selection rangecount (#968)

* Fix #952 (#969)

* Update codex.tooltips

* Selection bugfix (#970)

* Selection bugfix

* fix cross block selection

* close inline toolbar when blocks selected via shift

* remove inline toolbar closing on cross block selection mouse up due to the bug (#972)

* [Feature] Log levels (#971)

* Decrease margins (#973)

* Decrease margins

* Update editor.licenses.txt

* Update src/components/domIterator.ts

Co-Authored-By: Murod Khaydarov <murod.haydarov@gmail.com>

* [Fix] Fix delete blocks api method (#974)

* Update docs/usage.md

Co-Authored-By: Murod Khaydarov <murod.haydarov@gmail.com>

* rm unused

* Update yarn.lock file

* upd bundle, changelog
2019-11-30 23:42:39 +03:00

171 lines
4.1 KiB
TypeScript

/**
* @module ModificationsObserver
*
* Handles any mutations
* and gives opportunity to handle outside
*/
import Module from '../__module';
import * as _ from '../utils';
import Block from '../block';
export default class ModificationsObserver extends Module {
/**
* Debounce Timer
* @type {number}
*/
public static readonly DebounceTimer = 450;
/**
* MutationObserver instance
*/
private observer: MutationObserver;
/**
* Allows to temporary disable mutations handling
*/
private disabled: boolean;
/**
* Used to prevent several mutation callback execution
* @type {Function}
*/
private mutationDebouncer = _.debounce( () => {
this.updateNativeInputs();
this.config.onChange();
}, ModificationsObserver.DebounceTimer);
/**
* Array of native inputs in Blocks.
* Changes in native inputs are not handled by modification observer, so we need to set change event listeners on them
*/
private nativeInputs: HTMLElement[] = [];
/**
* Clear timeout and set null to mutationDebouncer property
*/
public destroy() {
this.mutationDebouncer = null;
if (this.observer) {
this.observer.disconnect();
}
this.observer = null;
this.nativeInputs.forEach((input) => this.Editor.Listeners.off(input, 'input', this.mutationDebouncer));
}
/**
* Preparation method
* @return {Promise<void>}
*/
public async prepare(): Promise<void> {
/**
* wait till Browser render Editor's Blocks
*/
window.setTimeout( () => {
this.setObserver();
}, 1000);
}
/**
* Allows to disable observer,
* for example when Editor wants to stealthy mutate DOM
*/
public disable() {
this.disabled = true;
}
/**
* Enables mutation handling
* Should be called after .disable()
*/
public enable() {
this.disabled = false;
}
/**
* setObserver
*
* sets 'DOMSubtreeModified' listener on Editor's UI.nodes.redactor
* so that User can handle outside from API
*/
private setObserver(): void {
const {UI} = this.Editor;
const observerOptions = {
childList: true,
attributes: true,
subtree: true,
characterData: true,
characterDataOldValue: true,
};
this.observer = new MutationObserver((mutationList, observer) => {
this.mutationHandler(mutationList, observer);
});
this.observer.observe(UI.nodes.redactor, observerOptions);
}
/**
* MutationObserver events handler
* @param mutationList
* @param observer
*/
private mutationHandler(mutationList, observer) {
/**
* Skip mutations in stealth mode
*/
if (this.disabled) {
return;
}
/**
* We divide two Mutation types:
* 1) mutations that concerns client changes: settings changes, symbol added, deletion, insertions and so on
* 2) functional changes. On each client actions we set functional identifiers to interact with user
*/
let contentMutated = false;
mutationList.forEach((mutation) => {
switch (mutation.type) {
case 'childList':
case 'subtree':
case 'characterData':
case 'characterDataOldValue':
contentMutated = true;
break;
case 'attributes':
const mutatedTarget = mutation.target as Element;
/**
* Changes on Element.ce-block usually is functional
*/
if (!mutatedTarget.classList.contains(Block.CSS.wrapper)) {
contentMutated = true;
return;
}
break;
}
});
/** call once */
if (contentMutated) {
this.mutationDebouncer();
}
}
/**
* Gets native inputs and set oninput event handler
*/
private updateNativeInputs(): void {
if (this.nativeInputs) {
this.nativeInputs.forEach((input) => {
this.Editor.Listeners.off(input, 'input');
});
}
this.nativeInputs = Array.from(this.Editor.UI.nodes.redactor.querySelectorAll('textarea, input, select'));
this.nativeInputs.forEach((input) => this.Editor.Listeners.on(input, 'input', this.mutationDebouncer));
}
}