diff --git a/editors/demo/src/Editors.js b/editors/demo/src/Editors.js index fa52e35509b57dc1e5f1d06514566f8cc480bbe7..977ed5cc4dbf7c694157ede01a713d4b2f5b983e 100644 --- a/editors/demo/src/Editors.js +++ b/editors/demo/src/Editors.js @@ -54,7 +54,7 @@ const Editors = () => { case 'ncbi': break; default: - return <Editoria />; + return <HHMI />; } }; diff --git a/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestion.js b/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestion.js index 2fe6f664e94d82023b0d25fe193f0bf77e4da690..bff7df91482165966463247f86ccfdcbaa84f75d 100644 --- a/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestion.js +++ b/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestion.js @@ -1,6 +1,7 @@ import { injectable } from 'inversify'; import { Tools } from 'wax-prosemirror-services'; -import {Commands} from 'wax-prosemirror-utilities' +import { Fragment } from 'prosemirror-model'; +import { v4 as uuidv4 } from 'uuid'; @injectable() class MultipleChoiceQuestion extends Tools { @@ -9,9 +10,15 @@ class MultipleChoiceQuestion extends Tools { name = 'Multiple Choice'; get run() { - return (state, dispatch) => { - Commands.setBlockType(state.config.schema.nodes.multiple_choice)(state, dispatch); + const { empty, $from, $to } = state.selection; + const content = Fragment.empty; + + const footnote = state.config.schema.nodes.multiple_choice.create( + { id: uuidv4() }, + content, + ); + dispatch(state.tr.replaceSelectionWith(footnote)); }; } diff --git a/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js b/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js index b35c6765c8070615d462ceaf5e9fe06b08458ac3..af89bb4644953108a51ab98eb90b62d83d8e2ebe 100644 --- a/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js +++ b/editors/demo/src/HHMI/MultipleChoiceQuestionService/MultipleChoiceQuestionService.js @@ -9,9 +9,11 @@ class MultipleChoiceQuestionService extends Service { register() { this.container.bind('MultipleChoiceQuestion').to(MultipleChoiceQuestion); const createNode = this.container.get('CreateNode'); + createNode({ multiple_choice: multipleChoiceNode, }); + const addPortal = this.container.get('AddPortal'); addPortal({ name: 'multiple_choice', component: TestComponent }); diff --git a/editors/demo/src/HHMI/MultipleChoiceQuestionService/components/TestComponent.js b/editors/demo/src/HHMI/MultipleChoiceQuestionService/components/TestComponent.js index e8d820a895fcc2cec7cc3c505cd91e8cd68fd587..1e83da5cab3255ca32f800422952787545157c76 100644 --- a/editors/demo/src/HHMI/MultipleChoiceQuestionService/components/TestComponent.js +++ b/editors/demo/src/HHMI/MultipleChoiceQuestionService/components/TestComponent.js @@ -1,21 +1,97 @@ -import React from 'react'; - +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useLayoutEffect, useRef } from 'react'; +import { EditorState } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view'; +import { StepMap } from 'prosemirror-transform'; +import styled from 'styled-components'; const styles = { - backgroundColor: 'red', - width: '200px', - height: '200px', + border: '1px solid black', }; -export default () => { +export default ({ node, view, getPos }) => { + console.log(node); + const editorRef = useRef(); + let questionView; + useLayoutEffect(() => { + questionView = new EditorView( + { mount: editorRef.current }, + { + state: EditorState.create({ + doc: node, + // plugins: [keymap(createKeyBindings()), ...app.getPlugins()], + }), + dispatchTransaction, + handleDOMEvents: { + // blur: () => { + // view[noteId].dispatch( + // view[noteId].state.tr.setSelection( + // new TextSelection(view[noteId].state.tr.doc.resolve(0)), + // ), + // ); + // }, + + mousedown: () => { + // Kludge to prevent issues due to the fact that the whole + // footnote is node-selected (and thus DOM-selected) when + // the parent editor is focused. + + if (view.hasFocus()) questionView.focus(); + }, + }, + + attributes: { + spellcheck: 'false', + }, + }, + ); + }, []); + + const dispatchTransaction = tr => { + console.log('dispatch', questionView.state.applyTransaction(tr)); + let { state, transactions } = questionView.state.applyTransaction(tr); + questionView.updateState(state); + + if (!tr.getMeta('fromOutside')) { + let outerTr = view.state.tr, + offsetMap = StepMap.offset(getPos() + 1); + for (let i = 0; i < transactions.length; i++) { + let steps = transactions[i].steps; + for (let j = 0; j < steps.length; j++) + outerTr.step(steps[j].map(offsetMap)); + } + if (outerTr.docChanged) view.dispatch(outerTr); + } + }; + console.log(questionView); + if (questionView) { + const { state } = questionView; + const start = node.content.findDiffStart(state.doc.content); + if (start != null) { + let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content); + const overlap = start - Math.min(endA, endB); + if (overlap > 0) { + endA += overlap; + endB += overlap; + } + questionView.dispatch( + state.tr + .replace(start, endB, node.slice(start, endA)) + .setMeta('fromOutside', true), + ); + } + } - // useEffect(() => { - // const editorViewDOM = editorViewRef.current; - // if (editorViewDOM) { - // createEditorView(editorViewDOM); - // } - // }, [createEditorView]); + const clickMe = () => { + console.log(node.attrs); + view.dispatch(view.state.tr); + }; - return <div style={styles}></div>; + return ( + <> + <div ref={editorRef} style={styles} /> + <button onClick={clickMe}>Click me</button> + </> + ); }; diff --git a/editors/demo/src/HHMI/MultipleChoiceQuestionService/schema/multipleChoiceNode.js b/editors/demo/src/HHMI/MultipleChoiceQuestionService/schema/multipleChoiceNode.js index aa55a46283f7a9cd2db8607a772ff910d3412c2c..17f8f80eb37bb1a9f572de382c37f006021ccb11 100644 --- a/editors/demo/src/HHMI/MultipleChoiceQuestionService/schema/multipleChoiceNode.js +++ b/editors/demo/src/HHMI/MultipleChoiceQuestionService/schema/multipleChoiceNode.js @@ -1,8 +1,11 @@ const multipleChoiceNode = { - group: 'block multiple', - content: 'text*', - atom: true, - code: true, + group: 'inline', + content: 'inline*', + inline: true, + // atom: true, + attrs: { + id: { default: '' }, + }, toDOM: () => ['multiple-choice', { class: 'multiple-choice' }, 0], parseDOM: [ { @@ -11,6 +14,19 @@ const multipleChoiceNode = { ], }; +// const multipleChoiceNode = { +// group: 'block multiple', +// content: 'text*', +// atom: true, +// code: true, +// toDOM: () => ['multiple-choice', { class: 'multiple-choice' }, 0], +// parseDOM: [ +// { +// tag: 'multiple-choice', +// }, +// ], +// }; + // const multipleChoiceNode = { // content: 'block+', // group: 'block', diff --git a/wax-prosemirror-services/src/PortalService/components/PortalComponent.js b/wax-prosemirror-services/src/PortalService/components/PortalComponent.js index 0ed215313620ec7942b7199fa06c2e2709f2d47f..a27c133f4026fbf275dc6fd764df40ca8e13f891 100644 --- a/wax-prosemirror-services/src/PortalService/components/PortalComponent.js +++ b/wax-prosemirror-services/src/PortalService/components/PortalComponent.js @@ -23,7 +23,16 @@ export default () => { <> {portals.length > 0 && portals.map(({ dom, component: Component }) => { - return ReactDOM.createPortal(<Component />, dom, uuidv4()); + return ReactDOM.createPortal( + <Component + node={node} + view={view} + getPos={getPos} + decorations={decorations} + />, + dom, + uuidv4(), + ); })} </> );