diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionComponent.js index 934ecd9636039044b82a07d47faa7b56cb16ad7a..0cb0e8b3fd5ce38b63da210ea55563e056a8d31c 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionComponent.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionComponent.js @@ -1,5 +1,7 @@ +/* eslint-disable react/prop-types */ import React from 'react'; +import QuestionEditorComponent from './QuestionEditorComponent'; export default ({ node, view, getPos }) => { - return <span>Question</span>; + return <QuestionEditorComponent getPos={getPos} node={node} view={view} />; }; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionEditorComponent.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionEditorComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..d19ad04d17746ed7b9a90b2db382b479205da5d1 --- /dev/null +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/components/QuestionEditorComponent.js @@ -0,0 +1,165 @@ +/* eslint-disable react/destructuring-assignment */ +/* eslint-disable react/prop-types */ + +import React, { useContext, useRef, useEffect } from 'react'; +import styled from 'styled-components'; +import { EditorView } from 'prosemirror-view'; +import { EditorState, TextSelection } from 'prosemirror-state'; +import { StepMap } from 'prosemirror-transform'; +import { keymap } from 'prosemirror-keymap'; +import { baseKeymap } from 'prosemirror-commands'; +import { undo, redo } from 'prosemirror-history'; +import { WaxContext } from 'wax-prosemirror-core'; +import Placeholder from '../plugins/placeholder'; + +const EditorWrapper = styled.div` + border: none; + display: flex; + flex: 2 1 auto; + justify-content: left; + + .ProseMirror { + white-space: break-spaces; + width: 100%; + word-wrap: break-word; + + &:focus { + outline: none; + } + + p.empty-node:first-child::before { + content: attr(data-content); + } + + .empty-node::before { + color: rgb(170, 170, 170); + float: left; + font-style: italic; + height: 0px; + pointer-events: none; + } + } +`; + +const QuestionEditorComponent = ({ node, view, getPos }) => { + const editorRef = useRef(); + + const context = useContext(WaxContext); + let questionView; + const questionId = node.attrs.id; + const isEditable = context.view.main.props.editable(editable => { + return editable; + }); + + let finalPlugins = []; + + const createKeyBindings = () => { + const keys = getKeys(); + Object.keys(baseKeymap).forEach(key => { + keys[key] = baseKeymap[key]; + }); + return keys; + }; + + const getKeys = () => { + return { + 'Mod-z': () => undo(view.state, view.dispatch), + 'Mod-y': () => redo(view.state, view.dispatch), + }; + }; + + const plugins = [keymap(createKeyBindings()), ...context.app.getPlugins()]; + + // eslint-disable-next-line no-shadow + const createPlaceholder = placeholder => { + return Placeholder({ + content: placeholder, + }); + }; + + finalPlugins = finalPlugins.concat([ + createPlaceholder('Type your question'), + ...plugins, + ]); + + useEffect(() => { + questionView = new EditorView( + { + mount: editorRef.current, + }, + { + editable: () => isEditable, + state: EditorState.create({ + doc: node, + plugins: finalPlugins, + }), + // This is the magic part + dispatchTransaction, + disallowedTools: ['MultipleChoice'], + handleDOMEvents: { + mousedown: () => { + context.view.main.dispatch( + context.view.main.state.tr.setSelection( + new TextSelection( + context.view.main.state.tr.doc.resolve(getPos() + 2), + ), + ), + ); + // context.view[activeViewId].dispatch( + // context.view[activeViewId].state.tr.setSelection( + // TextSelection.between( + // context.view[activeViewId].state.selection.$anchor, + // context.view[activeViewId].state.selection.$head, + // ), + // ), + // ); + context.updateView({}, questionId); + // 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 (questionView.hasFocus()) questionView.focus(); + }, + }, + + attributes: { + spellcheck: 'false', + }, + }, + ); + + // Set Each note into Wax's Context + context.updateView( + { + [questionId]: questionView, + }, + questionId, + ); + if (questionView.hasFocus()) questionView.focus(); + }, []); + + const dispatchTransaction = tr => { + const { state, transactions } = questionView.state.applyTransaction(tr); + questionView.updateState(state); + context.updateView({}, questionId); + + if (!tr.getMeta('fromOutside')) { + const outerTr = view.state.tr; + const offsetMap = StepMap.offset(getPos() + 1); + for (let i = 0; i < transactions.length; i++) { + const { steps } = transactions[i]; + for (let j = 0; j < steps.length; j++) + outerTr.step(steps[j].map(offsetMap)); + } + if (outerTr.docChanged) + view.dispatch(outerTr.setMeta('outsideView', questionId)); + } + }; + + return ( + <EditorWrapper> + <div ref={editorRef} /> + </EditorWrapper> + ); +}; + +export default QuestionEditorComponent; diff --git a/wax-prosemirror-services/src/MultipleChoiceQuestionService/helpers/helpers.js b/wax-prosemirror-services/src/MultipleChoiceQuestionService/helpers/helpers.js index 702247163e45c3324fb72b4737ca2b4fecffd09a..ad88b564fdda7f6512c2a93e6bb78cc5de77eff9 100644 --- a/wax-prosemirror-services/src/MultipleChoiceQuestionService/helpers/helpers.js +++ b/wax-prosemirror-services/src/MultipleChoiceQuestionService/helpers/helpers.js @@ -95,8 +95,9 @@ const createOptions = ( dispatch(tr); setTimeout(() => { - createEmptyParagraph(context, secondOption.attrs.id); createEmptyParagraph(context, firstOption.attrs.id); + createEmptyParagraph(context, secondOption.attrs.id); + createEmptyParagraph(context, question.attrs.id); }, 50); };