diff --git a/wax-prosemirror-components/src/components/comments/Comment.js b/wax-prosemirror-components/src/components/comments/Comment.js index c215af8c8a0e474692012968405d2a74041f6531..08cc0155ccf49633d428075c02fcb54711093aa0 100644 --- a/wax-prosemirror-components/src/components/comments/Comment.js +++ b/wax-prosemirror-components/src/components/comments/Comment.js @@ -97,7 +97,7 @@ export default ({ comment, activeView, user, active }) => { allCommentsWithSameId.forEach(singleComment => { const markPosition = DocumentHelpers.findMarkPosition( - activeView, + state, singleComment.pos, 'comment', ); diff --git a/wax-prosemirror-components/src/components/comments/CommentBox.js b/wax-prosemirror-components/src/components/comments/CommentBox.js index 05b730cb2f9a79898ca8bf90ef89ef69a8e5c657..94ab170c9da58450365e25b770cf1267456bce63 100644 --- a/wax-prosemirror-components/src/components/comments/CommentBox.js +++ b/wax-prosemirror-components/src/components/comments/CommentBox.js @@ -18,6 +18,7 @@ const CommentBoxStyled = styled.div` transition: ${({ state }) => 'top 1s, opacity 1.5s, left 1s'}; top: ${props => (props.top ? `${props.top}px` : 0)}; left: ${props => (props.active ? `${63}%` : `${65}%`)}; + max-width: 250px; opacity: ${({ state }) => { switch (state) { case 'exited': diff --git a/wax-prosemirror-components/src/components/trackChanges/TrackChangeBox.js b/wax-prosemirror-components/src/components/trackChanges/TrackChangeBox.js index b81aa5ec4122eaf91de33a7d7593bd0073429c8f..4001c5e04898f5b5919f5f50134f6e597a3e80f0 100644 --- a/wax-prosemirror-components/src/components/trackChanges/TrackChangeBox.js +++ b/wax-prosemirror-components/src/components/trackChanges/TrackChangeBox.js @@ -14,6 +14,7 @@ const TrackChangeBoxStyled = styled.div` transition: ${({ state }) => 'top 1s, opacity 1.5s, left 1s'}; top: ${props => (props.top ? `${props.top}px` : 0)}; left: ${props => (props.active ? `${63}%` : `${65}%`)}; + max-width: 450px; opacity: ${({ state }) => { switch (state) { case 'exited': diff --git a/wax-prosemirror-layouts/src/layouts/EditorElements.js b/wax-prosemirror-layouts/src/layouts/EditorElements.js index 24c5a1ea52eee6325c6dacf202cf4f10bc6f12f0..81693009ceff7118d88de134cc483fd8da21a016 100644 --- a/wax-prosemirror-layouts/src/layouts/EditorElements.js +++ b/wax-prosemirror-layouts/src/layouts/EditorElements.js @@ -196,7 +196,7 @@ export default css` .selected-deletion, .selected-format-change, .selected-block-change { - background-color: #fffacf; + background-color: #eefbfb; } .format-change { diff --git a/wax-prosemirror-layouts/src/layouts/EditoriaLayout.js b/wax-prosemirror-layouts/src/layouts/EditoriaLayout.js index 24d63cda65504f2597a3c19e814dfb75a1ace120..e2140eb3daf92e0b4b89d5f8e19c79a329581b31 100644 --- a/wax-prosemirror-layouts/src/layouts/EditoriaLayout.js +++ b/wax-prosemirror-layouts/src/layouts/EditoriaLayout.js @@ -20,6 +20,7 @@ const LayoutWrapper = styled.div` position: relative; bottom: 14px; background: white; + z-index: 999; color: #a3a3a3; font-weight: 600; letter-spacing: 0.15em; @@ -105,7 +106,7 @@ const WaxSurfaceScroll = styled.div` const MainMenuContainer = styled.div` background: #fff; - height: 52px; + min-height: 52px; line-height: 32px; position: relative; user-select: none; @@ -120,7 +121,8 @@ const MainMenuInner = styled.div` position: absolute; right: 0; top: 0; - background: transparent; + height: 100%; + background: #fff; z-index: 9999; div { display: flex; @@ -183,7 +185,7 @@ const CommentsContainer = styled.div` height: 100%; `; -let surfaceHeight = 700; +let surfaceHeight = 500; let notesHeight = 50; const onResizeEnd = arr => { @@ -250,7 +252,7 @@ const EditoriaLayout = ({ editor }) => { <PanelGroup direction="column" panelWidths={[ - { size: surfaceHeight, resize: 'dynamic' }, + { size: surfaceHeight, resize: 'stretch' }, { size: notesHeight, resize: 'stretch' }, ]} onResizeEnd={onResizeEnd} diff --git a/wax-prosemirror-plugins/src/comments/CommentPlugin.js b/wax-prosemirror-plugins/src/comments/CommentPlugin.js index 03b77b01f0c4f4a85d9740fa1f0142a87d85991b..2d61ca2aa537faecd218b37418e50f37684c5250 100644 --- a/wax-prosemirror-plugins/src/comments/CommentPlugin.js +++ b/wax-prosemirror-plugins/src/comments/CommentPlugin.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + import { minBy, maxBy, last } from 'lodash'; import { Plugin, PluginKey } from 'prosemirror-state'; import { Decoration, DecorationSet } from 'prosemirror-view'; @@ -7,7 +9,10 @@ const commentPlugin = new PluginKey('commentPlugin'); const getComment = state => { const commentMark = state.schema.marks.comment; - const commentOnSelection = DocumentHelpers.findFragmentedMark(state, commentMark); + const commentOnSelection = DocumentHelpers.findFragmentedMark( + state, + commentMark, + ); // Don't allow Active comment if selection is not collapsed if ( @@ -19,21 +24,36 @@ const getComment = state => { } if (commentOnSelection) { - const commentNodes = DocumentHelpers.findChildrenByMark(state.doc, commentMark, true); + const commentNodes = DocumentHelpers.findChildrenByMark( + state.doc, + commentMark, + true, + ); const allCommentsWithSameId = []; commentNodes.map(node => { node.node.marks.filter(mark => { - if (mark.type.name === 'comment' && commentOnSelection.attrs.id === mark.attrs.id) { + if ( + mark.type.name === 'comment' && + commentOnSelection.attrs.id === mark.attrs.id + ) { allCommentsWithSameId.push(node); } }); }); - if (allCommentsWithSameId.length > 1) { - const minPos = minBy(allCommentsWithSameId, 'pos'); - const maxPos = maxBy(allCommentsWithSameId, 'pos'); + const minPos = minBy(allCommentsWithSameId, 'pos'); + const maxPos = maxBy(allCommentsWithSameId, 'pos'); + if ( + state.selection.from === + maxPos.pos + last(allCommentsWithSameId).node.nodeSize + ) { + state.schema.marks.comment.spec.inclusive = false; + } else { + state.schema.marks.comment.spec.inclusive = true; + } + if (allCommentsWithSameId.length > 1) { return { from: minPos.pos, to: maxPos.pos + last(allCommentsWithSameId).node.nodeSize, diff --git a/wax-prosemirror-plugins/src/trackChanges/FindSelectedChanges.js b/wax-prosemirror-plugins/src/trackChanges/FindSelectedChanges.js index fbeb2339d4f1ca81f528b37c6327d96a43823d5c..14f24e8723b208abd40416d70f214c6fe1c68864 100644 --- a/wax-prosemirror-plugins/src/trackChanges/FindSelectedChanges.js +++ b/wax-prosemirror-plugins/src/trackChanges/FindSelectedChanges.js @@ -1,35 +1,42 @@ -import { getFromToMark } from './helpers'; +import { DocumentHelpers } from 'wax-prosemirror-utilities'; const findSelectedChanges = state => { - const selection = state.selection, - selectedChanges = { - insertion: false, - deletion: false, - formatChange: false, - }; - let insertionPos = false, - deletionPos = false, - formatChangePos = false, - insertionMark, - deletionMark, - formatChangeMark, - insertionSize, - deletionSize, - formatChangeSize; + const { selection } = state; + const selectedChanges = { + insertion: false, + deletion: false, + formatChange: false, + }; + + let insertionPos = false; + let deletionPos = false; + let formatChangePos = false; + let insertionMark; + let deletionMark; + let formatChangeMark; + let insertionSize; + let deletionSize; + let formatChangeSize; if (selection.empty) { - const resolvedPos = state.doc.resolve(selection.from), - marks = resolvedPos.marks(); + const resolvedPos = state.doc.resolve(selection.from); + const marks = resolvedPos.marks(); + if (marks) { - insertionMark = marks.find(mark => mark.type.name === 'insertion' && !mark.attrs.approved); + insertionMark = marks.find( + mark => mark.type.name === 'insertion' && !mark.attrs.approved, + ); + if (insertionMark) { insertionPos = selection.from; } deletionMark = marks.find(mark => mark.type.name === 'deletion'); + if (deletionMark) { deletionPos = selection.from; } formatChangeMark = marks.find(mark => mark.type.name === 'format_change'); + if (formatChangeMark) { formatChangePos = selection.from; } @@ -42,7 +49,9 @@ const findSelectedChanges = state => { if (!insertionMark) { insertionMark = node.attrs.track ? node.attrs.track.find(trackAttr => trackAttr.type === 'insertion') - : node.marks.find(mark => mark.type.name === 'insertion' && !mark.attrs.approved); + : node.marks.find( + mark => mark.type.name === 'insertion' && !mark.attrs.approved, + ); if (insertionMark) { insertionPos = pos; if (!node.isInline) { @@ -62,7 +71,9 @@ const findSelectedChanges = state => { } } if (!formatChangeMark) { - formatChangeMark = node.marks.find(mark => mark.type.name === 'format_change'); + formatChangeMark = node.marks.find( + mark => mark.type.name === 'format_change', + ); if (formatChangeMark) { formatChangePos = pos; if (!node.isInline) { @@ -70,26 +81,31 @@ const findSelectedChanges = state => { } } } + return false; }); } if (insertionMark) { selectedChanges.insertion = insertionSize ? { from: insertionPos, to: insertionPos + insertionSize } - : getFromToMark(state.doc, insertionPos, insertionMark); + : DocumentHelpers.findMarkPosition(state, insertionPos, 'insertion'); } if (deletionMark) { selectedChanges.deletion = deletionSize ? { from: deletionPos, to: deletionPos + deletionSize } - : getFromToMark(state.doc, deletionPos, deletionMark); + : DocumentHelpers.findMarkPosition(state, deletionPos, 'deletion'); } if (formatChangeMark) { selectedChanges.formatChange = formatChangeSize ? { from: formatChangePos, to: formatChangePos + formatChangeSize } - : getFromToMark(state.doc, formatChangePos, formatChangeMark); + : DocumentHelpers.findMarkPosition( + state, + formatChangePos, + 'format_change', + ); } return selectedChanges; }; -export { findSelectedChanges }; +export default findSelectedChanges; diff --git a/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js b/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js index 1190757191de86921ced747c943696e3875c7d2e..5ebf2892bd52dd6e62876c16a8d8751161f5f0e7 100644 --- a/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js +++ b/wax-prosemirror-plugins/src/trackChanges/TrackChangePlugin.js @@ -1,41 +1,15 @@ import { Plugin, PluginKey } from 'prosemirror-state'; import { Decoration, DecorationSet } from 'prosemirror-view'; -import { findSelectedChanges } from './FindSelectedChanges'; -import { deactivateAllSelectedChanges } from './helpers'; +import findSelectedChanges from './FindSelectedChanges'; export const key = new PluginKey('track'); -export const selectedInsertionSpec = {}; -export const selectedDeletionSpec = {}; -export const selectedChangeFormatSpec = {}; -export const selectedChangeBlockSpec = {}; export default options => { return new Plugin({ key, state: { init(config, state) { - const userIds = ['33']; - state.doc.descendants(node => { - if (node.attrs.track) { - node.attrs.track.forEach(track => { - if (!userIds.includes(track.user) && track.user !== 0) { - userIds.push(track.user); - } - }); - } else { - node.marks.forEach(mark => { - if ( - ['deletion', 'insertion', 'format_change'].includes(mark.type.name) && - !userIds.includes(mark.attrs.user) && - mark.attrs.user !== 0 - ) { - userIds.push(mark.attrs.user); - } - }); - } - }); - return { decos: DecorationSet.empty, }; @@ -43,51 +17,61 @@ export default options => { apply(tr, prev, oldState, state) { const meta = tr.getMeta(key); if (meta) { - // There has been an update, return values from meta instead - // of previous values return meta; } - let { decos } = this.getState(oldState); + const { + selection: { from, to }, + } = state; + let { decos } = this.getState(oldState); + decos = DecorationSet.empty; if (tr.selectionSet) { - const { insertion, deletion, formatChange } = findSelectedChanges(state); - decos = DecorationSet.empty; - const decoType = tr.selection.node ? Decoration.node : Decoration.inline; + const { insertion, deletion, formatChange } = findSelectedChanges( + state, + ); + + const decoType = tr.selection.node + ? Decoration.node + : Decoration.inline; + + state.doc.nodesBetween(from, to, (node, pos) => { + if ( + node.attrs.track && + node.attrs.track.find(track => track.type === 'block_change') + ) { + let nodeSize = pos; + node.descendants((childNode, childPos) => { + nodeSize += childNode.nodeSize; + }); + + decos = decos.add(tr.doc, [ + decoType(pos, nodeSize, { + class: 'selected-block-change', + }), + ]); + } + }); + if (insertion) { decos = decos.add(tr.doc, [ - decoType( - insertion.from, - insertion.to, - { - class: 'selected-insertion', - }, - selectedInsertionSpec, - ), + decoType(insertion.from, insertion.to, { + class: 'selected-insertion', + }), ]); } if (deletion) { decos = decos.add(tr.doc, [ - decoType( - deletion.from, - deletion.to, - { - class: 'selected-deletion', - }, - selectedDeletionSpec, - ), + decoType(deletion.from, deletion.to, { + class: 'selected-deletion', + }), ]); } if (formatChange) { decos = decos.add(tr.doc, [ - decoType( - formatChange.from, - formatChange.to, - { - class: 'selected-format-change', - }, - selectedChangeFormatSpec, - ), + decoType(formatChange.from, formatChange.to, { + class: 'selected-format-change', + }), ]); } } else { @@ -105,7 +89,7 @@ export default options => { }, handleDOMEvents: { focus: (view, _event) => { - view.dispatch(deactivateAllSelectedChanges(view.state.tr)); + // view.dispatch(deactivateAllSelectedChanges(view.state.tr)); }, }, }, diff --git a/wax-prosemirror-plugins/src/trackChanges/helpers.js b/wax-prosemirror-plugins/src/trackChanges/helpers.js deleted file mode 100644 index 52065fd0a688f2362862956319697f8e80a2e795..0000000000000000000000000000000000000000 --- a/wax-prosemirror-plugins/src/trackChanges/helpers.js +++ /dev/null @@ -1,72 +0,0 @@ -import {Decoration, DecorationSet} from "prosemirror-view" - -import { - key, - selectedInsertionSpec, - selectedDeletionSpec, - selectedChangeFormatSpec, - selectedChangeBlockSpec -} from "./TrackChangePlugin" - -export function getSelectedChanges(state) { - const {decos} = key.getState(state) - - const insertion = decos.find(undefined, undefined, spec => spec === selectedInsertionSpec)[0], - deletion = decos.find(undefined, undefined, spec => spec === selectedDeletionSpec)[0], - format_change = decos.find(undefined, undefined, spec => spec === selectedChangeFormatSpec)[0], - block_change = decos.find(undefined, undefined, spec => spec === selectedChangeBlockSpec)[0] - - return {insertion, deletion, format_change, block_change} -} - -export function setSelectedChanges(state, type, pos) { - const tr = state.tr, - node = tr.doc.nodeAt(pos), - mark = node.attrs.track ? - node.attrs.track.find(trackAttr => trackAttr.type===type) : - node.marks.find(mark => mark.type.name===type) - if (!mark) { - return - } - const selectedChange = node.isInline ? getFromToMark(tr.doc, pos, mark) : {from: pos, to: pos + node.nodeSize} - let decos = DecorationSet.empty, spec - if (type==='insertion') { - spec = selectedInsertionSpec - } else if (type==='deletion') { - spec = selectedDeletionSpec - } else if (type==='format_change') { - spec = selectedChangeFormatSpec - } else if (type==='block_change') { - spec = selectedChangeBlockSpec - } - const decoType = node.isInline ? Decoration.inline : Decoration.node - decos = decos.add(tr.doc, [decoType(selectedChange.from, selectedChange.to, { - class: `selected-${type}` - }, spec)]) - return tr.setMeta(key, {decos}).setMeta('track', true) -} - -export function deactivateAllSelectedChanges(tr) { - const pluginState = { - decos: DecorationSet.empty - } - return tr.setMeta(key, pluginState).setMeta('track', true) -} - -// From https://discuss.prosemirror.net/t/expanding-the-selection-to-the-active-mark/478/2 with some bugs fixed -export function getFromToMark(doc, pos, mark) { - const $pos = doc.resolve(pos), parent = $pos.parent - const start = parent.childAfter($pos.parentOffset) - if (!start.node) { - return null - } - let startIndex = $pos.index(), startPos = $pos.start() + start.offset - while (startIndex > 0 && mark.isInSet(parent.child(startIndex - 1).marks)) { - startPos -= parent.child(--startIndex).nodeSize - } - let endIndex = $pos.index() + 1, endPos = $pos.start() + start.offset + start.node.nodeSize - while (endIndex < parent.childCount && mark.isInSet(parent.child(endIndex).marks)) { - endPos += parent.child(endIndex++).nodeSize - } - return {from: startPos, to: endPos} -} diff --git a/wax-prosemirror-services/src/NoteService/Note.js b/wax-prosemirror-services/src/NoteService/Note.js index c86840e98279c6f880ca42d40250a9e493e3d5fa..80dc15f5d6aea9887622f4a274b816976bf829b7 100644 --- a/wax-prosemirror-services/src/NoteService/Note.js +++ b/wax-prosemirror-services/src/NoteService/Note.js @@ -22,7 +22,7 @@ class Note extends Tools { ); const footnote = state.config.schema.nodes.footnote.create( { id: uuidv4() }, - content, + Fragment.empty, ); dispatch(state.tr.replaceSelectionWith(footnote)); }; diff --git a/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js b/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js index 210c80cb93dec14f3c6c3d77449050694cc9811d..2370331b4a262d70b71879fabcfc58ed803dcdfa 100644 --- a/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js +++ b/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js @@ -1,4 +1,5 @@ import { injectable } from 'inversify'; +import { minBy, maxBy } from 'lodash'; import { keymap } from 'prosemirror-keymap'; import { undo, redo } from 'prosemirror-history'; @@ -42,6 +43,7 @@ const redoShortCut = (state, dispatch, view) => class ShortCuts { constructor(plugins, schema) { this.insertBreak = this.insertBreak.bind(this); + this.pressEnter = this.pressEnter.bind(this); this.insertRule = this.insertRule.bind(this); this.PmPlugins = plugins; this.schema = schema; @@ -54,6 +56,16 @@ class ShortCuts { return true; } + pressEnter(state, dispatch) { + // LISTS + if (splitListItem(this.schema.nodes.list_item)(state)) { + splitListItem(this.schema.nodes.list_item)(state, dispatch); + return true; + } + + return false; + } + insertRule(state, dispatch) { const hr = this.schema.nodes.horizontal_rule.create(); dispatch(state.tr.replaceSelectionWith(hr).scrollIntoView()); @@ -94,7 +106,7 @@ class ShortCuts { 'Mod-_': this.insertRule, 'Mod-[': liftListItem(this.schema.nodes.list_item), 'Mod-]': sinkListItem(this.schema.nodes.list_item), - Enter: splitListItem(this.schema.nodes.list_item), + Enter: this.pressEnter, 'Shift-Ctrl-8': wrapInList(this.schema.nodes.bulletlist), 'Shift-Ctrl-9': wrapInList(this.schema.nodes.orderedlist), }; diff --git a/wax-prosemirror-services/src/TrackChangeService/RejectTrackChangeService/RejectTrackChange.js b/wax-prosemirror-services/src/TrackChangeService/RejectTrackChangeService/RejectTrackChange.js index e7edca812ed8d376e08d39bdbec748ea4e5f3b87..113bdec11bea9fe88bef3493caaf66a06184ab06 100644 --- a/wax-prosemirror-services/src/TrackChangeService/RejectTrackChangeService/RejectTrackChange.js +++ b/wax-prosemirror-services/src/TrackChangeService/RejectTrackChangeService/RejectTrackChange.js @@ -89,6 +89,23 @@ class RejectTrackChange extends Tools { 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); diff --git a/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js b/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js index cc9ca50699f2de70e3e085190679ac8a22b112be..b52026a3ee3e0ca9001238d89b10fc1195ccd305 100644 --- a/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js +++ b/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js @@ -1,9 +1,14 @@ import { trackChangesMarks, trackChangesNodes } from 'wax-prosemirror-schema'; +import { TrackChangePlugin } from 'wax-prosemirror-plugins'; import Service from '../Service'; import TrackChangeServices from './index'; +const PLUGIN_KEY = 'trackChngePlugin'; + class TrackChangeService extends Service { - boot() {} + boot() { + this.app.PmPlugins.add(PLUGIN_KEY, TrackChangePlugin(PLUGIN_KEY)); + } register() { const createMark = this.container.get('CreateMark'); diff --git a/wax-prosemirror-utilities/src/document/DocumentHelpers.js b/wax-prosemirror-utilities/src/document/DocumentHelpers.js index 02d6cbfbd4f26eced8bcc24bfbe19ecce9efc770..3815010c85fd29af77fe2d748892895315e89946 100644 --- a/wax-prosemirror-utilities/src/document/DocumentHelpers.js +++ b/wax-prosemirror-utilities/src/document/DocumentHelpers.js @@ -96,27 +96,34 @@ const findAllMarksWithSameId = (state, mark) => { return allMarksWithSameId; }; -const findMarkPosition = (activeView, initialPos, markType) => { - const $pos = activeView.state.tr.doc.resolve(initialPos); +// From https://discuss.prosemirror.net/t/expanding-the-selection-to-the-active-mark/478/2 + +const findMarkPosition = (state, initialPos, markType) => { + const $pos = state.tr.doc.resolve(initialPos); const { parent } = $pos; const start = parent.childAfter($pos.parentOffset); if (!start.node) return null; const actualMark = start.node.marks.find(mark => mark.type.name === markType); + let startIndex = $pos.index(); let startPos = $pos.start() + start.offset; while ( startIndex > 0 && actualMark.isInSet(parent.child(startIndex - 1).marks) - ) + ) { startPos -= parent.child(--startIndex).nodeSize; - let endIndex = $pos.indexAfter(); - let endPos = startPos + start.node.nodeSize; + } + let endIndex = $pos.index() + 1; + let endPos = $pos.start() + start.offset + start.node.nodeSize; + while ( - endPos < parent.childCount && + endIndex < parent.childCount && actualMark.isInSet(parent.child(endIndex).marks) - ) + ) { endPos += parent.child(endIndex++).nodeSize; - return { from: startPos, to: endPos }; + } + + return { from: startPos, to: endPos, attrs: actualMark.attrs }; }; export const flatten = (node, descend = true) => {