diff --git a/editors/editoria/src/Editoria.js b/editors/editoria/src/Editoria.js index c4dc8f5c990a0e25ec281476dacecb652ccdab56..5d61393122a523fb7328211ebdd536c4d9eb324b 100644 --- a/editors/editoria/src/Editoria.js +++ b/editors/editoria/src/Editoria.js @@ -47,9 +47,10 @@ const Editoria = () => ( autoFocus placeholder="Type Something..." fileUpload={file => renderImage(file)} - value={`<p class="paragraph"><span class="comment" data-id="834ba3c5-1fcf-4a42-8e2f-1f975f229716" data-conversation="[]" data-group="main">and a </span><span class="insertion" data-id="" data-user="1234" data-username="demo" data-date="26541557" data-group=""><span class="comment" data-id="834ba3c5-1fcf-4a42-8e2f-1f975f229716" data-conversation="[]" data-group="main">sdasdssd</span></span><span class="comment" data-id="834ba3c5-1fcf-4a42-8e2f-1f975f229716" data-conversation="[]" data-group="main">paragraph</span></p><p class="paragraph">more</p>`} + value={`<p class="paragraph">This is the first paragraph</p><p class="paragraph">This is the <span class="comment" data-id="ff7552b8-956c-4822-99b5-826715751c45" data-conversation="[{"demo":"dsdadsa"}]" data-group="main">second</span> paragraph</p><p class="author">This is an author</p>`} + // value={`<p class="paragraph"><span class="comment" data-id="834ba3c5-1fcf-4a42-8e2f-1f975f229716" data-conversation="[]" data-group="main">and a </span><span class="insertion" data-id="" data-user="1234" data-username="demo" data-date="26541557" data-group=""><span class="comment" data-id="834ba3c5-1fcf-4a42-8e2f-1f975f229716" data-conversation="[]" data-group="main">sdasdssd</span></span><span class="comment" data-id="834ba3c5-1fcf-4a42-8e2f-1f975f229716" data-conversation="[]" data-group="main">paragraph</span></p><p class="paragraph">more</p>`} layout={EditoriaLayout} - TrackChange + // TrackChange // onChange={source => console.log(source)} user={user} /> diff --git a/editors/editoria/src/config/config.js b/editors/editoria/src/config/config.js index 51001e4d3059c36c75746d7213b30bc5eb5d1579..a1187692dce33a230e0a4905560553ed9b79b022 100644 --- a/editors/editoria/src/config/config.js +++ b/editors/editoria/src/config/config.js @@ -56,26 +56,26 @@ export default { PmPlugins: [columnResizing(), tableEditing(), invisibles([hardBreak()])], - // Always load first TrackChangeService, + // Always load first CommentsService and LinkService, //as it matters on how PM treats nodes and marks services: [ + new DisplayBlockLevelService(), + new DisplayToolGroupService(), + new TextBlockLevelService(), + new TextToolGroupService(), + new ListsService(), new TrackChangeService(), new CommentsService(), new LinkService(), new PlaceholderService(), new ImageService(), - new ListsService(), new InlineAnnotationsService(), new TablesService(), - new TextBlockLevelService(), new BaseService(), new BaseToolGroupService(), - new DisplayBlockLevelService(), new NoteService(), new TableToolGroupService(), - new DisplayToolGroupService(), new ImageToolGroupService(), - new TextToolGroupService(), new AnnotationToolGroupService(), new NoteToolGroupService(), new ListToolGroupService() diff --git a/wax-prosemirror-components/src/components/comments/CommentBox.js b/wax-prosemirror-components/src/components/comments/CommentBox.js index c394e7a9dbdf79fd80674217393c531d3ea36d6f..e17cc74892d0c3a51d28c06df55e90035e209651 100644 --- a/wax-prosemirror-components/src/components/comments/CommentBox.js +++ b/wax-prosemirror-components/src/components/comments/CommentBox.js @@ -1,15 +1,20 @@ -import React, { Fragment, useState, useEffect, useContext } from "react"; +import React, { + Fragment, + useState, + useEffect, + useContext, + useRef +} from "react"; + import { Transition } from "react-transition-group"; import styled from "styled-components"; import { WaxContext } from "wax-prosemirror-core"; const CommentBoxStyled = styled.div` - height: 50px; - width: 50px; display: flex; flex-direction: column; margin-top: 10px; - background: black; + border: 1px solid #ffab20; position: absolute; transition: ${({ state }) => "top 1s, opacity 1.5s, left 1s"}; top: ${props => (props.top ? `${props.top}px` : 0)}; @@ -28,17 +33,51 @@ const CommentBoxStyled = styled.div` }}; `; -export default ({ mark, view, top, dataComment }) => { - const [animate, setAnimate] = useState(false); - const { view: { main }, app, activeView } = useContext(WaxContext); - const activeCommentPlugin = app.PmPlugins.get("activeComment"); - const activeComment = activeCommentPlugin.getState(activeView.state).comment; +export default ({ comment, view, top, dataBox }) => { + const { view: { main: { props: { user } } }, app, activeView } = useContext( + WaxContext + ), + { state, dispatch } = activeView, + commentInput = useRef(null), + [animate, setAnimate] = useState(false), + [commentAnnotation, setCommentAnnotation] = useState(comment), + [currentUser, setCurrentuser] = useState(user), + { attrs: { id } } = comment, + activeCommentPlugin = app.PmPlugins.get("activeComment"), + activeComment = activeCommentPlugin.getState(activeView.state).comment; + let active = false; - if (activeComment && mark.attrs.id === activeComment.attrs.id) active = true; + if (activeComment && id === activeComment.attrs.id) active = true; useEffect(() => { setAnimate(true); }, []); + const handleKeyDown = event => { + if (event.key === "Enter" || event.which === 13) { + saveComment(); + } + }; + + const saveComment = () => { + const { current: { value } } = commentInput; + const { tr, doc } = state; + const commentMark = state.schema.marks.comment; + + const obj = { [user.username]: value }; + commentAnnotation.attrs.conversation.push(obj); + + dispatch( + tr.addMark( + commentAnnotation.pos, + commentAnnotation.pos + commentAnnotation.node.nodeSize, + commentMark.create({ + ...((commentAnnotation && commentAnnotation.attrs) || {}), + conversation: commentAnnotation.attrs.conversation + }) + ) + ); + }; + return ( <Fragment> <Transition in={animate} timeout={1000}> @@ -46,9 +85,21 @@ export default ({ mark, view, top, dataComment }) => { <CommentBoxStyled top={top} state={state} - data-comment={dataComment} + data-box={dataBox} active={active} - /> + > + <div> + <input + type="text" + ref={commentInput} + placeholder="add a new comment" + onKeyPress={handleKeyDown} + autoFocus + /> + <button onClick={saveComment}>Post</button> + <button>Cancel</button> + </div> + </CommentBoxStyled> )} </Transition> </Fragment> diff --git a/wax-prosemirror-components/src/components/comments/CommentBubbleComponent.js b/wax-prosemirror-components/src/components/comments/CommentBubbleComponent.js index fe5a3bac2296ff7c604a0ea32e23f32ecb828b29..ebf06eda9d4c086442f1723977695f343ecaaff7 100644 --- a/wax-prosemirror-components/src/components/comments/CommentBubbleComponent.js +++ b/wax-prosemirror-components/src/components/comments/CommentBubbleComponent.js @@ -45,7 +45,7 @@ const CommentBubbleComponent = ({ !isSelectionComment() && showComment(activeViewId) && ( <button - onClick={event => { + onMouseDown={event => { createComment(event); }} > diff --git a/wax-prosemirror-components/src/components/comments/CommentsBoxList.js b/wax-prosemirror-components/src/components/comments/CommentsBoxList.js deleted file mode 100644 index 0722446cf0a77567fb5f83a9ea45f235eeb37a42..0000000000000000000000000000000000000000 --- a/wax-prosemirror-components/src/components/comments/CommentsBoxList.js +++ /dev/null @@ -1,22 +0,0 @@ -import React, { Fragment } from "react"; -import CommentBox from "./CommentBox"; - -export default ({ comments, view, position }) => { - return ( - <Fragment> - {comments.map((comment, index) => { - const { attrs: { id } } = comment; - const top = position[index] ? position[index][id] : 0; - return ( - <CommentBox - key={id} - mark={comment} - view={view} - top={top} - dataComment={`comment-${id}`} - /> - ); - })} - </Fragment> - ); -}; diff --git a/wax-prosemirror-components/src/components/link/LinkComponent.js b/wax-prosemirror-components/src/components/link/LinkComponent.js index c44c989fe393ce578cf4a4da3f3fc7b7810b8fcc..a7ac13a3b2ecdc9c3740a01b6571fc4a4308fada 100644 --- a/wax-prosemirror-components/src/components/link/LinkComponent.js +++ b/wax-prosemirror-components/src/components/link/LinkComponent.js @@ -19,6 +19,7 @@ const Button = styled.button` `; const LinkComponent = ({ mark, setPosition, position }) => { + console.log(mark); const href = mark ? mark.attrs.href : null, linkMark = mark ? mark : null, { view: { main }, activeView } = useContext(WaxContext), diff --git a/wax-prosemirror-components/src/components/rightArea/BoxList.js b/wax-prosemirror-components/src/components/rightArea/BoxList.js new file mode 100644 index 0000000000000000000000000000000000000000..4dc7dd382296de369e10860c9071174cbd6e3b36 --- /dev/null +++ b/wax-prosemirror-components/src/components/rightArea/BoxList.js @@ -0,0 +1,41 @@ +import { Mark } from "prosemirror-model"; +import React, { Fragment } from "react"; +import CommentBox from "../comments/CommentBox"; +import TrackChangeBox from "../trackChanges/TrackChangeBox"; + +export default ({ commentsTracks, view, position }) => { + return ( + <Fragment> + {commentsTracks.map((commentTrack, index) => { + const id = + commentTrack instanceof Mark + ? commentTrack.attrs.id + : commentTrack.node.attrs.id; + + const top = position[index] ? position[index][id] : 0; + + if (commentTrack.type && commentTrack.type.name === "comment") { + return ( + <CommentBox + key={id} + comment={commentTrack} + view={view} + top={top} + dataBox={id} + /> + ); + } else { + return ( + <TrackChangeBox + key={id} + trackChange={commentTrack} + view={view} + top={top} + dataBox={id} + /> + ); + } + })} + </Fragment> + ); +}; diff --git a/wax-prosemirror-components/src/components/rightArea/RightArea.js b/wax-prosemirror-components/src/components/rightArea/RightArea.js index 9152588481c41eb9ce9922c36ed26e7b16aad73f..4eae5f947e1c1c93273d180ac1b32035d62a873c 100644 --- a/wax-prosemirror-components/src/components/rightArea/RightArea.js +++ b/wax-prosemirror-components/src/components/rightArea/RightArea.js @@ -1,3 +1,4 @@ +import { Mark } from "prosemirror-model"; import React, { useContext, useState, @@ -9,52 +10,54 @@ import React, { import styled from "styled-components"; import { WaxContext } from "wax-prosemirror-core"; import { DocumentHelpers } from "wax-prosemirror-utilities"; -import CommentsBoxList from "./../comments/CommentsBoxList"; -import { each, uniqBy } from "lodash"; +import BoxList from "./BoxList"; +import { each, uniqBy, sortBy } from "lodash"; export default ({ area }) => { const { view: { main }, app, activeView } = useContext(WaxContext); const activeCommentPlugin = app.PmPlugins.get("activeComment"); - const [marks, setMarks] = useState([]); + const [marksNodes, setMarksNodes] = useState([]); const [position, setPosition] = useState(); const setTops = useCallback(() => { const result = []; - let markEl = null; + let markNodeEl = null; let annotationTop = 0; let boxHeight = 0; let top = 0; const allCommentsTop = []; - each(marks[area], (mark, pos) => { + each(marksNodes[area], (markNode, pos) => { const WaxSurface = main.dom.getBoundingClientRect(); - const { attrs: { id } } = mark; + const id = + markNode instanceof Mark ? markNode.attrs.id : markNode.node.attrs.id; + const activeComment = activeCommentPlugin.getState(activeView.state) .comment; let isActive = false; - if (activeComment && mark.attrs.id === activeComment.attrs.id) - isActive = true; + if (activeComment && id === activeComment.attrs.id) isActive = true; //annotation top if (area === "main") { - markEl = document.querySelector(`span[data-id="${id}"]`); - if (markEl) - annotationTop = markEl.getBoundingClientRect().top - WaxSurface.top; + markNodeEl = document.querySelector(`[data-id="${id}"]`); + if (markNodeEl) + annotationTop = + markNodeEl.getBoundingClientRect().top - WaxSurface.top; } else { const panelWrapper = document.getElementsByClassName("panelWrapper"); const panelWrapperHeight = panelWrapper[0].getBoundingClientRect() .height; - markEl = document + markNodeEl = document .querySelector("#notes-container") - .querySelector(`span[data-id="${id}"]`); - if (markEl) + .querySelector(`[data-id="${id}"]`); + if (markNodeEl) annotationTop = - markEl.getBoundingClientRect().top - panelWrapperHeight - 50; + markNodeEl.getBoundingClientRect().top - panelWrapperHeight - 50; } - // get height of this mark box - const boxEl = document.querySelector(`div[data-comment="comment-${id}"]`); + // get height of this markNode box + const boxEl = document.querySelector(`div[data-box="${id}"]`); if (boxEl) boxHeight = parseInt(boxEl.offsetHeight); // where the box should move to @@ -62,20 +65,20 @@ export default ({ area }) => { // if the above comment box has already taken up the height, move down if (pos > 0) { - const previousBox = marks[area][pos - 1]; + const previousBox = marksNodes[area][pos - 1]; const previousEndHeight = previousBox.endHeight; if (annotationTop < previousEndHeight) { top = previousEndHeight + 2; } } // store where the box ends to be aware of overlaps in the next box - mark.endHeight = top + boxHeight + 2; + markNode.endHeight = top + boxHeight + 2; result[pos] = top; allCommentsTop.push({ [id]: result[pos] }); // if active, move as many boxes above as needed to bring it to the annotation's height if (isActive) { - mark.endHeight = annotationTop + boxHeight + 2; + markNode.endHeight = annotationTop + boxHeight + 2; result[pos] = annotationTop; allCommentsTop[pos][id] = result[pos]; let b = true; @@ -85,7 +88,7 @@ export default ({ area }) => { if (i === 0) b = false; while (b) { - const boxAbove = marks[area][i - 1]; + const boxAbove = marksNodes[area][i - 1]; const boxAboveEnds = boxAbove.endHeight; const currentTop = result[i]; @@ -95,7 +98,12 @@ export default ({ area }) => { const overlap = boxAboveEnds - currentTop; result[i - 1] -= overlap; - allCommentsTop[i - 1][marks[area][i - 1].attrs.id] = result[i - 1]; + const previousMarkNode = + marksNodes[area][i - 1] instanceof Mark + ? marksNodes[area][i - 1].attrs.id + : marksNodes[area][i - 1].node.attrs.id; + + allCommentsTop[i - 1][previousMarkNode] = result[i - 1]; } if (!doesOverlap) b = false; @@ -110,7 +118,7 @@ export default ({ area }) => { useEffect( () => { - setMarks(updateMarks(main)); + setMarksNodes(updateMarks(main)); setPosition(setTops()); }, @@ -119,14 +127,14 @@ export default ({ area }) => { const CommentTrackComponent = useMemo( () => ( - <CommentsBoxList - comments={marks[area] || []} + <BoxList + commentsTracks={marksNodes[area] || []} area={area} view={main} position={position} /> ), - [marks[area] || [], position] + [marksNodes[area] || [], position] ); return <Fragment>{CommentTrackComponent}</Fragment>; }; @@ -136,6 +144,7 @@ const updateMarks = view => { const allBlockNodes = DocumentHelpers.findBlockNodes(view.state.doc); const allInlineNodes = DocumentHelpers.findInlineNodes(view.state.doc); const finalMarks = []; + const finalNodes = []; allInlineNodes.map(node => { if (node.node.marks.length > 0) { @@ -146,21 +155,36 @@ const updateMarks = view => { mark.type.name === "deletion" || mark.type.name === "format_change" ) { + mark.pos = node.pos; + mark.node = node.node; finalMarks.push(mark); } }); } }); - const groupedNodes = {}; - uniqBy(finalMarks, "attrs.id").forEach(mark => { - if (!groupedNodes[mark.attrs.group]) { - groupedNodes[mark.attrs.group] = [mark]; + allBlockNodes.map(node => { + if (node.node.attrs.track && node.node.attrs.track.length > 0) { + finalNodes.push(node); + } + }); + + const nodesAndMarks = [...uniqBy(finalMarks, "attrs.id"), ...finalNodes]; + + const groupedMarkNodes = {}; + + sortBy(nodesAndMarks, ["pos"]).forEach(markNode => { + const markNodeAttrs = markNode.attrs + ? markNode.attrs + : markNode.node.attrs; + + if (!groupedMarkNodes[markNodeAttrs.group]) { + groupedMarkNodes[markNodeAttrs.group] = [markNode]; } else { - groupedNodes[mark.attrs.group].push(mark); + groupedMarkNodes[markNodeAttrs.group].push(markNode); } }); - return groupedNodes; + return groupedMarkNodes; } return []; }; diff --git a/wax-prosemirror-components/src/components/trackChanges/TrackChangeBox.js b/wax-prosemirror-components/src/components/trackChanges/TrackChangeBox.js new file mode 100644 index 0000000000000000000000000000000000000000..7c1fadeca38daa1a181cdd8451d51b748b1828d5 --- /dev/null +++ b/wax-prosemirror-components/src/components/trackChanges/TrackChangeBox.js @@ -0,0 +1,73 @@ +import { Mark } from "prosemirror-model"; +import React, { Fragment, useState, useEffect, useContext } from "react"; +import { Transition } from "react-transition-group"; +import styled from "styled-components"; +import { WaxContext } from "wax-prosemirror-core"; + +const TrackChangeBoxStyled = styled.div` + display: flex; + flex-direction: column; + margin-top: 10px; + border: 1px solid blue; + position: absolute; + transition: ${({ state }) => "top 1s, opacity 1.5s, left 1s"}; + top: ${props => (props.top ? `${props.top}px` : 0)}; + left: ${props => (props.active ? `${63}%` : `${65}%`)}; + opacity: ${({ state }) => { + switch (state) { + case "exited": + return 0.2; + case "exiting": + return 0.4; + case "entering": + return 0.6; + case "entered": + return 1; + } + }}; +`; + +export default ({ trackChange, view, top, dataBox }) => { + console.log(trackChange); + const [animate, setAnimate] = useState(false); + const { view: { main }, app, activeView } = useContext(WaxContext); + let action; + if (trackChange instanceof Mark) { + if ((trackChange.type.name = "format_change")) { + const { attrs: { username, before, after } } = trackChange; + action = `User ${username} added ${after[0]}`; + } + } else { + action = `User demo changed paragraph to heading 1`; + } + // const { attrs: { id } } = comment; + + // const activeCommentPlugin = app.PmPlugins.get("activeComment"); + // const activeComment = activeCommentPlugin.getState(activeView.state).comment; + let active = false; + // if (activeComment && id === activeComment.attrs.id) active = true; + useEffect(() => { + setAnimate(true); + }, []); + + return ( + <Fragment> + <Transition in={animate} timeout={1000}> + {state => ( + <TrackChangeBoxStyled + top={top} + state={state} + data-box={dataBox} + active={active} + > + <div> + {action} + <button>Accept</button> + <button>Reject</button> + </div> + </TrackChangeBoxStyled> + )} + </Transition> + </Fragment> + ); +}; diff --git a/wax-prosemirror-core/src/WaxView.js b/wax-prosemirror-core/src/WaxView.js index 7f593654d6c1311e1aba1cf8e96c7e209b44323d..34e36b88aceed50bcf07f471f064d7f062318cea 100644 --- a/wax-prosemirror-core/src/WaxView.js +++ b/wax-prosemirror-core/src/WaxView.js @@ -32,6 +32,7 @@ export default props => { editable: () => !readonly, state: EditorState.create(options), dispatchTransaction, + user, handleDOMEvents: { blur: onBlur ? view => { @@ -56,8 +57,9 @@ export default props => { const dispatchTransaction = transaction => { const { TrackChange } = props; + const group = "main"; const tr = TrackChange - ? trackedTransaction(transaction, view.state, user) + ? trackedTransaction(transaction, view.state, user, group) : transaction; const state = view.state.apply(tr); diff --git a/wax-prosemirror-plugins/src/comments/ActiveComment.js b/wax-prosemirror-plugins/src/comments/ActiveComment.js index b2396b8cbee0b76dacf660e48377ec8e37c22e35..5c7c63bab296a49db29cb5ffebda9a915d1f2190 100644 --- a/wax-prosemirror-plugins/src/comments/ActiveComment.js +++ b/wax-prosemirror-plugins/src/comments/ActiveComment.js @@ -6,8 +6,6 @@ import { DocumentHelpers } from "wax-prosemirror-utilities"; const activeComment = new PluginKey("activeComment"); const getComment = state => { - if (state.selection.from !== state.selection.to) return; - const commentMark = state.schema.marks["comment"]; const commentOnSelection = DocumentHelpers.findMark(state, commentMark); @@ -42,6 +40,12 @@ const getComment = state => { }; } } + if ( + state.selection.from !== state.selection.to && + commentOnSelection && + commentOnSelection.attrs.conversation.length + ) + return; return commentOnSelection; }; diff --git a/wax-prosemirror-schema/src/marks/commentMark.js b/wax-prosemirror-schema/src/marks/commentMark.js index 2c2f36a33a6a834ce5e6a675abcb773b8613f239..b946d7dd8c395a4b6f97b954a3a79c34307ca43d 100644 --- a/wax-prosemirror-schema/src/marks/commentMark.js +++ b/wax-prosemirror-schema/src/marks/commentMark.js @@ -15,7 +15,7 @@ const comment = { class: hook.dom.getAttribute("class"), id: hook.dom.dataset.id, group: hook.dom.dataset.group, - conversation: hook.dom.dataset.conversation + conversation: JSON.parse(hook.dom.dataset.conversation) }); next(); } diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/addMarkStep.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/addMarkStep.js index f5031d00cdf72939c2696a74cec017ace1e5683f..6c2629ca074cd5d74a1e2d2176a01413ceb0e27f 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/addMarkStep.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/addMarkStep.js @@ -1,4 +1,6 @@ -const addMarkStep = (state, tr, step, newTr, map, doc, user, date) => { +import { v4 as uuidv4 } from "uuid"; + +const addMarkStep = (state, tr, step, newTr, map, doc, user, date, group) => { doc.nodesBetween(step.from, step.to, (node, pos) => { if (!node.isInline) { return true; @@ -13,7 +15,7 @@ const addMarkStep = (state, tr, step, newTr, map, doc, user, date) => { ); } if ( - ["em", "strong", "underline"].includes(step.mark.type.name) && + !["comment"].includes(step.mark.type.name) && !node.marks.find(mark => mark.type === step.mark.type) ) { const formatChangeMark = node.marks.find( @@ -43,7 +45,9 @@ const addMarkStep = (state, tr, step, newTr, map, doc, user, date) => { username: user.username, date, before, - after + after, + group, + id: uuidv4() }) ); } else if (formatChangeMark) { diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markDeletion.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markDeletion.js index b4c76ef12c7d93330e415335a38d6245fc0fdd3f..bb4ba72bf2ebd1441b9385fc88669bc8db632b0a 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markDeletion.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markDeletion.js @@ -2,11 +2,11 @@ import { Selection, TextSelection } from "prosemirror-state"; import { Slice } from "prosemirror-model"; import { ReplaceStep, Mapping } from "prosemirror-transform"; -const markDeletion = (tr, from, to, user, date) => { +const markDeletion = (tr, from, to, user, date, group) => { const deletionMark = tr.doc.type.schema.marks.deletion.create({ user: user.userId, - username: user.username, - date + username: user.username + // date }); let firstTableCellChild = false; const deletionMap = new Mapping(); @@ -83,8 +83,8 @@ const markDeletion = (tr, from, to, user, date) => { track.push({ type: "deletion", user: user.userId, - username: user.username, - date + username: user.username + // date }); tr.setNodeMarkup( deletionMap.map(pos), diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markInsertion.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markInsertion.js index 7d06539058e62114dae60b7fd07996bd4c125afb..c8c6d547cc1cb597b96beb43aba9c678aef98483 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markInsertion.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markInsertion.js @@ -1,11 +1,13 @@ -const markInsertion = (tr, from, to, user, date) => { +const markInsertion = (tr, from, to, user, date, group) => { tr.removeMark(from, to, tr.doc.type.schema.marks.deletion); tr.removeMark(from, to, tr.doc.type.schema.marks.insertion); + const insertionMark = tr.doc.type.schema.marks.insertion.create({ user: user.userId, - username: user.username, - date + username: user.username + // date }); + tr.addMark(from, to, insertionMark); // Add insertion mark also to block nodes (figures, text blocks) but not table cells/rows and lists. tr.doc.nodesBetween(from, to, (node, pos) => { @@ -27,13 +29,14 @@ const markInsertion = (tr, from, to, user, date) => { type: "insertion", user: user.userId, username: user.username, - date + date, + group }); tr.setNodeMarkup( pos, null, - Object.assign({}, node.attrs, { track }), + Object.assign({}, node.attrs, { track, group }), node.marks ); } diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markWrapping.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markWrapping.js index 248cc97621204705d94cc1d47102c20fdcc1ccdf..57aad8058de00e2323787481b5189681cbd816c1 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markWrapping.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markWrapping.js @@ -1,6 +1,6 @@ import { v4 as uuidv4 } from "uuid"; -const markWrapping = (tr, pos, oldNode, newNode, user, date) => { +const markWrapping = (tr, pos, oldNode, newNode, user, date, group) => { let track = oldNode.attrs.track.slice(), blockTrack = track.find(track => track.type === "block_change"); @@ -12,7 +12,7 @@ const markWrapping = (tr, pos, oldNode, newNode, user, date) => { ) { blockTrack = { type: "block_change", - user: user.id, + user: user.userId, username: user.username, date, before: blockTrack.before @@ -22,7 +22,7 @@ const markWrapping = (tr, pos, oldNode, newNode, user, date) => { } else { blockTrack = { type: "block_change", - user: user.id, + user: user.userId, username: user.username, date, before: { type: oldNode.type.name, attrs: oldNode.attrs } @@ -35,7 +35,11 @@ const markWrapping = (tr, pos, oldNode, newNode, user, date) => { } track.push(blockTrack); } - tr.setNodeMarkup(pos, null, Object.assign({}, newNode.attrs, { track })); + tr.setNodeMarkup( + pos, + null, + Object.assign({}, newNode.attrs, { track, group, id: uuidv4() }) + ); }; export default markWrapping; diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceAroundStep.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceAroundStep.js index fd4aca5161e7eca571b91f2cbb46fca259c7ea07..55902bc149f7471e0c138f4bff2bb8c301a3b34b 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceAroundStep.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceAroundStep.js @@ -2,17 +2,29 @@ import markDeletion from "./markDeletion"; import markInsertion from "./markInsertion"; import markWrapping from "./markWrapping"; -const replaceAroundStep = (state, tr, step, newTr, map, doc, user, date) => { +const replaceAroundStep = ( + state, + tr, + step, + newTr, + map, + doc, + user, + date, + group +) => { if (step.from === step.gapFrom && step.to === step.gapTo) { // wrapped in something newTr.step(step); const from = step.getMap().map(step.from, -1); const to = step.getMap().map(step.gapFrom); - markInsertion(newTr, from, to, user, date); + markInsertion(newTr, from, to, user, date, group); } else if (!step.slice.size) { // unwrapped from something map.appendMap(step.invert(doc).getMap()); - map.appendMap(markDeletion(newTr, step.from, step.gapFrom, user, date)); + map.appendMap( + markDeletion(newTr, step.from, step.gapFrom, user, date, group) + ); } else if ( step.slice.size === 2 && step.gapFrom - step.from === 1 && @@ -28,7 +40,8 @@ const replaceAroundStep = (state, tr, step, newTr, map, doc, user, date) => { oldNode, step.slice.content.firstChild, user, - date + date, + group ); } } else { diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceStep.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceStep.js index a74a96c2e3124164299ced2d57601046d040816c..f665bf1c5063f0aa2dddc21f1d6b484c0b91b730 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceStep.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceStep.js @@ -5,7 +5,7 @@ import { DocumentHelpers } from "wax-prosemirror-utilities"; import markDeletion from "./markDeletion"; import markInsertion from "./markInsertion"; -const replaceStep = (state, tr, step, newTr, map, doc, user, date) => { +const replaceStep = (state, tr, step, newTr, map, doc, user, date, group) => { const cellDeleteTr = ["deleteContentBackward", "deleteContentForward"].includes( tr.getMeta("inputType") @@ -38,7 +38,7 @@ const replaceStep = (state, tr, step, newTr, map, doc, user, date) => { } const mappedNewStepTo = newStep.getMap().map(newStep.to); - markInsertion(trTemp, newStep.from, mappedNewStepTo, user, date); + markInsertion(trTemp, newStep.from, mappedNewStepTo, user, date, group); // We condense it down to a single replace step. const condensedStep = new ReplaceStep( newStep.from, @@ -54,7 +54,7 @@ const replaceStep = (state, tr, step, newTr, map, doc, user, date) => { } } if (step.from !== step.to) { - map.appendMap(markDeletion(newTr, step.from, step.to, user, date)); + map.appendMap(markDeletion(newTr, step.from, step.to, user, date, group)); } }; diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js index 2a6d21e98131d51f21eaa1973f8abed333044774..b5ccd6bb7ef473a7d0c45872392c9708447cec14 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js @@ -20,7 +20,7 @@ import replaceAroundStep from "./helpers/replaceAroundStep"; import addMarkStep from "./helpers/addMarkStep"; import removeMarkStep from "./helpers/removeMarkStep"; -const trackedTransaction = (tr, state, user) => { +const trackedTransaction = (tr, state, user, group) => { if ( !tr.steps.length || (tr.meta && @@ -34,7 +34,7 @@ const trackedTransaction = (tr, state, user) => { const newTr = state.tr; const map = new Mapping(); - const date = Math.floor(Date.now() / 60000); + const date = Math.floor(Date.now() / 300000); tr.steps.forEach(originalStep => { const step = originalStep.map(map), @@ -45,16 +45,16 @@ const trackedTransaction = (tr, state, user) => { switch (step.constructor) { case ReplaceStep: - replaceStep(state, tr, step, newTr, map, doc, user, date); + replaceStep(state, tr, step, newTr, map, doc, user, date, group); break; case ReplaceAroundStep: - replaceAroundStep(state, tr, step, newTr, map, doc, user, date); + replaceAroundStep(state, tr, step, newTr, map, doc, user, date, group); break; case AddMarkStep: - addMarkStep(state, tr, step, newTr, map, doc, user, date); + addMarkStep(state, tr, step, newTr, map, doc, user, date, group); break; case RemoveMarkStep: - removeMarkStep(state, tr, step, newTr, map, doc, user, date); + removeMarkStep(state, tr, step, newTr, map, doc, user, date, group); break; } }); diff --git a/wax-prosemirror-utilities/src/commands/Commands.js b/wax-prosemirror-utilities/src/commands/Commands.js index 5668ce7765b4b4a4957ba76b30a6243a640e7d0f..eb1d9647d1414e15ccdac5e72abe7160eca2b08b 100644 --- a/wax-prosemirror-utilities/src/commands/Commands.js +++ b/wax-prosemirror-utilities/src/commands/Commands.js @@ -1,4 +1,6 @@ +import { Selection, TextSelection } from "prosemirror-state"; import { v4 as uuidv4 } from "uuid"; +import { toggleMark } from "prosemirror-commands"; const setBlockType = (nodeType, attrs = {}) => { return (state, dispatch) => { @@ -97,18 +99,11 @@ const isOnSameTextBlock = state => { }; const createComment = (state, dispatch, group) => { - const { selection: { $from, $to } } = state; - dispatch( - state.tr.addMark( - $from.pos, - $to.pos, - state.schema.marks.comment.create({ - id: uuidv4(), - group, - conversation: [] - }) - ) - ); + toggleMark(state.config.schema.marks.comment, { + id: uuidv4(), + group, + conversation: [] + })(state, dispatch); }; export default { diff --git a/wax-prosemirror-utilities/src/document/DocumentHelpers.js b/wax-prosemirror-utilities/src/document/DocumentHelpers.js index 716b5c35a2b624c645ed78264f18776e2890949e..37a09a354e27b5d3c0adbdf53b2d27df3c9060ab 100644 --- a/wax-prosemirror-utilities/src/document/DocumentHelpers.js +++ b/wax-prosemirror-utilities/src/document/DocumentHelpers.js @@ -62,10 +62,15 @@ const findChildrenByMark = (node, markType, descend) => { return findChildren(node, child => markType.isInSet(child.marks), descend); }; +export const findChildrenByAttr = (node, predicate, descend) => { + return findChildren(node, child => !!predicate(child.attrs), descend); +}; + export default { findMark, findBlockNodes, findChildrenByType, findInlineNodes, - findChildrenByMark + findChildrenByMark, + findChildrenByAttr };