diff --git a/editors/editoria/src/Editoria.js b/editors/editoria/src/Editoria.js index feb57c6796d0fbc6cf6ca9bd1ae1a4ead25eae15..375f028f56c3ae01ad17ec58dcb25e268d697170 100644 --- a/editors/editoria/src/Editoria.js +++ b/editors/editoria/src/Editoria.js @@ -47,9 +47,9 @@ const Editoria = () => ( autoFocus placeholder="Type Something..." fileUpload={file => renderImage(file)} - value={"this is some content"} + value={`<ul><li><p class="paragraph">this</p></li><li><p class="paragraph">that</p></li></ul><p class="paragraph">and a paragraph</p><p>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 08f4a99e4b4e8693e399a3f67fbd3b011f91e81c..1e332ab95a60a13c5cc0f106f004e975dd78713d 100644 --- a/editors/editoria/src/config/config.js +++ b/editors/editoria/src/config/config.js @@ -56,12 +56,15 @@ export default { PmPlugins: [columnResizing(), tableEditing(), invisibles([hardBreak()])], + // Always load first CommentsService and LinkService, + //as it matters on how PM treats nodes and marks services: [ + new CommentsService(), + new LinkService(), new PlaceholderService(), new ImageService(), new ListsService(), new InlineAnnotationsService(), - new LinkService(), new TablesService(), new TextBlockLevelService(), new BaseService(), @@ -75,7 +78,6 @@ export default { new AnnotationToolGroupService(), new NoteToolGroupService(), new ListToolGroupService(), - new TrackChangeService(), - new CommentsService() + new TrackChangeService() ] }; diff --git a/wax-prosemirror-components/src/components/HeadingsDropDown.js b/wax-prosemirror-components/src/components/HeadingsDropDown.js index 9de03389a084e87ae5fe7e985017909968ba7b25..d4fdcaea747f914ccf1329ba587303778e7d4258 100644 --- a/wax-prosemirror-components/src/components/HeadingsDropDown.js +++ b/wax-prosemirror-components/src/components/HeadingsDropDown.js @@ -1,6 +1,6 @@ import React from "react"; import styled from "styled-components"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; import Dropdown from "react-dropdown"; import "react-dropdown/style.css"; @@ -21,10 +21,9 @@ const HeadingsDropDown = ({ dispatch, state, item }) => ( <DropdownStyled options={dropDownOptions} onChange={option => { - setBlockType(state.config.schema.nodes.heading, { level: option.value })( - state, - dispatch - ); + Commands.setBlockType(state.config.schema.nodes.heading, { + level: option.value + })(state, dispatch); }} placeholder="Choose heading" select={item.select && item.select(state)} diff --git a/wax-prosemirror-components/src/components/rightArea/RightArea.js b/wax-prosemirror-components/src/components/rightArea/RightArea.js index 910204385638f5d7c15c58e66dcbd4f8abda60f9..9152588481c41eb9ce9922c36ed26e7b16aad73f 100644 --- a/wax-prosemirror-components/src/components/rightArea/RightArea.js +++ b/wax-prosemirror-components/src/components/rightArea/RightArea.js @@ -10,7 +10,7 @@ import styled from "styled-components"; import { WaxContext } from "wax-prosemirror-core"; import { DocumentHelpers } from "wax-prosemirror-utilities"; import CommentsBoxList from "./../comments/CommentsBoxList"; -import { each } from "lodash"; +import { each, uniqBy } from "lodash"; export default ({ area }) => { const { view: { main }, app, activeView } = useContext(WaxContext); @@ -133,23 +133,31 @@ export default ({ area }) => { const updateMarks = view => { if (view) { - const commentNodes = DocumentHelpers.findChildrenByMark( - view.state.doc, - view.state.schema.marks.comment, - true - ); - const allComments = commentNodes.map(node => { - return node.node.marks.filter(comment => { - return comment.type.name === "comment"; - }); + const allBlockNodes = DocumentHelpers.findBlockNodes(view.state.doc); + const allInlineNodes = DocumentHelpers.findInlineNodes(view.state.doc); + const finalMarks = []; + + allInlineNodes.map(node => { + if (node.node.marks.length > 0) { + node.node.marks.filter(mark => { + if ( + mark.type.name === "comment" || + mark.type.name === "insertion" || + mark.type.name === "deletion" || + mark.type.name === "format_change" + ) { + finalMarks.push(mark); + } + }); + } }); const groupedNodes = {}; - allComments.forEach(comment => { - if (!groupedNodes[comment[0].attrs.group]) { - groupedNodes[comment[0].attrs.group] = [comment[0]]; + uniqBy(finalMarks, "attrs.id").forEach(mark => { + if (!groupedNodes[mark.attrs.group]) { + groupedNodes[mark.attrs.group] = [mark]; } else { - groupedNodes[comment[0].attrs.group].push(comment[0]); + groupedNodes[mark.attrs.group].push(mark); } }); return groupedNodes; diff --git a/wax-prosemirror-core/src/Wax.js b/wax-prosemirror-core/src/Wax.js index 825a00e273284b7605a2704fd6ef85aeda1e64d6..276b44ac22562b9414742d079a2b9ccb4cc90386 100644 --- a/wax-prosemirror-core/src/Wax.js +++ b/wax-prosemirror-core/src/Wax.js @@ -31,9 +31,10 @@ const serializer = schema => { }; }; +let schema; const createApplication = props => { const application = Application.create(props); - application.getSchema(); + schema = application.getSchema(); application.bootServices(); return application; }; @@ -50,7 +51,6 @@ const LayoutWrapper = styled.div` const Wax = props => { let finalPlugins; - let schema; const [application, setApplication] = useState(); useEffect(() => { @@ -74,8 +74,6 @@ const Wax = props => { if (!application) return null; - schema = application.getSchema(); - const WaxOnchange = onChange ? onChange : value => true; const editorContent = value ? value : ""; diff --git a/wax-prosemirror-schema/src/marks/commentMark.js b/wax-prosemirror-schema/src/marks/commentMark.js index e297644ddde7454bc129cafe3bea4ad3cb3ee16b..cb155e807f9000758eae465845578af44ebbe3f4 100644 --- a/wax-prosemirror-schema/src/marks/commentMark.js +++ b/wax-prosemirror-schema/src/marks/commentMark.js @@ -1,33 +1,36 @@ const comment = { attrs: { + class: { default: "comment" }, id: { default: "" }, group: { default: "" }, conversation: [] }, inclusive: false, - // excludes: "", parseDOM: [ { - tag: "span.comment[data-conversation]", - getAttrs(dom) { - return { - id: dom.dataset.id, - group: dom.dataset.group, - conversation: JSON.parse(dom.dataset.conversation) - }; + tag: "span.comment", + getAttrs(hook, next) { + Object.assign(hook, { + class: hook.dom.getAttribute("class"), + id: hook.dom.dataset.id, + group: hook.dom.dataset.group, + conversation: hook.dom.dataset.conversation + }); + next(); } } ], - toDOM(node) { - return [ + toDOM(hook, next) { + hook.value = [ "span", { - class: "comment", - "data-id": node.attrs.id, - "data-group": node.attrs.group, - "data-conversation": JSON.stringify(node.attrs.conversation) + class: hook.node.attrs.class, + "data-id": hook.node.attrs.id, + "data-conversation": JSON.stringify(hook.node.attrs.conversation), + "data-group": hook.node.attrs.group } ]; + next(); } }; diff --git a/wax-prosemirror-schema/src/marks/trackChangesMarks/deletionMark.js b/wax-prosemirror-schema/src/marks/trackChangesMarks/deletionMark.js index 1de64cb023ee6bc9cf21e91ef49fdd737b717645..d96ad4811ed6943c9074ef51436467ef8c7d46df 100644 --- a/wax-prosemirror-schema/src/marks/trackChangesMarks/deletionMark.js +++ b/wax-prosemirror-schema/src/marks/trackChangesMarks/deletionMark.js @@ -1,5 +1,6 @@ const deletion = { attrs: { + class: { default: "deletion" }, id: { default: "" }, user: { default: 0 }, username: { default: "" }, @@ -11,29 +12,32 @@ const deletion = { parseDOM: [ { tag: "span.deletion", - getAttrs(dom) { - return { - id: dom.dataset.id, - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date), - group: dom.dataset.group - }; + getAttrs(hook, next) { + Object.assign(hook, { + class: hook.dom.getAttribute("class"), + id: hook.dom.dataset.id, + user: parseInt(hook.dom.dataset.user), + username: hook.dom.dataset.username, + date: parseInt(hook.dom.dataset.date), + group: hook.dom.dataset.group + }); + next(); } } ], - toDOM(node) { - return [ + toDOM(hook, next) { + hook.value = [ "span", { - class: `deletion user-${node.attrs.user}`, - "data-id": node.attrs.id, - "data-user": node.attrs.user, - "data-username": node.attrs.username, - "data-date": node.attrs.date, - "data-group": node.attrs.group + class: hook.node.attrs.class, + "data-id": hook.node.attrs.id, + "data-user": hook.node.attrs.user, + "data-username": hook.node.attrs.username, + "data-date": hook.node.attrs.date, + "data-group": hook.node.attrs.group } ]; + next(); } }; export default deletion; diff --git a/wax-prosemirror-schema/src/marks/trackChangesMarks/formatChangeMark.js b/wax-prosemirror-schema/src/marks/trackChangesMarks/formatChangeMark.js index 5fc79a00c48462208ed9e68150d36253f0f09b9c..633578c39156605c6f7cdae3f18e38749f3c0a06 100644 --- a/wax-prosemirror-schema/src/marks/trackChangesMarks/formatChangeMark.js +++ b/wax-prosemirror-schema/src/marks/trackChangesMarks/formatChangeMark.js @@ -1,5 +1,8 @@ +import { SchemaHelpers } from "wax-prosemirror-utilities"; + const format_change = { attrs: { + class: { default: "format-change" }, id: { default: "" }, user: { default: 0 }, username: { default: "" }, @@ -13,33 +16,36 @@ const format_change = { parseDOM: [ { tag: "span.format-change", - getAttrs(dom) { - return { - id: dom.dataset.id, - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date), - before: parseFormatList(dom.dataset.before), - after: parseFormatList(dom.dataset.after), - group: dom.dataset.group - }; + getAttrs(hook, next) { + Object.assign(hook, { + class: hook.dom.getAttribute("class"), + id: hook.dom.dataset.id, + user: parseInt(hook.dom.dataset.user), + username: hook.dom.dataset.username, + date: parseInt(hook.dom.dataset.date), + before: SchemaHelpers.parseFormatList(hook.dom.dataset.before), + after: SchemaHelpers.parseFormatList(hook.dom.dataset.after), + group: hook.dom.dataset.group + }); + next(); } } ], - toDOM(node) { - return [ + toDOM(hook, next) { + hook.value = [ "span", { - class: `format-change user-${node.attrs.user}`, - "data-id": node.attrs.id, - "data-user": node.attrs.user, - "data-username": node.attrs.username, - "data-date": node.attrs.date, - "data-before": JSON.stringify(node.attrs.before), - "data-after": JSON.stringify(node.attrs.after), - "data-group": node.attrs.group + class: hook.node.attrs.class, + "data-id": hook.node.attrs.id, + "data-user": hook.node.attrs.user, + "data-username": hook.node.attrs.username, + "data-date": hook.node.attrs.date, + "data-before": JSON.stringify(hook.node.attrs.before), + "data-after": JSON.stringify(hook.node.attrs.after), + "data-group": hook.node.attrs.group } ]; + next(); } }; diff --git a/wax-prosemirror-schema/src/marks/trackChangesMarks/insertionMark.js b/wax-prosemirror-schema/src/marks/trackChangesMarks/insertionMark.js index 945d52ea756b773020d1c7692f4596446a95b747..22330a58a636855687c57335c35bb7d93360ae10 100644 --- a/wax-prosemirror-schema/src/marks/trackChangesMarks/insertionMark.js +++ b/wax-prosemirror-schema/src/marks/trackChangesMarks/insertionMark.js @@ -1,10 +1,10 @@ const insertion = { attrs: { + class: { default: "insertion" }, id: { default: "" }, user: { default: 0 }, username: { default: "" }, date: { default: 0 }, - approved: { default: true }, group: { default: "" } }, inclusive: false, @@ -12,47 +12,32 @@ const insertion = { parseDOM: [ { tag: "span.insertion", - getAttrs(dom) { - return { - id: dom.dataset.id, - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date), - inline: true, - approved: false, - group: dom.dataset.group - }; - } - }, - { - tag: "span.approved-insertion", - getAttrs(dom) { - return { - "data-id": node.attrs.id, - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date), - inline: true, - approved: true, - group: dom.dataset.group - }; + getAttrs(hook, next) { + Object.assign(hook, { + class: hook.dom.getAttribute("class"), + id: hook.dom.dataset.id, + user: parseInt(hook.dom.dataset.user), + username: hook.dom.dataset.username, + date: parseInt(hook.dom.dataset.date), + group: hook.dom.dataset.group + }); + next(); } } ], - toDOM(node) { - return [ + toDOM(hook, next) { + hook.value = [ "span", { - class: node.attrs.approved - ? "approved-insertion" - : `insertion user-${node.attrs.user}`, - "data-id": node.attrs.id, - "data-user": node.attrs.user, - "data-username": node.attrs.username, - "data-date": node.attrs.date, - "data-group": node.attrs.group + class: hook.node.attrs.class, + "data-id": hook.node.attrs.id, + "data-user": hook.node.attrs.user, + "data-username": hook.node.attrs.username, + "data-date": hook.node.attrs.date, + "data-group": hook.node.attrs.group } ]; + next(); } }; diff --git a/wax-prosemirror-schema/src/nodes/bulletListNode.js b/wax-prosemirror-schema/src/nodes/bulletListNode.js index 2fd0744dc914077bc7570242392f9022e5642cc1..ed09e842d8ca4cdc30369bd79f96a20a9d85d7de 100644 --- a/wax-prosemirror-schema/src/nodes/bulletListNode.js +++ b/wax-prosemirror-schema/src/nodes/bulletListNode.js @@ -1,3 +1,5 @@ +import { SchemaHelpers } from "wax-prosemirror-utilities"; + const bulletlist = { group: "block", content: "list_item+", @@ -9,7 +11,7 @@ const bulletlist = { tag: "ul", getAttrs(hook, next) { Object.assign(hook, { - track: parseTracks(hook.dom.dataset.track) + track: SchemaHelpers.parseTracks(hook.dom.dataset.track) }); next(); } diff --git a/wax-prosemirror-services/src/BaseService/RedoService/Redo.js b/wax-prosemirror-services/src/BaseService/RedoService/Redo.js index f01a0aa71d69d3490e7e5a0c80ab390c904bccaf..9407f7f5bb872c317f47f10a36fd19c779595e6c 100644 --- a/wax-prosemirror-services/src/BaseService/RedoService/Redo.js +++ b/wax-prosemirror-services/src/BaseService/RedoService/Redo.js @@ -10,7 +10,10 @@ export default class Redo extends Tools { onlyOnMain = true; get run() { - return redo; + return (state, dispatch) => { + const { tr } = state; + redo(state, tr => dispatch(tr.setMeta("inputType", "historyRedo"))); + }; } get enable() { diff --git a/wax-prosemirror-services/src/BaseService/UndoService/Undo.js b/wax-prosemirror-services/src/BaseService/UndoService/Undo.js index 2d234e1d840dd1c48db434cdb8322694b20366e8..5e374ad9ee91953d5d3e514d556b5a71b5582459 100644 --- a/wax-prosemirror-services/src/BaseService/UndoService/Undo.js +++ b/wax-prosemirror-services/src/BaseService/UndoService/Undo.js @@ -10,7 +10,10 @@ export default class Undo extends Tools { onlyOnMain = true; get run() { - return undo; + return (state, dispatch) => { + const { tr } = state; + undo(state, tr => dispatch(tr.setMeta("inputType", "historyUndo"))); + }; } get enable() { diff --git a/wax-prosemirror-services/src/CommentsService/CommentsService.js b/wax-prosemirror-services/src/CommentsService/CommentsService.js index 96d9ef630a477dbbfcfa9fae7f352f6327a16ccb..1a35255163621987ac19d1abbf8f56c9d78f7fd5 100644 --- a/wax-prosemirror-services/src/CommentsService/CommentsService.js +++ b/wax-prosemirror-services/src/CommentsService/CommentsService.js @@ -29,8 +29,11 @@ export default class CommentsService extends Service { register() { const createMark = this.container.get("CreateMark"); - createMark({ - comment: commentMark - }); + createMark( + { + comment: commentMark + }, + { toWaxSchema: true } + ); } } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/AuthorService/Author.js b/wax-prosemirror-services/src/DisplayBlockLevel/AuthorService/Author.js index 6be138dd84d3867dcf35a138f1efc71f55025856..6276577b8c6f42172801ffaa7590ee2643e04d35 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/AuthorService/Author.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/AuthorService/Author.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class Author extends Tools { @@ -9,13 +9,15 @@ export default class Author extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.author)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.author, { + class: "author" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.author)(state); + return Commands.setBlockType(state.config.schema.nodes.author)(state); }; } } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphPoetryService/EpigraphPoetry.js b/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphPoetryService/EpigraphPoetry.js index e3d63856ad2c61fd6adf2e4ed0d59a62ccb49597..ad74a932ee5310ffb8ac744f88dc32115edf98ea 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphPoetryService/EpigraphPoetry.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphPoetryService/EpigraphPoetry.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class EpigraphPoetry extends Tools { @@ -9,13 +9,17 @@ export default class EpigraphPoetry extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.epigraphPoetry)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.epigraphPoetry, { + class: "epigraph-poetry" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.epigraphPoetry)(state); + return Commands.setBlockType(state.config.schema.nodes.epigraphPoetry)( + state + ); }; } } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphProseService/EpigraphProse.js b/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphProseService/EpigraphProse.js index 8347f6bcba19e6d9775570556621f413bf7bdb68..0b800c069e6386d6de6f96b4279e0eabf6c0391d 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphProseService/EpigraphProse.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/EpigraphProseService/EpigraphProse.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class EpigraphProse extends Tools { @@ -9,13 +9,17 @@ export default class EpigraphProse extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.epigraphProse)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.epigraphProse, { + class: "epigraph-prose" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.epigraphProse)(state); + return Commands.setBlockType(state.config.schema.nodes.epigraphProse)( + state + ); }; } } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading1.js b/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading1.js index 8ad2dc6815cae6e8a5f17475c58c0cbfefdde9df..7cd58aec6fc8c3d468438f110ead120108d5b0a4 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading1.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading1.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class Heading1 extends Tools { @@ -9,7 +9,7 @@ export default class Heading1 extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.heading, { level: 1 })( + Commands.setBlockType(state.config.schema.nodes.heading, { level: 1 })( state, dispatch ); @@ -18,9 +18,8 @@ export default class Heading1 extends Tools { get enable() { return state => { - return setBlockType(state.config.schema.nodes.heading, { - level: 1, - track: [] + return Commands.setBlockType(state.config.schema.nodes.heading, { + level: 1 })(state); }; } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading2.js b/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading2.js index 80a7b88e85be051e845f935af071e30ea4d22e20..77bc4d4eacc65e9d0c247573791bf91023312807 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading2.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading2.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class Heading2 extends Tools { @@ -9,7 +9,7 @@ export default class Heading2 extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.heading, { level: 2 })( + Commands.setBlockType(state.config.schema.nodes.heading, { level: 2 })( state, dispatch ); @@ -18,9 +18,8 @@ export default class Heading2 extends Tools { get enable() { return state => { - return setBlockType(state.config.schema.nodes.heading, { - level: 2, - track: [] + return Commands.setBlockType(state.config.schema.nodes.heading, { + level: 2 })(state); }; } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading3.js b/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading3.js index 8163289fb5ed31ef2f65eea71069b16779202170..bf27f4bcef78b858d9b65eea8f79a0d51fe9fd2a 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading3.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/HeadingService/Heading3.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class Heading3 extends Tools { @@ -9,7 +9,7 @@ export default class Heading3 extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.heading, { level: 3 })( + Commands.setBlockType(state.config.schema.nodes.heading, { level: 3 })( state, dispatch ); @@ -18,9 +18,8 @@ export default class Heading3 extends Tools { get enable() { return state => { - return setBlockType(state.config.schema.nodes.heading, { - level: 3, - track: [] + return Commands.setBlockType(state.config.schema.nodes.heading, { + level: 3 })(state); }; } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/SubTitleService/SubTitle.js b/wax-prosemirror-services/src/DisplayBlockLevel/SubTitleService/SubTitle.js index caac1427b53f761996822dabc87b6173bcf65b36..332d816d9aee15c4e1d616d2382d500875954cb1 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/SubTitleService/SubTitle.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/SubTitleService/SubTitle.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class SubTitle extends Tools { @@ -9,13 +9,15 @@ export default class SubTitle extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.subtitle)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.subtitle, { + class: "cst" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.subtitle)(state); + return Commands.setBlockType(state.config.schema.nodes.subtitle)(state); }; } } diff --git a/wax-prosemirror-services/src/DisplayBlockLevel/TitleService/Title.js b/wax-prosemirror-services/src/DisplayBlockLevel/TitleService/Title.js index 9f77b510ceca3a802dc4ee14dca643ac5fa669bc..469096c151095cef8c20dee2ffd9ebeac9acc39c 100644 --- a/wax-prosemirror-services/src/DisplayBlockLevel/TitleService/Title.js +++ b/wax-prosemirror-services/src/DisplayBlockLevel/TitleService/Title.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class Title extends Tools { @@ -9,13 +9,15 @@ export default class Title extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.title)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.title, { + class: "title" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.title)(state); + return Commands.setBlockType(state.config.schema.nodes.title)(state); }; } } diff --git a/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js b/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js index 63f28ab0a9cf9831f4b0d299b51e6c36830b1022..dc8d0b63dedb1678b81f286fcc00bf8a08e89661 100644 --- a/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js +++ b/wax-prosemirror-services/src/ShortCutsService/ShortCuts.js @@ -15,9 +15,31 @@ import { setBlockType, chainCommands, exitCode, - selectParentNode + selectParentNode, + joinBackward, + selectNodeBackward, + deleteSelection } from "prosemirror-commands"; +const backSpace = chainCommands( + deleteSelection, + joinBackward, + selectNodeBackward +); + +const backSpaceShortCut = (state, dispatch, view) => + backSpace( + state, + tr => dispatch(tr.setMeta("inputType", "deleteContentBackward")), + view + ); + +const undoShortCut = (state, dispatch, view) => + undo(state, tr => dispatch(tr.setMeta("inputType", "historyUndo")), view); + +const redoShortCut = (state, dispatch, view) => + redo(state, tr => dispatch(tr.setMeta("inputType", "historyRedo")), view); + @injectable() class ShortCuts { constructor(plugins, schema) { @@ -63,10 +85,10 @@ class ShortCuts { getKeys() { return { - "Mod-z": undo, - "Shift-Mod-z": redo, - Backspace: undoInputRule, - "Mod-y": redo, + "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), diff --git a/wax-prosemirror-services/src/TextBlockLevel/ExtractPoetryService/ExtractPoetry.js b/wax-prosemirror-services/src/TextBlockLevel/ExtractPoetryService/ExtractPoetry.js index f3507f5d87bb4fdb95ed926d34ccb8bd9c2a1eba..d4c87d7f53c796d5f07077a0a40fd555a083e409 100644 --- a/wax-prosemirror-services/src/TextBlockLevel/ExtractPoetryService/ExtractPoetry.js +++ b/wax-prosemirror-services/src/TextBlockLevel/ExtractPoetryService/ExtractPoetry.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() class ExtractPoetry extends Tools { @@ -9,13 +9,17 @@ class ExtractPoetry extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.extractPoetry)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.extractPoetry, { + class: "extract-poetry" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.extractPoetry)(state); + return Commands.setBlockType(state.config.schema.nodes.extractPoetry)( + state + ); }; } } diff --git a/wax-prosemirror-services/src/TextBlockLevel/ExtractProseService/ExtractProse.js b/wax-prosemirror-services/src/TextBlockLevel/ExtractProseService/ExtractProse.js index 261cfe18de1fd51b7b63a0c5376a6fa338bfb281..e887b90542a3012cbf8e269f719a1c119c0a4ed0 100644 --- a/wax-prosemirror-services/src/TextBlockLevel/ExtractProseService/ExtractProse.js +++ b/wax-prosemirror-services/src/TextBlockLevel/ExtractProseService/ExtractProse.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() class ExtractProse extends Tools { @@ -9,13 +9,17 @@ class ExtractProse extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.extractProse)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.extractProse, { + class: "extract-prose" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.extractProse)(state); + return Commands.setBlockType(state.config.schema.nodes.extractProse)( + state + ); }; } } diff --git a/wax-prosemirror-services/src/TextBlockLevel/ParagraphContinuedService/ParagraphContinued.js b/wax-prosemirror-services/src/TextBlockLevel/ParagraphContinuedService/ParagraphContinued.js index bcc3c506e807d35422df6cf9fb47621c908c1575..0821ef32b4390949cb99a1452a6c43b97d775f1d 100644 --- a/wax-prosemirror-services/src/TextBlockLevel/ParagraphContinuedService/ParagraphContinued.js +++ b/wax-prosemirror-services/src/TextBlockLevel/ParagraphContinuedService/ParagraphContinued.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() class ParagraphContinued extends Tools { @@ -9,13 +9,17 @@ class ParagraphContinued extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.paragraphCont)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.paragraphCont, { + class: "paragraph-cont" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.paragraphCont)(state); + return Commands.setBlockType(state.config.schema.nodes.paragraphCont)( + state + ); }; } } diff --git a/wax-prosemirror-services/src/TextBlockLevel/ParagraphService/Paragraph.js b/wax-prosemirror-services/src/TextBlockLevel/ParagraphService/Paragraph.js index b4480b8d9767db366ec8c4ba88dc87ee389bd8c4..e3018a4e2bcf78c83f10d8c8480cdec20345178f 100644 --- a/wax-prosemirror-services/src/TextBlockLevel/ParagraphService/Paragraph.js +++ b/wax-prosemirror-services/src/TextBlockLevel/ParagraphService/Paragraph.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() export default class Paragraph extends Tools { @@ -9,13 +9,15 @@ export default class Paragraph extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.paragraph)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.paragraph, { + class: "paragraph" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.paragraph)(state); + return Commands.setBlockType(state.config.schema.nodes.paragraph)(state); }; } } diff --git a/wax-prosemirror-services/src/TextBlockLevel/SourceNoteService/SourceNote.js b/wax-prosemirror-services/src/TextBlockLevel/SourceNoteService/SourceNote.js index 5e05eea06c5494e8ef41034841d395b82be6bbf5..3e333e8dd142d1c4906db3e38f43db467a7bfb62 100644 --- a/wax-prosemirror-services/src/TextBlockLevel/SourceNoteService/SourceNote.js +++ b/wax-prosemirror-services/src/TextBlockLevel/SourceNoteService/SourceNote.js @@ -1,6 +1,6 @@ import Tools from "../../lib/Tools"; import { injectable } from "inversify"; -import { setBlockType } from "prosemirror-commands"; +import { Commands } from "wax-prosemirror-utilities"; @injectable() class SourceNote extends Tools { @@ -9,13 +9,15 @@ class SourceNote extends Tools { get run() { return (state, dispatch) => { - setBlockType(state.config.schema.nodes.sourceNote)(state, dispatch); + Commands.setBlockType(state.config.schema.nodes.sourceNote, { + class: "source-note" + })(state, dispatch); }; } get enable() { return state => { - return setBlockType(state.config.schema.nodes.sourceNote)(state); + return Commands.setBlockType(state.config.schema.nodes.sourceNote)(state); }; } } diff --git a/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js b/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js index 0cb3937abb94072aca55ada25b919c8dce1b30e3..5d642d6d6148b9e1b7a3c34b00de292fde726724 100644 --- a/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js +++ b/wax-prosemirror-services/src/TrackChangeService/TrackChangeService.js @@ -9,9 +9,12 @@ class TrackChangeService extends Service { const createNode = this.container.get("CreateNode"); Object.keys(trackChangesMarks).forEach(mark => { - createMark({ - [mark]: trackChangesMarks[mark] - }); + createMark( + { + [mark]: trackChangesMarks[mark] + }, + { toWaxSchema: true } + ); }); Object.keys(trackChangesNodes).forEach(node => { diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/addMarkStep.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/addMarkStep.js new file mode 100644 index 0000000000000000000000000000000000000000..f5031d00cdf72939c2696a74cec017ace1e5683f --- /dev/null +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/addMarkStep.js @@ -0,0 +1,60 @@ +const addMarkStep = (state, tr, step, newTr, map, doc, user, date) => { + doc.nodesBetween(step.from, step.to, (node, pos) => { + if (!node.isInline) { + return true; + } + if (node.marks.find(mark => mark.type.name === "deletion")) { + return false; + } else { + newTr.addMark( + Math.max(step.from, pos), + Math.min(step.to, pos + node.nodeSize), + step.mark + ); + } + if ( + ["em", "strong", "underline"].includes(step.mark.type.name) && + !node.marks.find(mark => mark.type === step.mark.type) + ) { + const formatChangeMark = node.marks.find( + mark => mark.type.name === "format_change" + ); + let after, before; + if (formatChangeMark) { + if (formatChangeMark.attrs.before.includes(step.mark.type.name)) { + before = formatChangeMark.attrs.before.filter( + markName => markName !== step.mark.type.name + ); + after = formatChangeMark.attrs.after; + } else { + before = formatChangeMark.attrs.before; + after = formatChangeMark.attrs.after.concat(step.mark.type.name); + } + } else { + before = []; + after = [step.mark.type.name]; + } + if (after.length || before.length) { + newTr.addMark( + Math.max(step.from, pos), + Math.min(step.to, pos + node.nodeSize), + state.schema.marks.format_change.create({ + user: user.userId, + username: user.username, + date, + before, + after + }) + ); + } else if (formatChangeMark) { + newTr.removeMark( + Math.max(step.from, pos), + Math.min(step.to, pos + node.nodeSize), + formatChangeMark + ); + } + } + }); +}; + +export default addMarkStep; diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/markDeletion.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markDeletion.js similarity index 87% rename from wax-prosemirror-services/src/TrackChangeService/track-changes/markDeletion.js rename to wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markDeletion.js index 9dec3c1bded77bcc339d5ef013591485ce06a35b..b4c76ef12c7d93330e415335a38d6245fc0fdd3f 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/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, username, date1, date10) => { +const markDeletion = (tr, from, to, user, date) => { const deletionMark = tr.doc.type.schema.marks.deletion.create({ - user, - username, - date: date10 + user: user.userId, + username: user.username, + date }); let firstTableCellChild = false; const deletionMap = new Mapping(); @@ -24,9 +24,7 @@ const markDeletion = (tr, from, to, user, username, date1, date10) => { node.isInline && node.marks.find( mark => - mark.type.name === "insertion" && - mark.attrs.user === user && - !mark.attrs.approved + mark.type.name === "insertion" && mark.attrs.user === user.userId ) ) { const removeStep = new ReplaceStep( @@ -53,7 +51,8 @@ const markDeletion = (tr, from, to, user, username, date1, date10) => { ) { if ( node.attrs.track.find( - trackAttr => trackAttr.type === "insertion" && trackAttr.user === user + trackAttr => + trackAttr.type === "insertion" && trackAttr.user === user.userId ) ) { let removeStep; @@ -81,7 +80,12 @@ const markDeletion = (tr, from, to, user, username, date1, date10) => { } } else { const track = node.attrs.track.slice(); - track.push({ type: "deletion", user, username, date: date1 }); + track.push({ + type: "deletion", + user: user.userId, + username: user.username, + date + }); tr.setNodeMarkup( deletionMap.map(pos), null, diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/markInsertion.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markInsertion.js similarity index 73% rename from wax-prosemirror-services/src/TrackChangeService/track-changes/markInsertion.js rename to wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markInsertion.js index de8fc0f84c8b112efbc15b7579168958e1cdbb34..7d06539058e62114dae60b7fd07996bd4c125afb 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/markInsertion.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markInsertion.js @@ -1,20 +1,10 @@ -const markInsertion = ( - tr, - from, - to, - user, - username, - date1, - date10, - approved -) => { +const markInsertion = (tr, from, to, user, date) => { 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, - username, - date: date10, - approved + user: user.userId, + 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. @@ -32,9 +22,14 @@ const markInsertion = ( } if (node.attrs.track) { const track = []; - if (!approved) { - track.push({ type: "insertion", user, username, date: date1 }); - } + + track.push({ + type: "insertion", + user: user.userId, + username: user.username, + date + }); + tr.setNodeMarkup( pos, null, @@ -43,6 +38,7 @@ const markInsertion = ( ); } if (node.type.name === "table") { + // A table was inserted. We don't add track marks to elements inside of it. return false; } }); diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/markWrapping.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markWrapping.js similarity index 67% rename from wax-prosemirror-services/src/TrackChangeService/track-changes/markWrapping.js rename to wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/markWrapping.js index e15856960572a4d2f080101551c63f2ca75e991b..248cc97621204705d94cc1d47102c20fdcc1ccdf 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/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, username, date1) => { +const markWrapping = (tr, pos, oldNode, newNode, user, date) => { let track = oldNode.attrs.track.slice(), blockTrack = track.find(track => track.type === "block_change"); @@ -12,9 +12,9 @@ const markWrapping = (tr, pos, oldNode, newNode, user, username, date1) => { ) { blockTrack = { type: "block_change", - user, - username, - date: date1, + user: user.id, + username: user.username, + date, before: blockTrack.before }; track.push(blockTrack); @@ -22,27 +22,20 @@ const markWrapping = (tr, pos, oldNode, newNode, user, username, date1) => { } else { blockTrack = { type: "block_change", - user, - username, - date: date1, + user: user.id, + username: user.username, + date, before: { type: oldNode.type.name, attrs: oldNode.attrs } }; - - // if (blockTrack.before.attrs.id) { - // delete blockTrack.before.attrs.id; - // } - + if (blockTrack.before.attrs.id) { + delete blockTrack.before.attrs.id; + } if (blockTrack.before.attrs.track) { delete blockTrack.before.attrs.track; } - track.push(blockTrack); } - tr.setNodeMarkup( - pos, - null, - Object.assign({}, newNode.attrs, { id: uuidv4(), track }) - ); + tr.setNodeMarkup(pos, null, Object.assign({}, newNode.attrs, { track })); }; export default markWrapping; diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/removeMarkStep.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/removeMarkStep.js new file mode 100644 index 0000000000000000000000000000000000000000..f53d6af877edd9d7a3fa6a0809e399759cfe71d9 --- /dev/null +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/removeMarkStep.js @@ -0,0 +1,61 @@ +const removeMarkStep = (state, tr, step, newTr, map, doc, user, date) => { + doc.nodesBetween(step.from, step.to, (node, pos) => { + if (!node.isInline) { + return true; + } + if (node.marks.find(mark => mark.type.name === "deletion")) { + return false; + } else { + newTr.removeMark( + Math.max(step.from, pos), + Math.min(step.to, pos + node.nodeSize), + step.mark + ); + } + + if ( + ["em", "strong", "underline"].includes(step.mark.type.name) && + node.marks.find(mark => mark.type === step.mark.type) + ) { + const formatChangeMark = node.marks.find( + mark => mark.type.name === "format_change" + ); + let after, before; + if (formatChangeMark) { + if (formatChangeMark.attrs.after.includes(step.mark.type.name)) { + after = formatChangeMark.attrs.after.filter( + markName => markName !== step.mark.type.name + ); + before = formatChangeMark.attrs.before; + } else { + after = formatChangeMark.attrs.after; + before = formatChangeMark.attrs.before.concat(step.mark.type.name); + } + } else { + after = []; + before = [step.mark.type.name]; + } + if (after.length || before.length) { + newTr.addMark( + Math.max(step.from, pos), + Math.min(step.to, pos + node.nodeSize), + state.schema.marks.format_change.create({ + user: user.userId, + username: user.username, + date, + before, + after + }) + ); + } else if (formatChangeMark) { + newTr.removeMark( + Math.max(step.from, pos), + Math.min(step.to, pos + node.nodeSize), + formatChangeMark + ); + } + } + }); +}; + +export default removeMarkStep; diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceAroundStep.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceAroundStep.js new file mode 100644 index 0000000000000000000000000000000000000000..fd4aca5161e7eca571b91f2cbb46fca259c7ea07 --- /dev/null +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceAroundStep.js @@ -0,0 +1,58 @@ +import markDeletion from "./markDeletion"; +import markInsertion from "./markInsertion"; +import markWrapping from "./markWrapping"; + +const replaceAroundStep = (state, tr, step, newTr, map, doc, user, date) => { + 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); + } else if (!step.slice.size) { + // unwrapped from something + map.appendMap(step.invert(doc).getMap()); + map.appendMap(markDeletion(newTr, step.from, step.gapFrom, user, date)); + } else if ( + step.slice.size === 2 && + step.gapFrom - step.from === 1 && + step.to - step.gapTo === 1 + ) { + // Replaced one wrapping with another + newTr.step(step); + const oldNode = doc.nodeAt(step.from); + if (oldNode.attrs.track) { + markWrapping( + newTr, + step.from, + oldNode, + step.slice.content.firstChild, + user, + date + ); + } + } else { + console.log("to fix"); + // newTr.step(step); + // const ranges = [ + // { + // from: step.getMap().map(step.from, -1), + // to: step.getMap().map(step.gapFrom) + // }, + // { + // from: step.getMap().map(step.gapTo, -1), + // to: step.getMap().map(step.to) + // } + // ]; + // ranges.forEach(range => + // doc.nodesBetween(range.from, range.to, (node, pos) => { + // if (pos < range.from) { + // return true; + // } + // markInsertion(newTr, range.from, range.to, user, date); + // }) + // ); + } +}; + +export default replaceAroundStep; diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceStep.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceStep.js new file mode 100644 index 0000000000000000000000000000000000000000..a74a96c2e3124164299ced2d57601046d040816c --- /dev/null +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/helpers/replaceStep.js @@ -0,0 +1,61 @@ +import { ReplaceStep } from "prosemirror-transform"; +import { CellSelection } from "prosemirror-tables"; +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 cellDeleteTr = + ["deleteContentBackward", "deleteContentForward"].includes( + tr.getMeta("inputType") + ) && state.selection instanceof CellSelection; + + // if deletion mark move to the end of deletion + const deletionMarkSchema = state.schema.marks.deletion; + const deletionMark = DocumentHelpers.findMark( + state, + deletionMarkSchema, + false + ); + const positionTo = deletionMark ? deletionMark.to : step.to; + + const newStep = !cellDeleteTr + ? new ReplaceStep( + positionTo, // We insert all the same steps, but with "from"/"to" both set to "to" in order not to delete content. Mapped as needed. + positionTo, + step.slice, + step.structure + ) + : false; + + // We didn't apply the original step in its original place. We adjust the map accordingly. + map.appendMap(step.invert(doc).getMap()); + if (newStep) { + const trTemp = state.apply(newTr).tr; + if (trTemp.maybeStep(newStep).failed) { + return; + } + const mappedNewStepTo = newStep.getMap().map(newStep.to); + + markInsertion(trTemp, newStep.from, mappedNewStepTo, user, date); + // We condense it down to a single replace step. + const condensedStep = new ReplaceStep( + newStep.from, + newStep.to, + trTemp.doc.slice(newStep.from, mappedNewStepTo) + ); + + newTr.step(condensedStep); + const mirrorIndex = map.maps.length - 1; + map.appendMap(condensedStep.getMap(), mirrorIndex); + if (!newTr.selection.eq(trTemp.selection)) { + newTr.setSelection(trTemp.selection); + } + } + if (step.from !== step.to) { + map.appendMap(markDeletion(newTr, step.from, step.to, user, date)); + } +}; + +export default replaceStep; diff --git a/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js b/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js index 913a370052938d8a3412edad8cc27d9b95821aa8..2a6d21e98131d51f21eaa1973f8abed333044774 100644 --- a/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js +++ b/wax-prosemirror-services/src/TrackChangeService/track-changes/trackedTransaction.js @@ -5,7 +5,6 @@ License included in folder. */ import { Selection, TextSelection } from "prosemirror-state"; -import { Slice } from "prosemirror-model"; import { ReplaceStep, ReplaceAroundStep, @@ -13,40 +12,29 @@ import { RemoveMarkStep, Mapping } from "prosemirror-transform"; -import { CellSelection } from "prosemirror-tables"; -import markDeletion from "./markDeletion"; -import markInsertion from "./markInsertion"; -import markWrapping from "./markWrapping"; +import { DocumentHelpers } from "wax-prosemirror-utilities"; -const trackedTransaction = (tr, state, currentUser) => { +import replaceStep from "./helpers/replaceStep"; +import replaceAroundStep from "./helpers/replaceAroundStep"; +import addMarkStep from "./helpers/addMarkStep"; +import removeMarkStep from "./helpers/removeMarkStep"; + +const trackedTransaction = (tr, state, user) => { if ( !tr.steps.length || (tr.meta && !Object.keys(tr.meta).every(metadata => ["inputType", "uiEvent", "paste"].includes(metadata) )) || - // don't replace history TRs ["historyUndo", "historyRedo"].includes(tr.getMeta("inputType")) ) { return tr; } - const user = currentUser.userId, - approved = false, - // !editor.view.state.doc.firstChild.attrs.tracked && - // editor.docInfo.access_rights !== "write-tracked", - newTr = state.tr, - map = new Mapping(), - exactDate = Date.now(), - date10 = Math.floor(exactDate / 600000) * 10, // 10 minute interval - date1 = Math.floor(exactDate / 60000), // 1 minute interval - username = currentUser.username, - // We only insert content if this is not directly a tr for cell deletion. This is because tables delete rows by deleting the - // contents of each cell and replacing it with an empty paragraph. - cellDeleteTr = - ["deleteContentBackward", "deleteContentForward"].includes( - tr.getMeta("inputType") - ) && state.selection instanceof CellSelection; + + const newTr = state.tr; + const map = new Mapping(); + const date = Math.floor(Date.now() / 60000); tr.steps.forEach(originalStep => { const step = originalStep.map(map), @@ -55,241 +43,22 @@ const trackedTransaction = (tr, state, currentUser) => { return; } - //if (step instanceof ReplaceStep) { - if (step.jsonID === "replace") { - const newStep = approved - ? step - : step.slice.size && !cellDeleteTr - ? new ReplaceStep( - step.to, // We insert all the same steps, but with "from"/"to" both set to "to" in order not to delete content. Mapped as needed. - step.to, - step.slice, - step.structure - ) - : false; - // We didn't apply the original step in its original place. We adjust the map accordingly. - map.appendMap(step.invert(doc).getMap()); - if (newStep) { - const trTemp = state.apply(newTr).tr; - if (trTemp.maybeStep(newStep).failed) { - return; - } - - const mappedNewStepTo = newStep.getMap().map(newStep.to); - markInsertion( - trTemp, - newStep.from, - mappedNewStepTo, - user, - username, - date1, - date10, - approved - ); - // We condense it down to a single replace step. - const condensedStep = new ReplaceStep( - newStep.from, - newStep.to, - trTemp.doc.slice(newStep.from, mappedNewStepTo) - ); - - newTr.step(condensedStep); - map.appendMap(condensedStep.getMap()); - if (!newTr.selection.eq(trTemp.selection)) { - newTr.setSelection(trTemp.selection); - } - } - if (!approved && step.from !== step.to) { - map.appendMap( - markDeletion(newTr, step.from, step.to, user, username, date1, date10) - ); - } - } else if (approved) { - newTr.step(step); - } else if (step instanceof ReplaceAroundStep) { - 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, username, date1, date10, false); - } else if (!step.slice.size) { - // unwrapped from something - map.appendMap(step.invert(doc).getMap()); - map.appendMap( - markDeletion( - newTr, - step.from, - step.gapFrom, - user, - username, - date1, - date10 - ) - ); - } else if ( - step.slice.size === 2 && - step.gapFrom - step.from === 1 && - step.to - step.gapTo === 1 - ) { - // Replaced one wrapping with another - newTr.step(step); - const oldNode = doc.nodeAt(step.from); - if (oldNode.attrs.track) { - markWrapping( - newTr, - step.from, - oldNode, - step.slice.content.firstChild, - user, - username, - date1 - ); - } - } else { - newTr.step(step); - const ranges = [ - { - from: step.getMap().map(step.from, -1), - to: step.getMap().map(step.gapFrom) - }, - { - from: step.getMap().map(step.gapTo, -1), - to: step.getMap().map(step.to) - } - ]; - ranges.forEach(range => - doc.nodesBetween(range.from, range.to, (node, pos) => { - if (pos < range.from) { - return true; - } - markInsertion( - newTr, - range.from, - range.to, - user, - username, - date1, - date10, - false - ); - }) - ); - } - } else if (step instanceof AddMarkStep) { - doc.nodesBetween(step.from, step.to, (node, pos) => { - if (!node.isInline) { - return true; - } - if (node.marks.find(mark => mark.type.name === "deletion")) { - return false; - } else { - newTr.addMark( - Math.max(step.from, pos), - Math.min(step.to, pos + node.nodeSize), - step.mark - ); - } - if (!node.marks.find(mark => mark.type === step.mark.type)) { - const formatChangeMark = node.marks.find( - mark => mark.type.name === "format_change" - ); - let after, before; - if (formatChangeMark) { - if (formatChangeMark.attrs.before.includes(step.mark.type.name)) { - before = formatChangeMark.attrs.before.filter( - markName => markName !== step.mark.type.name - ); - after = formatChangeMark.attrs.after; - } else { - before = formatChangeMark.attrs.before; - after = formatChangeMark.attrs.after.concat(step.mark.type.name); - } - } else { - before = []; - after = [step.mark.type.name]; - } - if (after.length || before.length) { - newTr.addMark( - Math.max(step.from, pos), - Math.min(step.to, pos + node.nodeSize), - state.schema.marks.format_change.create({ - user, - username, - date: date10, - before, - after - }) - ); - } else if (formatChangeMark) { - newTr.removeMark( - Math.max(step.from, pos), - Math.min(step.to, pos + node.nodeSize), - formatChangeMark - ); - } - } - }); - } else if (step instanceof RemoveMarkStep) { - doc.nodesBetween(step.from, step.to, (node, pos) => { - if (!node.isInline) { - return true; - } - if (node.marks.find(mark => mark.type.name === "deletion")) { - return false; - } else { - newTr.removeMark( - Math.max(step.from, pos), - Math.min(step.to, pos + node.nodeSize), - step.mark - ); - } - if (node.marks.find(mark => mark.type === step.mark.type)) { - const formatChangeMark = node.marks.find( - mark => mark.type.name === "format_change" - ); - let after, before; - if (formatChangeMark) { - if (formatChangeMark.attrs.after.includes(step.mark.type.name)) { - after = formatChangeMark.attrs.after.filter( - markName => markName !== step.mark.type.name - ); - before = formatChangeMark.attrs.before; - } else { - after = formatChangeMark.attrs.after; - before = formatChangeMark.attrs.before.concat( - step.mark.type.name - ); - } - } else { - after = []; - before = [step.mark.type.name]; - } - if (after.length || before.length) { - newTr.addMark( - Math.max(step.from, pos), - Math.min(step.to, pos + node.nodeSize), - state.schema.marks.format_change.create({ - user, - username, - date: date10, - before, - after - }) - ); - } else if (formatChangeMark) { - newTr.removeMark( - Math.max(step.from, pos), - Math.min(step.to, pos + node.nodeSize), - formatChangeMark - ); - } - } - }); + switch (step.constructor) { + case ReplaceStep: + replaceStep(state, tr, step, newTr, map, doc, user, date); + break; + case ReplaceAroundStep: + replaceAroundStep(state, tr, step, newTr, map, doc, user, date); + break; + case AddMarkStep: + addMarkStep(state, tr, step, newTr, map, doc, user, date); + break; + case RemoveMarkStep: + removeMarkStep(state, tr, step, newTr, map, doc, user, date); + break; } }); - // copy the input type meta data from the original transaction. if (tr.getMeta("inputType")) { newTr.setMeta(tr.getMeta("inputType")); } @@ -298,6 +67,13 @@ const trackedTransaction = (tr, state, currentUser) => { } if (tr.selectionSet) { + const deletionMarkSchema = state.schema.marks.deletion; + const deletionMark = DocumentHelpers.findMark( + state, + deletionMarkSchema, + false + ); + if ( tr.selection instanceof TextSelection && (tr.selection.from < state.selection.from || @@ -305,23 +81,31 @@ const trackedTransaction = (tr, state, currentUser) => { ) { const caretPos = map.map(tr.selection.from, -1); newTr.setSelection(new TextSelection(newTr.doc.resolve(caretPos))); + } else if (tr.selection.from > state.selection.from && deletionMark) { + const caretPos = map.map(deletionMark.to + 1, 1); + newTr.setSelection(new TextSelection(newTr.doc.resolve(caretPos))); } else { newTr.setSelection(tr.selection.map(newTr.doc, map)); } + } else { + if ( + state.selection.from - tr.selection.from > 1 && + tr.selection.$head.depth > 1 + ) { + const caretPos = map.map(tr.selection.from - 2, -1); + newTr.setSelection(new TextSelection(newTr.doc.resolve(caretPos))); + } else { + const caretPos = map.map(tr.selection.from, -1); + newTr.setSelection(new TextSelection(newTr.doc.resolve(caretPos))); + } } if (tr.storedMarksSet) { newTr.setStoredMarks(tr.storedMarks); } if (tr.scrolledIntoView) { newTr.scrollIntoView(); - if ( - tr.selection instanceof TextSelection && - tr.selection.from < state.selection.from - ) { - const caretPos = map.map(tr.selection.from, -1); - newTr.setSelection(new TextSelection(newTr.doc.resolve(caretPos))); - } } + return newTr; }; diff --git a/wax-prosemirror-utilities/src/commands/Commands.js b/wax-prosemirror-utilities/src/commands/Commands.js index 74d926b4dfe87fc73f790f632b2463d1048afe74..5668ce7765b4b4a4957ba76b30a6243a640e7d0f 100644 --- a/wax-prosemirror-utilities/src/commands/Commands.js +++ b/wax-prosemirror-utilities/src/commands/Commands.js @@ -1,5 +1,34 @@ import { v4 as uuidv4 } from "uuid"; +const setBlockType = (nodeType, attrs = {}) => { + return (state, dispatch) => { + const { tr } = state; + const { from, to } = state.selection; + state.doc.nodesBetween(from, to, (node, pos) => { + if (!node.isTextblock || node.hasMarkup(nodeType, attrs)) return; + let applicable = false; + if (node.type == nodeType) { + applicable = true; + } else { + const $pos = state.doc.resolve(pos), + index = $pos.index(); + applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType); + } + if (applicable) { + tr.setBlockType( + from, + to, + nodeType, + Object.assign({}, node.attrs, attrs) + ); + } + }); + if (!tr.steps.length) return false; + if (dispatch) dispatch(tr.scrollIntoView()); + return true; + }; +}; + const markActive = type => state => { const { from, $from, to, empty } = state.selection; @@ -83,6 +112,7 @@ const createComment = (state, dispatch, group) => { }; export default { + setBlockType, blockActive, canInsert, createComment, diff --git a/wax-prosemirror-utilities/src/schema/SchemaHelpers.js b/wax-prosemirror-utilities/src/schema/SchemaHelpers.js index 40cac9e012a537675757e8afc68e529a7849e209..79134bb58f82bf84d73c68cfa1cb3ee2c375e39f 100644 --- a/wax-prosemirror-utilities/src/schema/SchemaHelpers.js +++ b/wax-prosemirror-utilities/src/schema/SchemaHelpers.js @@ -11,7 +11,7 @@ const parseFormatList = str => { if (!Array.isArray(formatList)) { return []; } - return formatList.filter(format => typeof format === "string"); // ensure there are only strings in list + return formatList.filter(format => typeof format === "string"); }; const parseTracks = str => { @@ -38,14 +38,15 @@ const parseTracks = str => { }; const blockLevelToDOM = node => { - const attrs = node.attrs.track.length - ? { - "data-id": node.attrs.id, - class: node.attrs.class, - "data-track": JSON.stringify(node.attrs.track), - "data-group": node.attrs.group - } - : { class: node.attrs.class }; + const attrs = + node.attrs.track && node.attrs.track.length + ? { + "data-id": node.attrs.id, + class: node.attrs.class, + "data-track": JSON.stringify(node.attrs.track), + "data-group": node.attrs.group + } + : { class: node.attrs.class }; return attrs; };