163 lines
5.1 KiB
JavaScript
163 lines
5.1 KiB
JavaScript
import DiffMatchPatch from 'diff-match-patch';
|
|
import cledit from '../cledit/cledit';
|
|
import clDiffUtils from '../cledit/cldiffutils';
|
|
import store from '../store';
|
|
|
|
let clEditor;
|
|
const newDiscussionMarker0 = new cledit.Marker(0);
|
|
const newDiscussionMarker1 = new cledit.Marker(0, true);
|
|
let markerKeys;
|
|
let markerIdxMap;
|
|
let previousPatchableText;
|
|
let currentPatchableText;
|
|
let discussionMarkers;
|
|
let isChangePatch;
|
|
let contentId;
|
|
|
|
function getDiscussionMarkers(discussion, discussionId, onMarker) {
|
|
function getMarker(offsetName) {
|
|
const markerOffset = discussion[offsetName];
|
|
const markerKey = discussionId + offsetName;
|
|
let marker = discussionMarkers[markerKey];
|
|
if (markerOffset !== undefined) {
|
|
if (!marker) {
|
|
marker = new cledit.Marker(markerOffset, offsetName === 'offset1');
|
|
marker.discussionId = discussionId;
|
|
marker.offsetName = offsetName;
|
|
clEditor.addMarker(marker);
|
|
discussionMarkers[markerKey] = marker;
|
|
}
|
|
onMarker(marker);
|
|
}
|
|
}
|
|
getMarker('offset0');
|
|
getMarker('offset1');
|
|
}
|
|
|
|
function syncDiscussionMarkers() {
|
|
const content = store.getters['contents/current'];
|
|
Object.keys(discussionMarkers)
|
|
.forEach((markerKey) => {
|
|
const marker = discussionMarkers[markerKey];
|
|
// Remove marker if discussion was removed
|
|
const discussion = content.discussions[marker.discussionId];
|
|
if (!discussion || discussion[marker.offsetName] === undefined) {
|
|
clEditor.removeMarker(marker);
|
|
delete discussionMarkers[markerKey];
|
|
}
|
|
});
|
|
|
|
Object.keys(content.discussions)
|
|
.forEach((discussionId) => {
|
|
const discussion = content.discussions[discussionId];
|
|
getDiscussionMarkers(discussion, discussionId, (marker) => {
|
|
discussion[marker.offsetName] = marker.offset;
|
|
});
|
|
});
|
|
}
|
|
|
|
const diffMatchPatch = new DiffMatchPatch();
|
|
|
|
function makePatches() {
|
|
const diffs = diffMatchPatch.diff_main(previousPatchableText, currentPatchableText);
|
|
return diffMatchPatch.patch_make(previousPatchableText, diffs);
|
|
}
|
|
|
|
function applyPatches(patches) {
|
|
const newPatchableText = diffMatchPatch.patch_apply(patches, currentPatchableText)[0];
|
|
let result = newPatchableText;
|
|
if (markerKeys.length) {
|
|
// Strip text markers
|
|
result = result.replace(new RegExp(`[\ue000-${String.fromCharCode((0xe000 + markerKeys.length) - 1)}]`, 'g'), '');
|
|
}
|
|
// Expect a `contentChanged` event
|
|
if (result !== clEditor.getContent()) {
|
|
previousPatchableText = currentPatchableText;
|
|
currentPatchableText = newPatchableText;
|
|
isChangePatch = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function reversePatches(patches) {
|
|
const result = diffMatchPatch.patch_deepCopy(patches).reverse();
|
|
result.forEach((patch) => {
|
|
patch.diffs.forEach((diff) => {
|
|
diff[0] = -diff[0];
|
|
});
|
|
});
|
|
return result;
|
|
}
|
|
|
|
export default {
|
|
clEditor: null,
|
|
lastChange: 0,
|
|
lastExternalChange: 0,
|
|
createClEditor(editorElt) {
|
|
this.clEditor = cledit(editorElt, editorElt.parentNode);
|
|
clEditor = this.clEditor;
|
|
markerKeys = [];
|
|
markerIdxMap = Object.create(null);
|
|
discussionMarkers = {};
|
|
clEditor.on('contentChanged', (text) => {
|
|
store.dispatch('contents/patchCurrent', { text });
|
|
syncDiscussionMarkers();
|
|
const content = store.getters['contents/current'];
|
|
if (!isChangePatch) {
|
|
previousPatchableText = currentPatchableText;
|
|
currentPatchableText = clDiffUtils.makePatchableText(content, markerKeys, markerIdxMap);
|
|
} else {
|
|
// Take a chance to restore discussion offsets on undo/redo
|
|
content.text = currentPatchableText;
|
|
clDiffUtils.restoreDiscussionOffsets(content, markerKeys);
|
|
content.discussions.cl_each((discussion, discussionId) => {
|
|
getDiscussionMarkers(discussion, discussionId, (marker) => {
|
|
marker.offset = discussion[marker.offsetName];
|
|
});
|
|
});
|
|
}
|
|
isChangePatch = false;
|
|
this.lastChange = Date.now();
|
|
});
|
|
clEditor.addMarker(newDiscussionMarker0);
|
|
clEditor.addMarker(newDiscussionMarker1);
|
|
},
|
|
initClEditor(opts, reinit) {
|
|
const content = store.getters['contents/current'];
|
|
if (content) {
|
|
const options = Object.assign({}, opts);
|
|
|
|
if (contentId !== content.id) {
|
|
contentId = content.id;
|
|
currentPatchableText = clDiffUtils.makePatchableText(content, markerKeys, markerIdxMap);
|
|
previousPatchableText = currentPatchableText;
|
|
syncDiscussionMarkers();
|
|
}
|
|
|
|
if (reinit) {
|
|
options.content = content.text;
|
|
options.selectionStart = content.state.selectionStart;
|
|
options.selectionEnd = content.state.selectionEnd;
|
|
}
|
|
|
|
options.patchHandler = {
|
|
makePatches,
|
|
applyPatches: patches => applyPatches(patches),
|
|
reversePatches,
|
|
};
|
|
clEditor.init(options);
|
|
}
|
|
},
|
|
applyContent(isExternal) {
|
|
if (!clEditor) {
|
|
return null;
|
|
}
|
|
if (isExternal) {
|
|
this.lastExternalChange = Date.now();
|
|
}
|
|
syncDiscussionMarkers();
|
|
const content = store.getters['contents/current'];
|
|
return clEditor.setContent(content.text, isExternal);
|
|
},
|
|
};
|