diff --git a/editors/demo/src/Editors.js b/editors/demo/src/Editors.js index 166168897656c1d73e46585d9f7a08e372190219..1e839e3d33a7e73e494ec5a4903e8c7fa669e16d 100644 --- a/editors/demo/src/Editors.js +++ b/editors/demo/src/Editors.js @@ -70,7 +70,7 @@ const Editors = () => { case 'ncbi': return <NCBI />; default: - return <Editoria />; + return <HHMI />; } }; diff --git a/editors/demo/src/HHMI/config/config.js b/editors/demo/src/HHMI/config/config.js index be6033507a0f5b5b28347f39eb86dc6a785321f2..3257bebfda40a35db65aba489996d0df6fb12bd1 100644 --- a/editors/demo/src/HHMI/config/config.js +++ b/editors/demo/src/HHMI/config/config.js @@ -27,6 +27,8 @@ import { MultipleDropDownToolGroupService, EssayService, EssayToolGroupService, + MatchingService, + MatchingToolGroupService, } from 'wax-prosemirror-services'; import { DefaultSchema } from 'wax-prosemirror-utilities'; @@ -55,6 +57,7 @@ export default { 'MultipleDropDown', 'Essay', 'FillTheGap', + 'Matching', 'FullScreen', ], }, @@ -66,6 +69,8 @@ export default { PmPlugins: [columnResizing(), tableEditing(), invisibles([hardBreak()])], services: [ + new MatchingService(), + new MatchingToolGroupService(), new FillTheGapQuestionService(), new FillTheGapToolGroupService(), new MultipleChoiceQuestionService(), diff --git a/editors/demo/src/HHMI/layout/HhmiLayout.js b/editors/demo/src/HHMI/layout/HhmiLayout.js index 6f57c434ce99daabb85d4517f1280bf16458d19d..436f33949df23ec2cbe9b55cba96a3a3d1556533 100644 --- a/editors/demo/src/HHMI/layout/HhmiLayout.js +++ b/editors/demo/src/HHMI/layout/HhmiLayout.js @@ -48,7 +48,7 @@ const TopMenu = styled.div` margin-right: ${grid(5)}; } - > div[data-name='FillTheGap'] { + > div[data-name='Matching'] { border-right: none; } `; diff --git a/wax-prosemirror-core/src/Application.js b/wax-prosemirror-core/src/Application.js index 63b621a865b604a833a1f1011274ef7362b4e7ce..83a66f709d420530b5a84d6b9d298022d5c800ab 100644 --- a/wax-prosemirror-core/src/Application.js +++ b/wax-prosemirror-core/src/Application.js @@ -61,11 +61,17 @@ export default class Application { return this.schema.getSchema(); } + getShortCuts() { + this.shortCuts = this.container.get('ShortCuts'); + this.PmPlugins.add('shortcuts', this.shortCuts.createShortCuts()); + } + resetApp() { this.container = {}; this.config = {}; this.PmPlugins = {}; this.schema = {}; + this.shortCuts = {}; } static create(config) { diff --git a/wax-prosemirror-core/src/WaxView.js b/wax-prosemirror-core/src/WaxView.js index ed62326688b5c55b68d5800ada9ba0ed9a4cd6b7..26b332b0d7c175a8e83ef50eabf3e6a11df4f885 100644 --- a/wax-prosemirror-core/src/WaxView.js +++ b/wax-prosemirror-core/src/WaxView.js @@ -56,6 +56,7 @@ const WaxView = forwardRef((props, ref) => { if (!mounted) { context.app.bootServices(); + context.app.getShortCuts(); } const setEditorRef = useCallback( diff --git a/wax-prosemirror-services/src/InlineAnnotations/CodeService/CodeService.js b/wax-prosemirror-services/src/InlineAnnotations/CodeService/CodeService.js index f9be7a4d68edc30e31fc54e77fc72d345cd9159d..b17597f62f627a4e4e601dc64f6914de2fa7e301 100644 --- a/wax-prosemirror-services/src/InlineAnnotations/CodeService/CodeService.js +++ b/wax-prosemirror-services/src/InlineAnnotations/CodeService/CodeService.js @@ -4,13 +4,10 @@ import Code from './Code'; import Service from '../../Service'; class CodeService extends Service { - boot() { - const shortCuts = this.container.get('ShortCuts'); - shortCuts.addShortCut({ 'Mod-`': toggleMark(this.schema.marks.code) }); - } - register() { this.container.bind('Code').to(Code); + const CreateShortCut = this.container.get('CreateShortCut'); + const createMark = this.container.get('CreateMark'); createMark( { @@ -18,6 +15,10 @@ class CodeService extends Service { }, { toWaxSchema: true }, ); + + CreateShortCut({ + 'Mod-`': toggleMark(this.schema.marks.code), + }); } } diff --git a/wax-prosemirror-services/src/InlineAnnotations/EmphasisService/EmphasisService.js b/wax-prosemirror-services/src/InlineAnnotations/EmphasisService/EmphasisService.js index 859babb1069f24d8a3f8ccd195bf528234e57b7f..322d8c84610bb19841927e12094b551068c6ce37 100644 --- a/wax-prosemirror-services/src/InlineAnnotations/EmphasisService/EmphasisService.js +++ b/wax-prosemirror-services/src/InlineAnnotations/EmphasisService/EmphasisService.js @@ -4,20 +4,21 @@ import Emphasis from './Emphasis'; import Service from '../../Service'; class EmphasisService extends Service { - boot() { - const shortCuts = this.container.get('ShortCuts'); - shortCuts.addShortCut({ 'Mod-i': toggleMark(this.schema.marks.em) }); - } - register() { this.container.bind('Emphasis').to(Emphasis); const createMark = this.container.get('CreateMark'); + const CreateShortCut = this.container.get('CreateShortCut'); + createMark( { em: emphasisMark, }, { toWaxSchema: true }, ); + + CreateShortCut({ + 'Mod-i': toggleMark(this.schema.marks.em), + }); } } diff --git a/wax-prosemirror-services/src/InlineAnnotations/StrongService/StrongService.js b/wax-prosemirror-services/src/InlineAnnotations/StrongService/StrongService.js index 28d863b08fa5bd44c8050c74927412b2e5e143f6..777be0c18dcc54a996cc4765a5634f1a2726ac7d 100644 --- a/wax-prosemirror-services/src/InlineAnnotations/StrongService/StrongService.js +++ b/wax-prosemirror-services/src/InlineAnnotations/StrongService/StrongService.js @@ -5,17 +5,18 @@ import Strong from './Strong'; import './strong.css'; class StrongService extends Service { - boot() { - const shortCuts = this.container.get('ShortCuts'); - shortCuts.addShortCut({ 'Mod-b': toggleMark(this.schema.marks.strong) }); - } - register() { this.container.bind('Strong').to(Strong); const createMark = this.container.get('CreateMark'); + const CreateShortCut = this.container.get('CreateShortCut'); + createMark({ strong: strongMark, }); + + CreateShortCut({ + 'Mod-b': toggleMark(this.schema.marks.strong), + }); } } diff --git a/wax-prosemirror-services/src/InlineAnnotations/UnderlineService/UnderlineService.js b/wax-prosemirror-services/src/InlineAnnotations/UnderlineService/UnderlineService.js index 422b763edb2f08853f9b1a98eaa1c24453008c42..b6cec3a72374fec38a4b0fa313f67d9e3ef7a817 100644 --- a/wax-prosemirror-services/src/InlineAnnotations/UnderlineService/UnderlineService.js +++ b/wax-prosemirror-services/src/InlineAnnotations/UnderlineService/UnderlineService.js @@ -4,13 +4,9 @@ import Underline from './Underline'; import Service from '../../Service'; class UnderlineService extends Service { - boot() { - const shortCuts = this.container.get('ShortCuts'); - shortCuts.addShortCut({ 'Mod-u': toggleMark(this.schema.marks.underline) }); - } - register() { this.container.bind('Underline').to(Underline); + const CreateShortCut = this.container.get('CreateShortCut'); const createMark = this.container.get('CreateMark'); createMark( { @@ -18,6 +14,9 @@ class UnderlineService extends Service { }, { toWaxSchema: true }, ); + CreateShortCut({ + 'Mod-u': toggleMark(this.schema.marks.underline), + }); } } diff --git a/wax-prosemirror-services/src/LinkService/LinkInputRule.js b/wax-prosemirror-services/src/LinkService/LinkInputRule.js new file mode 100644 index 0000000000000000000000000000000000000000..b549f3cddbea4213902e92c7efdf20cba85cb0a2 --- /dev/null +++ b/wax-prosemirror-services/src/LinkService/LinkInputRule.js @@ -0,0 +1,36 @@ +import { InputRule } from 'prosemirror-inputrules'; + +const linkRule = markType => { + return MarkInputRule( + /(?:(?:(https|http|ftp)+):\/\/)?(?:\S+(?::\S*)?(@))?(?:(?:([a-z0-9][a-z0-9\-]*)?[a-z0-9]+)(?:\.(?:[a-z0-9\-])*[a-z0-9]+)*(?:\.(?:[a-z]{2,})(:\d{1,5})?))(?:\/[^\s]*)?\s$/i, + markType, + match => ({ type: match[2] === '@' ? 'email' : 'uri' }), + ); +}; + +const MarkInputRule = (regexp, markType, getAttrs) => { + return new InputRule(regexp, (state, match, start, end) => { + const $start = state.doc.resolve(start); + const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs; + if (!$start.parent.type.allowsMarkType(markType)) return null; + const linkString = match[0].substring(0, match[0].length - 1); + + const linkAttrs = + attrs.type === 'email' + ? { href: `mailto:${linkString}` } + : { href: linkString, target: '_blank' }; + const oLink = markType.create(linkAttrs); + + const tr = state.tr + .addMark(start, end, oLink) + .insertText(' ', start + linkString.length); + // .removeMark( + // start + linkString.length, + // start + linkString.length, + // markType, + // ); + return tr; + }); +}; + +export default linkRule; diff --git a/wax-prosemirror-services/src/ListsService/BulletListService/BulletListService.js b/wax-prosemirror-services/src/ListsService/BulletListService/BulletListService.js index be0192010ee5415109ea4661eed55c7f2157ab02..24565693018a68179b36f3bb603b345aed962b69 100644 --- a/wax-prosemirror-services/src/ListsService/BulletListService/BulletListService.js +++ b/wax-prosemirror-services/src/ListsService/BulletListService/BulletListService.js @@ -1,28 +1,30 @@ -// import { wrapInList } from 'prosemirror-schema-list'; import { bulletListNode } from 'wax-prosemirror-schema'; import Service from '../../Service'; import BulletList from './BulletList'; class BulletListService extends Service { name = 'BulletListService'; - boot() { - const shortCuts = this.container.get('ShortCuts'); - // shortCuts.addShortCut({ - // "Shift-Ctrl-8": wrapInList(this.schema.nodes.bulletlist) - // }); - } register() { + const CreateShortCut = this.container.get('CreateShortCut'); + const createNode = this.container.get('CreateNode'); + this.container.bind('BulletList').toDynamicValue(() => { return new BulletList(this.config); }); - const createNode = this.container.get('CreateNode'); + createNode( { bulletlist: bulletListNode, }, { toWaxSchema: true }, ); + + CreateShortCut({ + 'Shift-Ctrl-8': (state, dispatch) => { + this.container.get('BulletList').run(state, dispatch); + }, + }); } } diff --git a/wax-prosemirror-services/src/ListsService/LiftService/LiftService.js b/wax-prosemirror-services/src/ListsService/LiftService/LiftService.js index a074a2f82b4786130ec6e651e38323ad6ac17c6e..f877e1123a8b60bd642e6f3ad72f11196aacc669 100644 --- a/wax-prosemirror-services/src/ListsService/LiftService/LiftService.js +++ b/wax-prosemirror-services/src/ListsService/LiftService/LiftService.js @@ -1,18 +1,19 @@ -import Service from "../../Service"; -import { liftListItem, sinkListItem } from "prosemirror-schema-list"; -import Lift from "./Lift"; +import { liftListItem, sinkListItem } from 'prosemirror-schema-list'; +import Service from '../../Service'; +import Lift from './Lift'; class LiftService extends Service { - boot() { - const shortCuts = this.container.get("ShortCuts"); - // shortCuts.addShortCut({ - // "Mod-[": liftListItem(this.schema.nodes.list_item), - // "Mod-]": sinkListItem(this.schema.nodes.list_item) - // }); - } - register() { - this.container.bind("Lift").to(Lift); + this.container.bind('Lift').to(Lift); + const CreateShortCut = this.container.get('CreateShortCut'); + CreateShortCut({ + 'Mod-[': (state, dispatch) => + liftListItem(state.schema.nodes.list_item)(state, dispatch), + }); + CreateShortCut({ + 'Mod-]': (state, dispatch) => + sinkListItem(state.schema.nodes.list_item)(state, dispatch), + }); } } diff --git a/wax-prosemirror-services/src/ListsService/OrderedListService/OrderedListService.js b/wax-prosemirror-services/src/ListsService/OrderedListService/OrderedListService.js index 9c73e4410eb9fcd45dfb4b25b56e21fcbb6eb6d5..0919a743d8fc7b3bb4303b96c5d736474ab89bd8 100644 --- a/wax-prosemirror-services/src/ListsService/OrderedListService/OrderedListService.js +++ b/wax-prosemirror-services/src/ListsService/OrderedListService/OrderedListService.js @@ -1,18 +1,12 @@ -import Service from '../../Service'; -import { wrapInList } from 'prosemirror-schema-list'; import { orderedListNode } from 'wax-prosemirror-schema'; +import Service from '../../Service'; import OrderedList from './OrderedList'; class OrderedListService extends Service { name = 'OrderedListService'; - boot() { - const shortCuts = this.container.get('ShortCuts'); - // shortCuts.addShortCut({ - // "Shift-Ctrl-9": wrapInList(this.schema.nodes.orderedlist) - // }); - } register() { + const CreateShortCut = this.container.get('CreateShortCut'); this.container.bind('OrderedList').toDynamicValue(() => { return new OrderedList(this.config); }); @@ -23,6 +17,11 @@ class OrderedListService extends Service { }, { toWaxSchema: true }, ); + CreateShortCut({ + 'Shift-Ctrl-9': (state, dispatch) => { + this.container.get('OrderedList').run(state, dispatch); + }, + }); } } diff --git a/wax-prosemirror-services/src/ListsService/index.js b/wax-prosemirror-services/src/ListsService/index.js index c35db0480d8863f3150c3da09779b4409c43c0b8..34e8c3b5d5b3cec8a47033d9fe4f7800e5582eb0 100644 --- a/wax-prosemirror-services/src/ListsService/index.js +++ b/wax-prosemirror-services/src/ListsService/index.js @@ -5,9 +5,9 @@ import LiftService from './LiftService/LiftService'; import ListItemService from './ListItemService/ListItemService'; export default [ + new ListItemService(), new BulletListService(), new OrderedListService(), new JoinUpService(), new LiftService(), - new ListItemService(), ]; diff --git a/wax-prosemirror-services/src/MatchingService/MatchingContainerNodeView.js b/wax-prosemirror-services/src/MatchingService/MatchingContainerNodeView.js new file mode 100644 index 0000000000000000000000000000000000000000..387c6ccc37b88a493197affc95d3f51e056a3ab5 --- /dev/null +++ b/wax-prosemirror-services/src/MatchingService/MatchingContainerNodeView.js @@ -0,0 +1,32 @@ +import QuestionsNodeView from '../lib/helpers/QuestionsNodeView'; + +export default class MatchingContainerNodeView extends QuestionsNodeView { + constructor( + node, + view, + getPos, + decorations, + createPortal, + Component, + context, + ) { + super(node, view, getPos, decorations, createPortal, Component, context); + + this.node = node; + this.outerView = view; + this.getPos = getPos; + this.context = context; + } + + static name() { + return 'matching_container'; + } + + stopEvent(event) { + if (event.target.type === 'text') { + return true; + } + const innerView = this.context.pmViews[this.node.attrs.id]; + return innerView && innerView.dom.contains(event.target); + } +} diff --git a/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js b/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js index a5f68685ba299e388db925615e5319718b05b5c3..cb84528fa5f96a8f2e32d3d47d5765940829ea60 100644 --- a/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js +++ b/wax-prosemirror-services/src/MatchingService/MatchingQuestion.js @@ -1,17 +1,35 @@ import { injectable } from 'inversify'; +import { wrapIn } from 'prosemirror-commands'; +import { v4 as uuidv4 } from 'uuid'; import Tools from '../lib/Tools'; @injectable() class MatchingQuestion extends Tools { - title = 'Change to Block Quote'; - label = 'Block Quote'; - name = 'BlockQuote'; + title = 'Add Matching'; + label = 'Matching'; + name = 'Matching'; get run() { - return (state, dispatch) => {}; + return (state, dispatch) => { + wrapIn(state.config.schema.nodes.matching_container, { + id: uuidv4(), + })(state, dispatch); + }; } - select = (state, activeViewId) => {}; + select = (state, activeViewId, activeView) => { + const { disallowedTools } = activeView.props; + let status = true; + const { from, to } = state.selection; + if (from === null || disallowedTools.includes('Matching')) return false; + + state.doc.nodesBetween(from, to, (node, pos) => { + if (node.type.groups.includes('questions')) { + status = false; + } + }); + return status; + }; get active() { return state => {}; diff --git a/wax-prosemirror-services/src/MatchingService/MatchingService.js b/wax-prosemirror-services/src/MatchingService/MatchingService.js index 2106442c45d1c7156920094d5e199100b973495c..c95696a11b9f41ef9c8726ba96fe461f7fbe91b5 100644 --- a/wax-prosemirror-services/src/MatchingService/MatchingService.js +++ b/wax-prosemirror-services/src/MatchingService/MatchingService.js @@ -1,13 +1,26 @@ import Service from '../Service'; import MatchingQuestion from './MatchingQuestion'; +import matchingContainerNode from './schema/matchingContainerNode'; +import MatchingContainerNodeView from './MatchingContainerNodeView'; +import MatchingContainerComponent from './components/MatchingContainerComponent'; class MatchingService extends Service { name = 'MatchingService'; - boot() {} - register() { this.container.bind('MatchingQuestion').to(MatchingQuestion); + const createNode = this.container.get('CreateNode'); + const addPortal = this.container.get('AddPortal'); + + createNode({ + matching_container: matchingContainerNode, + }); + + addPortal({ + nodeView: MatchingContainerNodeView, + component: MatchingContainerComponent, + context: this.app, + }); } } diff --git a/wax-prosemirror-services/src/MatchingService/components/FeedbackComponent.js b/wax-prosemirror-services/src/MatchingService/components/FeedbackComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..0afd56b88d70dcfe21cf7faabbe0af016fef4375 --- /dev/null +++ b/wax-prosemirror-services/src/MatchingService/components/FeedbackComponent.js @@ -0,0 +1,111 @@ +/* eslint-disable react/prop-types */ + +import React, { useContext, useRef, useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { TextSelection } from 'prosemirror-state'; +import { WaxContext } from 'wax-prosemirror-core'; +import { DocumentHelpers } from 'wax-prosemirror-utilities'; + +const FeedBack = styled.div` + color: black; + margin-top: 10px; + padding: 10px; +`; + +const FeedBackLabel = styled.span` + font-weight: 700; +`; + +const FeedBackInput = styled.input` + border: none; + border-bottom: 1px solid black; + display: flex; + width: 100%; + + &:focus { + outline: none; + } + + ::placeholder { + color: rgb(170, 170, 170); + font-style: italic; + } +`; + +export default ({ node, view, getPos, readOnly }) => { + const context = useContext(WaxContext); + const { + pmViews: { main }, + } = context; + const [feedBack, setFeedBack] = useState(' '); + const [isFirstRun, setFirstRun] = useState(true); + const [typing, setTyping] = useState(false); + const feedBackRef = useRef(null); + + useEffect(() => { + const allNodes = getNodes(main); + allNodes.forEach(singleNode => { + if (singleNode.node.attrs.id === node.attrs.id) { + if (!typing || context.transaction.meta.inputType === 'Redo') { + setFeedBack(singleNode.node.attrs.feedback); + } + if (!isFirstRun) { + if (singleNode.node.attrs.feedback === '') + setFeedBack(singleNode.node.attrs.feedback); + } + } + }); + }, [getNodes(main)]); + + const handleKeyDown = e => { + setTyping(true); + if (e.key === 'Backspace') { + main.dispatch( + main.state.tr.setSelection( + TextSelection.create(main.state.tr.doc, null), + ), + ); + } + }; + + const feedBackInput = () => { + setFeedBack(feedBackRef.current.value); + }; + + const saveFeedBack = () => { + return false; + }; + + const onFocus = () => { + main.dispatch( + main.state.tr.setSelection(TextSelection.create(main.state.tr.doc, null)), + ); + }; + + return ( + <FeedBack> + <FeedBackLabel>Feedback</FeedBackLabel> + <FeedBackInput + disabled={readOnly} + onBlur={saveFeedBack} + onChange={feedBackInput} + onFocus={onFocus} + onKeyDown={handleKeyDown} + placeholder="Insert feedback" + ref={feedBackRef} + type="text" + value={feedBack} + /> + </FeedBack> + ); +}; + +const getNodes = view => { + const allNodes = DocumentHelpers.findBlockNodes(view.state.doc); + const fillTheGapNodes = []; + allNodes.forEach(node => { + if (node.node.type.name === 'fill_the_gap_container') + fillTheGapNodes.push(node); + }); + return fillTheGapNodes; +}; diff --git a/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js b/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js new file mode 100644 index 0000000000000000000000000000000000000000..2099fe9a652ec189c378f5649db4b6a2f0293827 --- /dev/null +++ b/wax-prosemirror-services/src/MatchingService/components/MatchingContainerComponent.js @@ -0,0 +1,48 @@ +/* eslint-disable react/prop-types */ +import React, { useContext } from 'react'; +import { WaxContext } from 'wax-prosemirror-core'; +import styled from 'styled-components'; +import FeedbackComponent from './FeedbackComponent'; + +const MatchingContainer = styled.div` + border: 3px solid #f5f5f7; + margin-bottom: 30px; +`; + +const MatchingWrapper = styled.div` + margin-bottom: ; + margin: 0px 38px 15px 38px; + + margin-top: 10px; +`; + +export default ({ node, view, getPos }) => { + const context = useContext(WaxContext); + const { + pmViews: { main }, + } = context; + + const customProps = main.props.customValues; + + const isEditable = main.props.editable(editable => { + return editable; + }); + + const readOnly = !isEditable; + + return ( + <MatchingWrapper> + <span>Matching</span> + <MatchingContainer className="matching"> + {!(readOnly && !customProps.showFeedBack) && ( + <FeedbackComponent + getPos={getPos} + node={node} + readOnly={readOnly} + view={view} + /> + )} + </MatchingContainer> + </MatchingWrapper> + ); +}; diff --git a/wax-prosemirror-services/src/MatchingService/schema/matchingContainerNode.js b/wax-prosemirror-services/src/MatchingService/schema/matchingContainerNode.js new file mode 100644 index 0000000000000000000000000000000000000000..f2d7146045cd74050b2d9b3bfc381a42c1a41fdc --- /dev/null +++ b/wax-prosemirror-services/src/MatchingService/schema/matchingContainerNode.js @@ -0,0 +1,25 @@ +const matchingContainerNode = { + attrs: { + id: { default: '' }, + class: { default: 'matching-container' }, + }, + group: 'block questions', + atom: true, + content: 'block*', + parseDOM: [ + { + tag: 'div.matching-container', + getAttrs(dom) { + return { + id: dom.getAttribute('id'), + class: dom.getAttribute('class'), + }; + }, + }, + ], + toDOM(node) { + return ['div', node.attrs, 0]; + }, +}; + +export default matchingContainerNode; diff --git a/wax-prosemirror-services/src/RulesService/Rules.js b/wax-prosemirror-services/src/RulesService/Rules.js index 466bf45fc459a320ec127683e8a4ea63d2ba75b4..f6cf3e1e9a6df1d8a26c0112af3392fc9a845ced 100644 --- a/wax-prosemirror-services/src/RulesService/Rules.js +++ b/wax-prosemirror-services/src/RulesService/Rules.js @@ -9,6 +9,7 @@ import { // TODO add through service. import inlineInputRule from '../MathService/InlineInputRule'; import blockInputRule from '../MathService/BlockInputRule'; +import linkRule from '../LinkService/LinkInputRule'; @injectable() class Rules { @@ -33,6 +34,7 @@ class Rules { return [ ...smartQuotes, // > blockquote + linkRule(this.schema.marks.link), wrappingInputRule(/^\s*>\s$/, this.schema.nodes.blockquote), // 1. ordered list diff --git a/wax-prosemirror-services/src/SchemaService/Node.js b/wax-prosemirror-services/src/SchemaService/Node.js index b9d9e6e31723746cdc9de32b653acc25b25d5e62..8fa643f92d8d870c99b6acaae8a72f600831df1e 100644 --- a/wax-prosemirror-services/src/SchemaService/Node.js +++ b/wax-prosemirror-services/src/SchemaService/Node.js @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ import { isPlainObject } from 'lodash'; import ParseRule from './ParseRule'; import Middleware from '../lib/Middleware'; @@ -55,7 +56,7 @@ export default class Node { } toJSON() { - const importer = this.importer; + const { importer } = this; return { atom: this.atom, diff --git a/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js b/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js index 1e08adbbb2e891fc316610fa8ace99d3b296ba07..0823fe837e4077e9db9b8f9d29b7b68ab49c00f8 100644 --- a/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js +++ b/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js @@ -3,12 +3,7 @@ import { keymap } from 'prosemirror-keymap'; import { undo, redo } from 'prosemirror-history'; import { Commands, DocumentHelpers } from 'wax-prosemirror-utilities'; -import { - wrapInList, - splitListItem, - liftListItem, - sinkListItem, -} from 'prosemirror-schema-list'; +import { splitListItem } from 'prosemirror-schema-list'; import { NodeSelection, TextSelection } from 'prosemirror-state'; import { @@ -112,36 +107,41 @@ const undoShortCut = (state, dispatch, view) => const redoShortCut = (state, dispatch, view) => redo(state, tr => dispatch(tr.setMeta('inputType', 'Redo')), view); -@injectable() -class ShortCuts { - constructor(plugins, schema) { - this.insertBreak = this.insertBreak.bind(this); - this.insertRule = this.insertRule.bind(this); - this.PmPlugins = plugins; - this.schema = schema; - this.keys = this.getKeys(); - } +const insertBreak = (state, dispatch) => { + const br = state.schema.nodes.hard_break.create(); + dispatch(state.tr.replaceSelectionWith(br).scrollIntoView()); + return true; +}; - insertBreak(state, dispatch) { - const br = this.schema.nodes.hard_break.create(); - dispatch(state.tr.replaceSelectionWith(br).scrollIntoView()); - return true; - } +// const insertRule = (state, dispatch) => { +// const hr = this.schema.nodes.horizontal_rule.create(); +// dispatch(state.tr.replaceSelectionWith(hr).scrollIntoView()); +// return true; +// } + +const getKeys = { + 'Mod-z': undoShortCut, + 'Shift-Mod-z': redoShortCut, + Backspace: backSpaceShortCut, + 'Mod-y': redoShortCut, + Escape: selectParentNode, + 'Mod-Enter': chainCommands(exitCode, insertBreak), + 'Shift-Enter': chainCommands(exitCode, insertBreak), + 'Ctrl-Enter': chainCommands(exitCode, insertBreak), + // 'Mod-_': this.insertRule, + Enter: pressEnter, +}; - insertRule(state, dispatch) { - const hr = this.schema.nodes.horizontal_rule.create(); - dispatch(state.tr.replaceSelectionWith(hr).scrollIntoView()); - return true; - } +@injectable() +export default class ShortCuts { + keys = getKeys; createShortCuts() { - const shortCuts = keymap(this.createKeyBindings()); - this.PmPlugins.add('shortcuts', shortCuts); + return keymap(this.createKeyBindings()); } addShortCut(shortcut) { Object.assign(this.keys, shortcut); - this.createShortCuts(); } createKeyBindings() { @@ -154,25 +154,4 @@ class ShortCuts { }); return this.keys; } - - getKeys() { - return { - 'Mod-z': undoShortCut, - 'Shift-Mod-z': redoShortCut, - Backspace: backSpaceShortCut, - 'Mod-y': redoShortCut, - Escape: selectParentNode, - 'Mod-Enter': chainCommands(exitCode, this.insertBreak), - 'Shift-Enter': chainCommands(exitCode, this.insertBreak), - 'Ctrl-Enter': chainCommands(exitCode, this.insertBreak), - 'Mod-_': this.insertRule, - 'Mod-[': liftListItem(this.schema.nodes.list_item), - 'Mod-]': sinkListItem(this.schema.nodes.list_item), - Enter: pressEnter, - 'Shift-Ctrl-8': wrapInList(this.schema.nodes.bulletlist), - 'Shift-Ctrl-9': wrapInList(this.schema.nodes.orderedlist), - }; - } } - -export default ShortCuts; diff --git a/wax-prosemirror-services/src/ShortCutsService/ShortCutsService.js b/wax-prosemirror-services/src/ShortCutsService/ShortCutsService.js index 96d705b12e6fb30e1e7e52807d1af34afb2894d7..48513e80866fe42a952fdadf5558fdb673612a02 100644 --- a/wax-prosemirror-services/src/ShortCutsService/ShortCutsService.js +++ b/wax-prosemirror-services/src/ShortCutsService/ShortCutsService.js @@ -4,30 +4,14 @@ import ShortCuts from './ShortCuts'; export default class ShortCutsService extends Service { name = 'ShortCutsService'; - boot() { - const shortCuts = this.container.get('ShortCuts'); - shortCuts.createShortCuts(); - } - - // TODO start ShortCuts as Schema is initiated register() { - const { PmPlugins } = this.app; - this.container - .bind('ShortCuts') - .toDynamicValue(() => { - if (this.app.schema) { - const { - schema: { schema }, - } = this.app; - - return new ShortCuts(PmPlugins, schema); - } + this.container.bind('ShortCuts').to(ShortCuts).inSingletonScope(); - return new ShortCuts( - PmPlugins, - this.container.get('Schema').getSchema(), - ); - }) - .inSingletonScope(); + this.container.bind('CreateShortCut').toFactory(context => { + return shortCut => { + const shortCutsInstance = context.container.get('ShortCuts'); + shortCutsInstance.addShortCut(shortCut); + }; + }); } } diff --git a/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js b/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js index 135b4a9f8346df3c2ac67830ac4c69c08cc35bb1..eeb8819176a5654eda39f50dfd7af54f7e498de1 100644 --- a/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js +++ b/wax-prosemirror-services/src/TablesService/InsertTableService/InsertTableService.js @@ -3,16 +3,9 @@ import Service from '../../Service'; import Table from './Table'; class InsertTableService extends Service { - boot() { - const shortCuts = this.container.get('ShortCuts'); - shortCuts.addShortCut({ - Tab: goToNextCell(1), - 'Shift-Tab': goToNextCell(-1), - }); - } - register() { this.container.bind('Table').to(Table); + const CreateShortCut = this.container.get('CreateShortCut'); // eslint-disable-next-line camelcase const { table, table_row, table_cell, table_header } = tableNodes({ @@ -33,6 +26,12 @@ class InsertTableService extends Service { createNode({ table_header, }); + CreateShortCut({ + Tab: goToNextCell(1), + }); + CreateShortCut({ + 'Shift-Tab': goToNextCell(-1), + }); } } diff --git a/wax-prosemirror-services/src/WaxToolGroups/MatchingToolGroupService/Matching.js b/wax-prosemirror-services/src/WaxToolGroups/MatchingToolGroupService/Matching.js index 3671ef6fd1318baaca52243ac70b968631969cb4..497d3c5c2f216a8e386ea22f4b884c0dd41f53a4 100644 --- a/wax-prosemirror-services/src/WaxToolGroups/MatchingToolGroupService/Matching.js +++ b/wax-prosemirror-services/src/WaxToolGroups/MatchingToolGroupService/Matching.js @@ -4,9 +4,9 @@ import ToolGroup from '../../lib/ToolGroup'; @injectable() class Matching extends ToolGroup { tools = []; - constructor() { + constructor(@inject('MatchingQuestion') matchingQuestion) { super(); - this.tools = []; + this.tools = [matchingQuestion]; } }