@textbus/adapter-viewfly
Version:
Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.
175 lines (171 loc) • 6.83 kB
JavaScript
import { makeError, VElement, VTextNode, merge, Adapter } from '@textbus/core';
import { createDynamicRef, DynamicRef, jsx, getCurrentInstance, onUnmounted, onUpdated, ReflectiveInjector } from '@viewfly/core';
import { DomAdapter } from '@textbus/platform-browser';
import { NodeViewAdapter } from '@textbus/platform-node';
const adapterError = makeError('ViewflyDOMRenderer');
class ViewflyAdapter extends DomAdapter {
constructor(components, mount) {
super({
createCompositionNode(compositionState, updateNativeCompositionNode) {
const ref = createDynamicRef(node => {
updateNativeCompositionNode(node);
return () => {
updateNativeCompositionNode(null);
};
});
return new VElement('span', {
style: {
textDecoration: 'underline'
},
ref
}, [
new VTextNode(compositionState.text)
]);
},
getParentNode(node) {
return node.parentNode;
},
getChildNodes(parentElement) {
return Array.from(parentElement.childNodes);
},
isNativeElementNode(node) {
return node instanceof Element;
},
getChildByIndex(parentElement, index) {
return parentElement.childNodes[index];
},
getAndUpdateSlotRootNativeElement(vEle, update) {
const currentRef = vEle.attrs.get('ref');
const ref = createDynamicRef(nativeNode => {
update(nativeNode);
return () => {
update(null);
};
});
if (currentRef instanceof DynamicRef) {
vEle.attrs.set('ref', [currentRef, ref]);
}
else if (Array.isArray(currentRef)) {
currentRef.push(ref);
}
else {
vEle.attrs.set('ref', ref);
}
},
componentRender: (component) => {
const comp = this.components[component.name] || this.components['*'];
if (comp) {
let ref = this.componentRefs.get(component);
if (!ref) {
ref = createDynamicRef(rootNode => {
this.componentRootElementCaches.set(component, rootNode);
return () => {
this.componentRootElementCaches.remove(component);
};
});
this.componentRefs.set(component, ref);
}
return jsx(comp, {
component,
rootRef: ref
}, component.id);
}
throw adapterError(`cannot found view component \`${component.name}\`!`);
},
vElementToViewElement(vNode, children) {
const key = vNode.attrs.get('key');
vNode.attrs.delete('key');
const props = Object.assign({}, (Array.from(vNode.attrs).reduce((a, b) => {
a[b[0]] = b[1];
return a;
}, {})));
if (vNode.classes.size) {
props.class = Array.from(vNode.classes).join(' ');
}
if (vNode.styles) {
props.style = Array.from(vNode.styles).reduce((a, b) => {
a[b[0]] = b[1];
return a;
}, {});
}
if (children.length) {
props.children = children;
}
return jsx(vNode.tagName, props, key);
}
}, mount);
Object.defineProperty(this, "components", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "componentRefs", {
enumerable: true,
configurable: true,
writable: true,
value: new WeakMap()
});
let isRoot = true;
Object.entries(components).forEach(([key, viewFlyComponent]) => {
this.components[key] = (props) => {
const comp = getCurrentInstance();
const textbusComponent = props.component;
const subscription = merge(textbusComponent.changeMarker.onChange, textbusComponent.changeMarker.onForceChange).subscribe(() => {
if (textbusComponent.changeMarker.dirty) {
comp.markAsDirtied();
}
});
onUnmounted(() => {
subscription.unsubscribe();
});
if (isRoot) {
onUpdated(() => {
this.onViewUpdated.next();
});
isRoot = false;
}
onUpdated(() => {
textbusComponent.changeMarker.rendered();
if (!this.componentRootElementCaches.get(textbusComponent)) {
// eslint-disable-next-line max-len
throw adapterError(`Component \`${textbusComponent.name}\` is not bound to rootRef, you must bind rootRef to the root element node of the component view.`);
}
});
return viewFlyComponent(props);
};
});
}
render(rootComponent, injector) {
const childInjector = new ReflectiveInjector(injector, [{
provide: Adapter,
useValue: this
}, {
provide: DomAdapter,
useValue: this
}, {
provide: ViewflyAdapter,
useValue: this
}]);
return super.render(rootComponent, childInjector);
}
copy() {
document.execCommand('copy');
}
}
class ViewflyVDomAdapter extends NodeViewAdapter {
render(rootComponent, injector) {
const childInjector = new ReflectiveInjector(injector, [{
provide: Adapter,
useValue: this
}, {
provide: DomAdapter,
useValue: this
}, {
provide: ViewflyAdapter,
useValue: this
}]);
return super.render(rootComponent, childInjector);
}
}
export { ViewflyAdapter, ViewflyVDomAdapter };