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;
|