import { Blot } from './abstract/blot'; import ContainerBlot from './abstract/container'; import LinkedList from '../collection/linked-list'; import * as Registry from '../registry'; const OBSERVER_CONFIG = { attributes: true, characterData: true, characterDataOldValue: true, childList: true, subtree: true, }; const MAX_OPTIMIZE_ITERATIONS = 100; class ScrollBlot extends ContainerBlot { static blotName = 'scroll'; static defaultChild = 'block'; static scope = Registry.Scope.BLOCK_BLOT; static tagName = 'DIV'; observer: MutationObserver; constructor(node: HTMLDivElement) { super(node); this.scroll = this; this.observer = new MutationObserver((mutations: MutationRecord[]) => { this.update(mutations); }); this.observer.observe(this.domNode, OBSERVER_CONFIG); this.attach(); } detach() { super.detach(); this.observer.disconnect(); } deleteAt(index: number, length: number): void { this.update(); if (index === 0 && length === this.length()) { this.children.forEach(function(child) { child.remove(); }); } else { super.deleteAt(index, length); } } formatAt(index: number, length: number, name: string, value: any): void { this.update(); super.formatAt(index, length, name, value); } insertAt(index: number, value: string, def?: any): void { this.update(); super.insertAt(index, value, def); } optimize(context: { [key: string]: any }): void; optimize(mutations: MutationRecord[], context: { [key: string]: any }): void; optimize(mutations: any = [], context: any = {}): void { super.optimize(context); // We must modify mutations directly, cannot make copy and then modify let records = [].slice.call(this.observer.takeRecords()); // Array.push currently seems to be implemented by a non-tail recursive function // so we cannot just mutations.push.apply(mutations, this.observer.takeRecords()); while (records.length > 0) mutations.push(records.pop()); // TODO use WeakMap let mark = (blot: Blot | null, markParent: boolean = true) => { if (blot == null || blot === this) return; if (blot.domNode.parentNode == null) return; // @ts-ignore if (blot.domNode[Registry.DATA_KEY].mutations == null) { // @ts-ignore blot.domNode[Registry.DATA_KEY].mutations = []; } if (markParent) mark(blot.parent); }; let optimize = function(blot: Blot) { // Post-order traversal if ( // @ts-ignore blot.domNode[Registry.DATA_KEY] == null || // @ts-ignore blot.domNode[Registry.DATA_KEY].mutations == null ) { return; } if (blot instanceof ContainerBlot) { blot.children.forEach(optimize); } blot.optimize(context); }; let remaining = mutations; for (let i = 0; remaining.length > 0; i += 1) { if (i >= MAX_OPTIMIZE_ITERATIONS) { throw new Error('[Parchment] Maximum optimize iterations reached'); } remaining.forEach(function(mutation: MutationRecord) { let blot = Registry.find(mutation.target, true); if (blot == null) return; if (blot.domNode === mutation.target) { if (mutation.type === 'childList') { mark(Registry.find(mutation.previousSibling, false)); [].forEach.call(mutation.addedNodes, function(node: Node) { let child = Registry.find(node, false); mark(child, false); if (child instanceof ContainerBlot) { child.children.forEach(function(grandChild: Blot) { mark(grandChild, false); }); } }); } else if (mutation.type === 'attributes') { mark(blot.prev); } } mark(blot); }); this.children.forEach(optimize); remaining = [].slice.call(this.observer.takeRecords()); records = remaining.slice(); while (records.length > 0) mutations.push(records.pop()); } } update(mutations?: MutationRecord[], context: { [key: string]: any } = {}): void { mutations = mutations || this.observer.takeRecords(); // TODO use WeakMap mutations .map(function(mutation: MutationRecord) { let blot = Registry.find(mutation.target, true); if (blot == null) return null; // @ts-ignore if (blot.domNode[Registry.DATA_KEY].mutations == null) { // @ts-ignore blot.domNode[Registry.DATA_KEY].mutations = [mutation]; return blot; } else { // @ts-ignore blot.domNode[Registry.DATA_KEY].mutations.push(mutation); return null; } }) .forEach((blot: Blot | null) => { if ( blot == null || blot === this || //@ts-ignore blot.domNode[Registry.DATA_KEY] == null ) return; // @ts-ignore blot.update(blot.domNode[Registry.DATA_KEY].mutations || [], context); }); // @ts-ignore if (this.domNode[Registry.DATA_KEY].mutations != null) { // @ts-ignore super.update(this.domNode[Registry.DATA_KEY].mutations, context); } this.optimize(mutations, context); } } export default ScrollBlot;