Commit 282ae348 authored by chris's avatar chris

track changes initial

parent 42a62ec1
......@@ -26,7 +26,7 @@ import invisibles, {
import { Wax, CreateSchema, CreateShortCuts } from "wax-prosemirror-core";
import { EditoriaSchema } from "wax-prosemirror-schema";
import { LinkToolTipPlugin } from "wax-prosemirror-plugins";
import { LinkToolTipPlugin, TrackChangePlugin } from "wax-prosemirror-plugins";
import { MainMenuBar, SideMenuBar } from "wax-prosemirror-components";
import "wax-prosemirror-layouts/layouts/editoria-layout.css";
import "wax-prosemirror-layouts/vars/wax-editoria-vars.css";
......@@ -61,6 +61,7 @@ const plugins = [
columnResizing(),
tableEditing(),
// LinkToolTipPlugin,
TrackChangePlugin({ options: {} }),
invisibles([hardBreak()])
];
......
export { default as LinkToolTipPlugin } from "./src/LinkToolTipPlugin";
export {
default as TrackChangePlugin
} from "./src/trackChanges/TrackChangePlugin";
import { Plugin, PluginKey } from "prosemirror-state";
import { Decoration, DecorationSet, EditorView } from "prosemirror-view";
import { findSelectedChanges } from "./find_selected_changes";
import { deactivateAllSelectedChanges } from "./helpers";
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) {
// Make sure there are colors for all users who have left marks in the document
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 => {
console.log("dkddkdkdk");
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
};
},
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);
if (tr.selectionSet) {
const { insertion, deletion, formatChange } = findSelectedChanges(
state
);
decos = DecorationSet.empty;
const decoType = tr.selection.node
? Decoration.node
: Decoration.inline;
if (insertion) {
decos = decos.add(tr.doc, [
decoType(
insertion.from,
insertion.to,
{
class: "selected-insertion"
},
selectedInsertionSpec
)
]);
}
if (deletion) {
decos = decos.add(tr.doc, [
decoType(
deletion.from,
deletion.to,
{
class: "selected-deletion"
},
selectedDeletionSpec
)
]);
}
if (formatChange) {
decos = decos.add(tr.doc, [
decoType(
formatChange.from,
formatChange.to,
{
class: "selected-format_change"
},
selectedChangeFormatSpec
)
]);
}
} else {
decos = decos.map(tr.mapping, tr.doc);
}
return {
decos
};
}
},
props: {
decorations(state) {
const { decos } = this.getState(state);
return decos;
},
handleDOMEvents: {
focus: (view, _event) => {
console.log(view);
view.dispatch(deactivateAllSelectedChanges(view.state.tr));
}
}
}
});
};
import {
getFromToMark
} from "./helpers"
export function 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
if (selection.empty) {
const resolvedPos = state.doc.resolve(selection.from), marks = resolvedPos.marks()
if (marks) {
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
}
}
} else {
state.doc.nodesBetween(
selection.from,
selection.to,
(node, pos) => {
if (pos < selection.from) {
return true
}
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)
if (insertionMark) {
insertionPos = pos
if (!node.isInline) {
insertionSize = node.nodeSize
}
}
}
if (!deletionMark) {
deletionMark = node.attrs.track ?
node.attrs.track.find(trackAttr => trackAttr.type==='deletion') :
node.marks.find(mark => mark.type.name==='deletion')
if (deletionMark) {
deletionPos = pos
if (!node.isInline) {
deletionSize = node.nodeSize
}
}
}
if (!formatChangeMark) {
formatChangeMark = node.marks.find(mark => mark.type.name==='format_change')
if (formatChangeMark) {
formatChangePos = pos
if (!node.isInline) {
formatChangeSize = node.nodeSize
}
}
}
}
)
}
if (insertionMark) {
selectedChanges.insertion = insertionSize ?
{from: insertionPos, to: insertionPos + insertionSize} :
getFromToMark(state.doc, insertionPos, insertionMark)
}
if (deletionMark) {
selectedChanges.deletion = deletionSize ?
{from: deletionPos, to: deletionPos + deletionSize} :
getFromToMark(state.doc, deletionPos, deletionMark)
}
if (formatChangeMark) {
selectedChanges.formatChange = formatChangeSize ?
{from: formatChangePos, to: formatChangePos + formatChangeSize} :
getFromToMark(state.doc, formatChangePos, formatChangeMark)
}
return selectedChanges
}
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}
}
......@@ -377,6 +377,44 @@ const EditoriaSchema = {
toDOM() {
return ["cite", 0];
}
},
deletion: {
attrs: {
user: {
default: 0
},
username: {
default: ""
},
date: {
default: 0
}
},
inclusive: false,
group: "track",
parseDOM: [
{
tag: "span.deletion",
getAttrs(dom) {
return {
user: { id: "123" },
username: "chris",
date: "2333"
};
}
}
],
toDOM(node) {
return [
"span",
{
class: `deletion user-chris`,
"data-user": '{id: "123"}',
"data-username": "chris",
"data-date": "32323"
}
];
}
}
}
};
......
Markdown is supported
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