import LiveEditor from "./cell_editor/live_editor"; import Connection from "./cell_editor/live_editor/connection"; import { parseHookProps } from "../lib/attribute"; import { waitUntilInViewport } from "../lib/utils"; const CellEditor = { mounted() { this.props = this.getProps(); this.handleEvent( `cell_editor_init:${this.props.cellId}:${this.props.tag}`, ({ source, revision, doctest_reports, code_markers }) => { const editorContainer = this.el.querySelector( `[data-el-editor-container]`, ); const editorEl = document.createElement("div"); editorContainer.appendChild(editorEl); this.connection = new Connection( this, this.props.cellId, this.props.tag, ); this.liveEditor = new LiveEditor( editorEl, this.connection, source, revision, this.props.language, this.props.intellisense, this.props.readOnly, ); this.liveEditor.setCodeMarkers(code_markers); this.liveEditor.updateDoctests(doctest_reports); const skeletonEl = editorContainer.querySelector(`[data-el-skeleton]`); // Replace the skeleton with initial source, so that the // whole page is still searchable, before the editors are // lazily mounted. This also ensures the scroll has an // accurate length right away. const sourceEl = document.createElement("div"); sourceEl.classList.add( "whitespace-pre", "overflow-x-auto", "text-editor", "font-editor", "px-12", ); sourceEl.textContent = source; skeletonEl.replaceChildren(sourceEl); this.liveEditor.onMount(() => { // Remove the content placeholder skeletonEl.remove(); }); this.el.dispatchEvent( new CustomEvent("lb:cell:editor_created", { detail: { tag: this.props.tag, liveEditor: this.liveEditor }, bubbles: true, }), ); this.visibility = waitUntilInViewport(this.el, { root: document.querySelector("[data-el-notebook]"), proximity: 2000, }); // We mount the editor lazily once it enters the viewport this.visibility.promise.then(() => { if (!this.liveEditor.isMounted()) { this.liveEditor.mount(); } }); }, ); }, disconnected() { // When disconnected, this client is no longer seen by the server // and misses all collaborative changes. On reconnection we want // to clean up and mount a fresh hook, which we force by ensuring // the DOM id doesn't match this.el.removeAttribute("id"); }, destroyed() { if (this.connection) { this.connection.destroy(); } if (this.liveEditor) { this.el.dispatchEvent( new CustomEvent("lb:cell:editor_removed", { detail: { tag: this.props.tag }, bubbles: true, }), ); this.liveEditor.destroy(); } if (this.visibility) { this.visibility.cancel(); } }, getProps() { return parseHookProps(this.el, [ "cell-id", "tag", "language", "intellisense", "read-only", ]); }, }; export default CellEditor;