Stackedit/src/services/editorEngineSvc.js
2017-07-28 08:40:24 +01:00

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);
},
};