diff --git a/wax-prosemirror-components/src/components/findAndReplace/ExandedFindAndReplaceComponent.js b/wax-prosemirror-components/src/components/findAndReplace/ExandedFindAndReplaceComponent.js index d9ee659ce34e0f1ab7318e7e974966e6c062510d..3455807ed1796b6931d00b7c8156286145e68842 100644 --- a/wax-prosemirror-components/src/components/findAndReplace/ExandedFindAndReplaceComponent.js +++ b/wax-prosemirror-components/src/components/findAndReplace/ExandedFindAndReplaceComponent.js @@ -149,7 +149,7 @@ const ExandedFindAndReplaceComponent = ({ const searchRef = useRef(null); const replaceRef = useRef(null); const [searchValue, setSearchValue] = useState(nonExpandedText); - const [matchCaseSearch, setMatchCaseSearch] = useState(false); + const [matchCaseSearch, setMatchCaseSearch] = useState(matchCaseOption); const [match, setMatch] = useState([]); const [replaceValue, setReplaceValue] = useState(''); const [counterText, setCounterText] = useState('0 of 0'); diff --git a/wax-prosemirror-components/src/components/trackChanges/AcceptTrackChange.js b/wax-prosemirror-components/src/components/trackChanges/AcceptTrackChange.js new file mode 100644 index 0000000000000000000000000000000000000000..c65ed4a1eadb99deb65cf5901a5a6facf133ad28 --- /dev/null +++ b/wax-prosemirror-components/src/components/trackChanges/AcceptTrackChange.js @@ -0,0 +1,135 @@ +import { Mapping, RemoveMarkStep, ReplaceStep } from 'prosemirror-transform'; +import { Slice } from 'prosemirror-model'; +import removeNode from './removeNode'; + +const checkFromConfig = (mark, user, config) => { + if (mark.attrs.username === user.username && !config.own.accept) { + return false; + } + + if (mark.attrs.username !== user.username && !config.others.accept) { + return false; + } + + return true; +}; + +const acceptTrackChange = ( + state, + dispatch, + user, + activeTrackChange, + acceptConfig, +) => { + const { + tr, + selection: { from, to }, + } = state; + + tr.setMeta('AcceptReject', true); + const map = new Mapping(); + + state.doc.nodesBetween( + activeTrackChange.from, + activeTrackChange.to, + (node, pos) => { + if ( + node.attrs.track && + node.attrs.track.find(track => track.type === 'deletion') + ) { + removeNode(tr, node, pos, map); + } + if ( + node.marks && + node.marks.find(mark => mark.type.name === 'deletion') + ) { + const deletionMark = node.marks.find( + mark => mark.type.name === 'deletion', + ); + + const configCheck = checkFromConfig(deletionMark, user, acceptConfig); + if (!configCheck) return; + + const deletionStep = new ReplaceStep( + map.map(Math.max(pos, from)), + map.map(Math.min(pos + node.nodeSize, to)), + Slice.empty, + ); + tr.step(deletionStep); + map.appendMap(deletionStep.getMap()); + } else if ( + node.attrs.track && + node.attrs.track.find(track => track.type === 'insertion') + ) { + const track = node.attrs.track.filter( + track => track.type !== 'insertion', + ); + tr.setNodeMarkup( + map.map(pos), + null, + Object.assign(node.attrs.track, { track }), + node.marks, + ); + } else if ( + node.marks && + node.marks.find(mark => mark.type.name === 'insertion') + ) { + const insertionMark = node.marks.find( + mark => mark.type.name === 'insertion', + ); + const configCheck = checkFromConfig(insertionMark, user, acceptConfig); + if (!configCheck) return; + + tr.step( + new RemoveMarkStep( + map.map(Math.max(pos, from)), + map.map(Math.min(pos + node.nodeSize, to)), + insertionMark, + ), + ); + } else if ( + node.marks && + node.marks.find(mark => mark.type.name === 'format_change') + ) { + const formatChangeMark = node.marks.find( + mark => mark.type.name === 'format_change', + ); + const configCheck = checkFromConfig( + formatChangeMark, + user, + acceptConfig, + ); + if (!configCheck) return; + + tr.step( + new RemoveMarkStep( + activeTrackChange.from, + activeTrackChange.to, + formatChangeMark, + ), + ); + } else if ( + node.attrs.track && + node.attrs.track.find(track => track.type === 'block_change') + ) { + const blockChangeTrack = node.attrs.track.find( + track => track.type === 'block_change', + ); + + const track = node.attrs.track.filter( + track => track !== blockChangeTrack, + ); + + tr.setNodeMarkup( + map.map(pos), + null, + Object.assign(node.attrs.track, { track }), + node.marks, + ); + } + }, + ); + if (tr.steps.length) dispatch(tr); +}; + +export default acceptTrackChange; diff --git a/wax-prosemirror-components/src/components/trackChanges/ConnectedTrackChange.js b/wax-prosemirror-components/src/components/trackChanges/ConnectedTrackChange.js index 5ab5833e3c415f9bee872edfc0c9738d03f5d8dd..cc2972a118bdfdf0ee90cbb7c31d74134be54ecb 100644 --- a/wax-prosemirror-components/src/components/trackChanges/ConnectedTrackChange.js +++ b/wax-prosemirror-components/src/components/trackChanges/ConnectedTrackChange.js @@ -6,6 +6,8 @@ import { WaxContext } from 'wax-prosemirror-core'; import { last, maxBy } from 'lodash'; import { TextSelection } from 'prosemirror-state'; import TrackChangesBox from './TrackChangesBox'; +import acceptTrackChange from './AcceptTrackChange'; +import rejectTrackChange from './RejectTrackChange'; const ConnectedTrackChangeStyled = styled.div` margin-left: ${props => (props.active ? `${-20}px` : `${50}px`)}; @@ -18,9 +20,9 @@ const ConnectedTrackChangeStyled = styled.div` export default ({ trackChangeId, top, recalculateTops, trackChange }) => { const { app, activeView, view } = useContext(WaxContext); - + const user = app.config.get('user'); const [isActive, setIsActive] = useState(false); - // const { state, dispatch } = activeView; + const { state, dispatch } = activeView; const viewId = trackChange.attrs ? trackChange.attrs.viewid : trackChange.node.attrs.viewid; @@ -44,7 +46,7 @@ export default ({ trackChangeId, top, recalculateTops, trackChange }) => { view[viewId].dispatch( view[viewId].state.tr.setSelection( - new TextSelection(view[viewId].state.tr.doc.resolve(maxPos.pos)), + new TextSelection(view[viewId].state.tr.doc.resolve(maxPos.pos - 1)), ), ); @@ -71,6 +73,18 @@ export default ({ trackChangeId, top, recalculateTops, trackChange }) => { } }, [activeTrackChange]); + const onClickAccept = () => { + const acceptConfig = app.config.get('config.AcceptTrackChangeService'); + acceptTrackChange(state, dispatch, user, activeTrackChange, acceptConfig); + view[viewId].focus(); + }; + + const onClickReject = () => { + const rejectConfig = app.config.get('config.RejectTrackChangeService'); + rejectTrackChange(state, dispatch, user, activeTrackChange, rejectConfig); + view[viewId].focus(); + }; + const MemorizedTrackChange = useMemo( () => ( <ConnectedTrackChangeStyled @@ -81,7 +95,9 @@ export default ({ trackChangeId, top, recalculateTops, trackChange }) => { <TrackChangesBox active={isActive} key={trackChangeId} + onClickAccept={onClickAccept} onClickBox={onClickBox} + onClickReject={onClickReject} recalculateTops={recalculateTops} trackChangeId={trackChangeId} trackData={trackChange} diff --git a/wax-prosemirror-components/src/components/trackChanges/RejectTrackChange.js b/wax-prosemirror-components/src/components/trackChanges/RejectTrackChange.js new file mode 100644 index 0000000000000000000000000000000000000000..039bf44469d3d29dabf8a1e0233106935986904f --- /dev/null +++ b/wax-prosemirror-components/src/components/trackChanges/RejectTrackChange.js @@ -0,0 +1,129 @@ +import { + Mapping, + RemoveMarkStep, + ReplaceStep, + AddMarkStep, +} from 'prosemirror-transform'; +import { Slice } from 'prosemirror-model'; +import removeNode from './removeNode'; + +const checkFromConfig = (mark, user, config) => { + if (mark.attrs.username === user.username && !config.own.reject) { + return false; + } + + if (mark.attrs.username !== user.username && !config.others.reject) { + return false; + } + + return true; +}; + +const rejectTrackChange = ( + state, + dispatch, + user, + activeTrackChange, + rejectConfig, +) => { + const { + tr, + selection: { from, to }, + } = state; + tr.setMeta('AcceptReject', true); + const map = new Mapping(); + + state.doc.nodesBetween(from, to, (node, pos) => { + if (node.marks && node.marks.find(mark => mark.type.name === 'deletion')) { + const deletionMark = node.marks.find( + mark => mark.type.name === 'deletion', + ); + const configCheck = checkFromConfig(deletionMark, user, rejectConfig); + if (!configCheck) return; + + tr.step( + new RemoveMarkStep( + map.map(Math.max(pos, from)), + map.map(Math.min(pos + node.nodeSize, to)), + deletionMark, + ), + ); + } else if ( + node.attrs.track && + node.attrs.track.find(track => track.type === 'insertion') + ) { + removeNode(tr, node, pos, map); + } else if ( + node.marks && + node.marks.find(mark => mark.type.name === 'insertion') + ) { + const insertionMark = node.marks.find( + mark => mark.type.name === 'insertion', + ); + + const configCheck = checkFromConfig(insertionMark, user, rejectConfig); + if (!configCheck) return; + + const deletionStep = new ReplaceStep( + map.map(Math.max(pos, from)), + map.map(Math.min(pos + node.nodeSize, to)), + Slice.empty, + ); + tr.step(deletionStep); + map.appendMap(deletionStep.getMap()); + } else if ( + node.marks && + node.marks.find(mark => mark.type.name === 'format_change') + ) { + const formatChangeMark = node.marks.find( + mark => mark.type.name === 'format_change', + ); + formatChangeMark.attrs.before.forEach(oldMark => { + tr.step( + new AddMarkStep( + activeTrackChange.from, + activeTrackChange.to, + state.schema.marks[oldMark].create(), + ), + ); + }); + formatChangeMark.attrs.after.forEach(newMark => { + tr.step( + new RemoveMarkStep( + activeTrackChange.from, + activeTrackChange.to, + node.marks.find(mark => mark.type.name === newMark), + ), + ); + }); + + tr.step( + new RemoveMarkStep( + activeTrackChange.from, + activeTrackChange.to, + formatChangeMark, + ), + ); + } else if (!node.isInline && node.attrs.track) { + const blockChangeTrack = node.attrs.track.find( + track => track.type === 'block_change', + ); + if (blockChangeTrack) { + const track = node.attrs.track.filter( + track => track !== blockChangeTrack, + ); + tr.setNodeMarkup( + map.map(pos), + state.schema.nodes[blockChangeTrack.before.type], + Object.assign({}, node.attrs, blockChangeTrack.before.attrs, { + track, + }), + node.marks, + ); + } + } + }); + if (tr.steps.length) dispatch(tr); +}; + +export default rejectTrackChange; diff --git a/wax-prosemirror-components/src/components/trackChanges/removeNode.js b/wax-prosemirror-components/src/components/trackChanges/removeNode.js new file mode 100644 index 0000000000000000000000000000000000000000..c698e47c2ef9c4ec4ba8ce83db491a7c34d35c3c --- /dev/null +++ b/wax-prosemirror-components/src/components/trackChanges/removeNode.js @@ -0,0 +1,17 @@ +import { replaceStep } from 'prosemirror-transform'; +import { Selection } from 'prosemirror-state'; + +const removeNode = (tr, node, nodePos, map) => { + const newNodePos = map.map(nodePos); + const selectionBefore = Selection.findFrom(tr.doc.resolve(newNodePos), -1); + const start = selectionBefore.$anchor.pos; + const end = newNodePos + 1; + + const delStep = replaceStep(tr.doc, start, end); + + tr.step(delStep); + const stepMap = delStep.getMap(); + map.appendMap(stepMap); +}; + +export default removeNode; diff --git a/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js b/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js index b5256f4412d1f9d2829f1ac4ab2320efcc7f25b8..450507216cb7e82e443d3c109a57cc0cfc64058c 100644 --- a/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js +++ b/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js @@ -45,7 +45,13 @@ export default options => { node.descendants((childNode, childPos) => { nodeSize += childNode.nodeSize; }); - + // Active block + node.attrs.track[0].id = node.attrs.id; + currentTrackSelected = { + from: pos, + to: nodeSize, + attrs: node.attrs.track[0], + }; decos = decos.add(tr.doc, [ decoType(pos, nodeSize, { class: 'selected-block-change', diff --git a/wax-prosemirror-services/src/OverlayService/usePosition.js b/wax-prosemirror-services/src/OverlayService/usePosition.js index b7beae39797f6e83e663bced9b0d45954ff69ed0..d5e63043f6901b59253228b90a30b28c49f96376 100644 --- a/wax-prosemirror-services/src/OverlayService/usePosition.js +++ b/wax-prosemirror-services/src/OverlayService/usePosition.js @@ -29,11 +29,11 @@ export default options => { Check: wax-prosemirror-components/src/components/comments/CommentBubbleComponent.js for reposition the create new comment component. */ - const calculatePosition = (activeView, from, to) => { - const WaxSurface = activeView.dom.getBoundingClientRect(); - const start = activeView.coordsAtPos(from); - const end = activeView.coordsAtPos(to); - const left = end.left; + const calculatePosition = (focusedView, from, to) => { + const WaxSurface = focusedView.dom.getBoundingClientRect(); + const start = focusedView.coordsAtPos(from); + const end = focusedView.coordsAtPos(to); + const { left } = end; const top = end.top + 20; return { top, @@ -41,11 +41,11 @@ export default options => { }; }; - const displayOnSelection = (activeView, options) => { - const { selection } = activeView.state; + const displayOnSelection = focusedView => { + const { selection } = focusedView.state; const { from, to } = selection; if (from === to) return defaultOverlay; - const { left, top } = calculatePosition(activeView, from, to); + const { left, top } = calculatePosition(focusedView, from, to); return { left, top, @@ -55,15 +55,15 @@ export default options => { }; }; - const displayOnMark = (activeView, options) => { - const { markType, followCursor } = options; - const PMmark = activeView.state.schema.marks[markType]; - mark = DocumentHelpers.findMark(activeView.state, PMmark); + const displayOnMark = (focusedView, overlayOptions) => { + const { markType, followCursor } = overlayOptions; + const PMmark = focusedView.state.schema.marks[markType]; + mark = DocumentHelpers.findMark(focusedView.state, PMmark); if (!isObject(mark)) return defaultOverlay; - const { from, to } = followCursor ? activeView.state.selection : mark; + const { from, to } = followCursor ? focusedView.state.selection : mark; - const { left, top } = calculatePosition(activeView, from, to); + const { left, top } = calculatePosition(focusedView, from, to); return { left, @@ -74,15 +74,15 @@ export default options => { }; }; - const displayOnMarkOrSelection = (activeView, options) => { - const { markType, followCursor } = options; - const PMmark = activeView.state.schema.marks[markType]; - mark = DocumentHelpers.findMark(activeView.state, PMmark); + const displayOnMarkOrSelection = (focusedView, overlayOptions) => { + const { markType, followCursor } = overlayOptions; + const PMmark = focusedView.state.schema.marks[markType]; + mark = DocumentHelpers.findMark(focusedView.state, PMmark); - if (!isObject(mark)) return displayOnSelection(activeView, options); - const { from, to } = followCursor ? activeView.state.selection : mark; + if (!isObject(mark)) return displayOnSelection(focusedView, overlayOptions); + const { from, to } = followCursor ? focusedView.state.selection : mark; - const { left, top } = calculatePosition(activeView, from, to); + const { left, top } = calculatePosition(focusedView, from, to); return { left, top,