Skip to content
Snippets Groups Projects
Commit 022b862c authored by chris's avatar chris
Browse files

new files

parent 13c40b6e
No related branches found
No related tags found
1 merge request!107Tracked transaction
Showing with 430 additions and 214 deletions
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;
import { Selection, TextSelection } from "prosemirror-state";
import { Slice } from "prosemirror-model";
import { ReplaceStep, Mapping } from "prosemirror-transform";
const markDeletion = (tr, from, to, user, date) => {
const deletionMark = tr.doc.type.schema.marks.deletion.create({
user: user.userId,
username: user.username,
date
});
let firstTableCellChild = false;
const deletionMap = new Mapping();
// Add deletion mark to block nodes (figures, text blocks) and find already deleted inline nodes (and leave them alone)
tr.doc.nodesBetween(from, to, (node, pos) => {
if (pos < from && node.type.name === "table_cell") {
firstTableCellChild = true;
return true;
} else if ((pos < from && node.isBlock) || firstTableCellChild) {
firstTableCellChild = false;
return true;
} else if (["table_row", "table_cell"].includes(node.type.name)) {
return false;
} else if (
node.isInline &&
node.marks.find(
mark =>
mark.type.name === "insertion" && mark.attrs.user === user.userId
)
) {
const removeStep = new ReplaceStep(
deletionMap.map(Math.max(from, pos)),
deletionMap.map(Math.min(to, pos + node.nodeSize)),
Slice.empty
);
if (!tr.maybeStep(removeStep).failed) {
deletionMap.appendMap(removeStep.getMap());
}
} else if (
node.isInline &&
!node.marks.find(mark => mark.type.name === "deletion")
) {
tr.addMark(
deletionMap.map(Math.max(from, pos)),
deletionMap.map(Math.min(to, pos + node.nodeSize)),
deletionMark
);
} else if (
node.attrs.track &&
!node.attrs.track.find(trackAttr => trackAttr.type === "deletion") &&
!["bullet_list", "ordered_list"].includes(node.type.name)
) {
if (
node.attrs.track.find(
trackAttr =>
trackAttr.type === "insertion" && trackAttr.user === user.userId
)
) {
let removeStep;
// user has created element. so (s)he is allowed to delete it again.
if (node.isTextblock && to < pos + node.nodeSize) {
// The node is a textblock. So we need to merge into the last possible position inside the last text block.
const selectionBefore = Selection.findFrom(tr.doc.resolve(pos), -1);
if (selectionBefore instanceof TextSelection) {
removeStep = new ReplaceStep(
deletionMap.map(selectionBefore.$anchor.pos),
deletionMap.map(to),
Slice.empty
);
}
} else {
removeStep = new ReplaceStep(
deletionMap.map(Math.max(from, pos)),
deletionMap.map(Math.min(to, pos + node.nodeSize)),
Slice.empty
);
}
if (!tr.maybeStep(removeStep).failed) {
deletionMap.appendMap(removeStep.getMap());
}
} else {
const track = node.attrs.track.slice();
track.push({
type: "deletion",
user: user.userId,
username: user.username,
date
});
tr.setNodeMarkup(
deletionMap.map(pos),
null,
Object.assign({}, node.attrs, { track }),
node.marks
);
}
}
});
return deletionMap;
};
export default markDeletion;
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: 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.
tr.doc.nodesBetween(from, to, (node, pos) => {
if (
pos < from ||
["bullet_list", "ordered_list"].includes(node.type.name)
) {
return true;
} else if (
node.isInline ||
["table_row", "table_cell"].includes(node.type.name)
) {
return false;
}
if (node.attrs.track) {
const track = [];
track.push({
type: "insertion",
user: user.userId,
username: user.username,
date
});
tr.setNodeMarkup(
pos,
null,
Object.assign({}, node.attrs, { track }),
node.marks
);
}
if (node.type.name === "table") {
// A table was inserted. We don't add track marks to elements inside of it.
return false;
}
});
};
export default markInsertion;
import { v4 as uuidv4 } from "uuid";
const markWrapping = (tr, pos, oldNode, newNode, user, date) => {
let track = oldNode.attrs.track.slice(),
blockTrack = track.find(track => track.type === "block_change");
if (blockTrack) {
track = track.filter(track => track !== blockTrack);
if (
blockTrack.before.type !== newNode.type.name ||
blockTrack.before.attrs.level !== newNode.attrs.level
) {
blockTrack = {
type: "block_change",
user: user.id,
username: user.username,
date,
before: blockTrack.before
};
track.push(blockTrack);
}
} else {
blockTrack = {
type: "block_change",
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.track) {
delete blockTrack.before.attrs.track;
}
track.push(blockTrack);
}
tr.setNodeMarkup(pos, null, Object.assign({}, newNode.attrs, { track }));
};
export default markWrapping;
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;
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 {
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;
import { ReplaceStep } from "prosemirror-transform";
import { CellSelection } from "prosemirror-tables";
import markDeletion from "./markDeletion";
import markInsertion from "./markInsertion";
const replaceStep = (state, tr, step, newTr, map, doc, user, date) => {
// 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.
const cellDeleteTr =
["deleteContentBackward", "deleteContentForward"].includes(
tr.getMeta("inputType")
) && state.selection instanceof CellSelection;
const newStep = !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, 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;
...@@ -5,7 +5,6 @@ License included in folder. ...@@ -5,7 +5,6 @@ License included in folder.
*/ */
import { Selection, TextSelection } from "prosemirror-state"; import { Selection, TextSelection } from "prosemirror-state";
import { Slice } from "prosemirror-model";
import { import {
ReplaceStep, ReplaceStep,
ReplaceAroundStep, ReplaceAroundStep,
...@@ -13,11 +12,11 @@ import { ...@@ -13,11 +12,11 @@ import {
RemoveMarkStep, RemoveMarkStep,
Mapping Mapping
} from "prosemirror-transform"; } from "prosemirror-transform";
import { CellSelection } from "prosemirror-tables";
import markDeletion from "./markDeletion"; import replaceStep from "./helpers/replaceStep";
import markInsertion from "./markInsertion"; import replaceAroundStep from "./helpers/replaceAroundStep";
import markWrapping from "./markWrapping"; import addMarkStep from "./helpers/addMarkStep";
import removeMarkStep from "./helpers/removeMarkStep";
const trackedTransaction = (tr, state, user) => { const trackedTransaction = (tr, state, user) => {
if ( if (
...@@ -32,15 +31,9 @@ const trackedTransaction = (tr, state, user) => { ...@@ -32,15 +31,9 @@ const trackedTransaction = (tr, state, user) => {
return tr; return tr;
} }
const newTr = state.tr, const newTr = state.tr;
map = new Mapping(), const map = new Mapping();
date = Math.floor(Date.now() / 60000), // 1 minute interval const date = Math.floor(Date.now() / 60000);
// 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;
tr.steps.forEach(originalStep => { tr.steps.forEach(originalStep => {
const step = originalStep.map(map), const step = originalStep.map(map),
...@@ -50,215 +43,19 @@ const trackedTransaction = (tr, state, user) => { ...@@ -50,215 +43,19 @@ const trackedTransaction = (tr, state, user) => {
} }
if (step instanceof ReplaceStep) { if (step instanceof ReplaceStep) {
const newStep = !cellDeleteTr replaceStep(state, tr, step, newTr, map, doc, user, date);
? 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, 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));
}
} }
if (step instanceof ReplaceAroundStep) { if (step instanceof ReplaceAroundStep) {
if (step.from === step.gapFrom && step.to === step.gapTo) { replaceAroundStep(state, tr, step, newTr, map, doc, user, date);
// 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 {
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);
})
);
}
} }
if (step instanceof AddMarkStep) { if (step instanceof AddMarkStep) {
doc.nodesBetween(step.from, step.to, (node, pos) => { addMarkStep(state, tr, step, newTr, map, doc, user, date);
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
);
}
}
});
} }
if (step instanceof RemoveMarkStep) { if (step instanceof RemoveMarkStep) {
doc.nodesBetween(step.from, step.to, (node, pos) => { removeMarkStep(state, tr, step, newTr, map, doc, user, date);
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
);
}
}
});
} }
}); });
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment