Stackedit/src/services/editorEngineSvc.js
2017-08-25 11:37:46 +01:00

168 lines
5.2 KiB
JavaScript

import DiffMatchPatch from 'diff-match-patch';
import cledit from '../libs/cledit';
import utils from './utils';
import diffUtils from './diffUtils';
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(content, writeOffsets) {
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, writeOffsets
? (marker) => {
discussion[marker.offsetName] = marker.offset;
}
: (marker) => {
marker.offset = discussion[marker.offsetName];
});
});
}
function removeDiscussionMarkers() {
Object.keys(discussionMarkers).forEach((markerKey) => {
const marker = discussionMarkers[markerKey];
clEditor.removeMarker(marker);
delete discussionMarkers[markerKey];
});
}
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,
createClEditor(editorElt) {
this.clEditor = cledit(editorElt, editorElt.parentNode);
clEditor = this.clEditor;
markerKeys = [];
markerIdxMap = Object.create(null);
discussionMarkers = {};
clEditor.on('contentChanged', (text) => {
const oldContent = store.getters['content/current'];
const newContent = {
...oldContent,
discussions: utils.deepCopy(oldContent.discussions),
text,
};
syncDiscussionMarkers(newContent, true);
if (!isChangePatch) {
previousPatchableText = currentPatchableText;
currentPatchableText = diffUtils.makePatchableText(newContent, markerKeys, markerIdxMap);
} else {
// Take a chance to restore discussion offsets on undo/redo
diffUtils.restoreDiscussionOffsets(newContent, markerKeys);
syncDiscussionMarkers(newContent, false);
}
store.dispatch('content/patchCurrent', newContent);
isChangePatch = false;
});
clEditor.addMarker(newDiscussionMarker0);
clEditor.addMarker(newDiscussionMarker1);
},
initClEditor(opts) {
const content = store.getters['content/current'];
if (content) {
const options = Object.assign({}, opts);
if (contentId !== content.id) {
contentId = content.id;
currentPatchableText = diffUtils.makePatchableText(content, markerKeys, markerIdxMap);
previousPatchableText = currentPatchableText;
syncDiscussionMarkers(content, false);
const contentState = store.getters['contentState/current'];
options.content = content.text;
options.selectionStart = contentState.selectionStart;
options.selectionEnd = contentState.selectionEnd;
}
options.patchHandler = {
makePatches,
applyPatches,
reversePatches,
};
clEditor.init(options);
}
},
applyContent() {
if (clEditor) {
const content = store.getters['content/current'];
if (clEditor.setContent(content.text, true).range) {
// Marker will be recreated on contentChange
removeDiscussionMarkers();
} else {
syncDiscussionMarkers(content, false);
}
}
},
};