UNPKG

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