cledit refactoring

This commit is contained in:
benweet 2018-02-17 11:18:30 +00:00
parent f0721c9405
commit d57d2bb969
17 changed files with 430 additions and 397 deletions

View File

@ -4,7 +4,7 @@
<script> <script>
import Prism from 'prismjs'; import Prism from 'prismjs';
import cledit from '../libs/cledit'; import cledit from '../services/cledit';
export default { export default {
props: ['value', 'lang', 'disabled'], props: ['value', 'lang', 'disabled'],

View File

@ -34,7 +34,7 @@
<script> <script>
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import editorSvc from '../services/editorSvc'; import editorSvc from '../services/editorSvc';
import cledit from '../libs/cledit'; import cledit from '../services/cledit';
import store from '../store'; import store from '../store';
import EditorClassApplier from './common/EditorClassApplier'; import EditorClassApplier from './common/EditorClassApplier';

View File

@ -1,4 +1,4 @@
import cledit from '../../libs/cledit'; import cledit from '../../services/cledit';
import editorSvc from '../../services/editorSvc'; import editorSvc from '../../services/editorSvc';
import utils from '../../services/utils'; import utils from '../../services/utils';

View File

@ -1,4 +1,4 @@
import cledit from '../../libs/cledit'; import cledit from '../../services/cledit';
import editorSvc from '../../services/editorSvc'; import editorSvc from '../../services/editorSvc';
import utils from '../../services/utils'; import utils from '../../services/utils';

View File

@ -24,7 +24,7 @@
import { mapGetters, mapMutations, mapActions } from 'vuex'; import { mapGetters, mapMutations, mapActions } from 'vuex';
import Prism from 'prismjs'; import Prism from 'prismjs';
import UserImage from '../UserImage'; import UserImage from '../UserImage';
import cledit from '../../libs/cledit'; import cledit from '../../services/cledit';
import editorSvc from '../../services/editorSvc'; import editorSvc from '../../services/editorSvc';
import markdownConversionSvc from '../../services/markdownConversionSvc'; import markdownConversionSvc from '../../services/markdownConversionSvc';
import utils from '../../services/utils'; import utils from '../../services/utils';

View File

@ -37,15 +37,6 @@ function cledit(contentElt, scrollEltOpt) {
let lastTextContent = getTextContent(); let lastTextContent = getTextContent();
const highlighter = new cledit.Highlighter(editor); const highlighter = new cledit.Highlighter(editor);
let sectionList;
function parseSections(content, isInit) {
sectionList = highlighter.parseSections(content, isInit);
editor.$allElements = Array.prototype.slice
.call(contentElt.querySelectorAll('.cledit-section *'));
return sectionList;
}
/* eslint-disable new-cap */ /* eslint-disable new-cap */
const diffMatchPatch = new DiffMatchPatch(); const diffMatchPatch = new DiffMatchPatch();
/* eslint-enable new-cap */ /* eslint-enable new-cap */
@ -152,7 +143,11 @@ function cledit(contentElt, scrollEltOpt) {
}, 10); }, 10);
let watcher; let watcher;
let skipSaveSelection;
function checkContentChange(mutations) { function checkContentChange(mutations) {
if (contentElt.textContent.indexOf('.') >= 0) {
debugger;
}
watcher.noWatch(() => { watcher.noWatch(() => {
const removedSections = []; const removedSections = [];
const modifiedSections = []; const modifiedSections = [];
@ -186,9 +181,13 @@ function cledit(contentElt, scrollEltOpt) {
marker.adjustOffset(diffs); marker.adjustOffset(diffs);
}); });
if (!skipSaveSelection) {
selectionMgr.saveSelectionState(); selectionMgr.saveSelectionState();
const parsedSections = parseSections(newTextContent); }
editor.$trigger('contentChanged', newTextContent, diffs, parsedSections); skipSaveSelection = false;
const sectionList = highlighter.parseSections(newTextContent);
editor.$trigger('contentChanged', newTextContent, diffs, sectionList);
if (!ignoreUndo) { if (!ignoreUndo) {
undoMgr.addDiffs(lastTextContent, newTextContent, diffs); undoMgr.addDiffs(lastTextContent, newTextContent, diffs);
undoMgr.setDefaultMode('typing'); undoMgr.setDefaultMode('typing');
@ -270,12 +269,12 @@ function cledit(contentElt, scrollEltOpt) {
contentElt.addEventListener('keydown', keydownHandler((evt) => { contentElt.addEventListener('keydown', keydownHandler((evt) => {
selectionMgr.saveSelectionState(); selectionMgr.saveSelectionState();
adjustCursorPosition();
// Perform keystroke // Perform keystroke
let contentChanging = false;
const textContent = getTextContent(); const textContent = getTextContent();
const min = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd); let min = Math.min(selectionMgr.selectionStart, selectionMgr.selectionEnd);
const max = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd); let max = Math.max(selectionMgr.selectionStart, selectionMgr.selectionEnd);
const state = { const state = {
before: textContent.slice(0, min), before: textContent.slice(0, min),
after: textContent.slice(max), after: textContent.slice(max),
@ -286,15 +285,26 @@ function cledit(contentElt, scrollEltOpt) {
if (!keystroke.handler(evt, state, editor)) { if (!keystroke.handler(evt, state, editor)) {
return false; return false;
} }
editor.setContent(state.before + state.selection + state.after, false, min); const newContent = state.before + state.selection + state.after;
const min1 = state.before.length; if (newContent !== getTextContent()) {
const max1 = min + state.selection.length; editor.setContent(newContent, false, min);
contentChanging = true;
skipSaveSelection = true;
}
min = state.before.length;
max = min + state.selection.length;
selectionMgr.setSelectionStartEnd( selectionMgr.setSelectionStartEnd(
state.isBackwardSelection ? max1 : min1, state.isBackwardSelection ? max : min,
state.isBackwardSelection ? min : max1, state.isBackwardSelection ? min : max,
!contentChanging, // Expect a restore selection on mutation event
); );
return true; return true;
}); });
if (!contentChanging) {
// Optimization to avoid saving selection
adjustCursorPosition();
}
})); }));
contentElt.addEventListener('compositionstart', () => { contentElt.addEventListener('compositionstart', () => {
@ -406,8 +416,8 @@ function cledit(contentElt, scrollEltOpt) {
} }
} }
const parsedSections = parseSections(lastTextContent, true); const sectionList = highlighter.parseSections(lastTextContent, true);
editor.$trigger('contentChanged', lastTextContent, [0, lastTextContent], parsedSections); editor.$trigger('contentChanged', lastTextContent, [0, lastTextContent], sectionList);
if (options.selectionStart !== undefined && options.selectionEnd !== undefined) { if (options.selectionStart !== undefined && options.selectionEnd !== undefined) {
editor.setSelection(options.selectionStart, options.selectionEnd); editor.setSelection(options.selectionStart, options.selectionEnd);
} else { } else {
@ -423,4 +433,4 @@ function cledit(contentElt, scrollEltOpt) {
return editor; return editor;
} }
module.exports = cledit; export default cledit;

View File

@ -11,7 +11,6 @@ function createStyleSheet(document) {
} }
function Highlighter(editor) { function Highlighter(editor) {
const self = this;
cledit.Utils.createEventHooks(this); cledit.Utils.createEventHooks(this);
if (!styleElts.cl_some(styleElt => document.head.contains(styleElt))) { if (!styleElts.cl_some(styleElt => document.head.contains(styleElt))) {
@ -63,7 +62,7 @@ function Highlighter(editor) {
} }
this.parseSections = (content, isInit) => { this.parseSections = (content, isInit) => {
if (this.isComposing) { if (true) {
return sectionList; return sectionList;
} }
@ -133,14 +132,14 @@ function Highlighter(editor) {
sectionList = leftSections.concat(modifiedSections).concat(rightSections); sectionList = leftSections.concat(modifiedSections).concat(rightSections);
} }
function highlight(section) { const highlight = (section) => {
const html = editor.options.sectionHighlighter(section).replace(/\n/g, lfHtml); const html = editor.options.sectionHighlighter(section).replace(/\n/g, lfHtml);
const sectionElt = document.createElement('div'); const sectionElt = document.createElement('div');
sectionElt.className = 'cledit-section'; sectionElt.className = 'cledit-section';
sectionElt.innerHTML = html; sectionElt.innerHTML = html;
section.setElement(sectionElt); section.setElement(sectionElt);
self.$trigger('sectionHighlighted', section); this.$trigger('sectionHighlighted', section);
} };
const newSectionEltList = document.createDocumentFragment(); const newSectionEltList = document.createDocumentFragment();
modifiedSections.cl_each((section) => { modifiedSections.cl_each((section) => {
@ -183,7 +182,8 @@ function Highlighter(editor) {
childNode = nextNode; childNode = nextNode;
} }
this.addTrailingNode(); this.addTrailingNode();
self.$trigger('highlighted'); this.$trigger('highlighted');
if (editor.selectionMgr.hasFocus()) { if (editor.selectionMgr.hasFocus()) {
editor.selectionMgr.restoreSelection(); editor.selectionMgr.restoreSelection();
editor.selectionMgr.updateCursorCoordinates(); editor.selectionMgr.updateCursorCoordinates();

View File

@ -6,7 +6,6 @@ function SelectionMgr(editor) {
const scrollElt = editor.$scrollElt; const scrollElt = editor.$scrollElt;
cledit.Utils.createEventHooks(this); cledit.Utils.createEventHooks(this);
const self = this;
let lastSelectionStart = 0; let lastSelectionStart = 0;
let lastSelectionEnd = 0; let lastSelectionEnd = 0;
this.selectionStart = 0; this.selectionStart = 0;
@ -28,7 +27,7 @@ function SelectionMgr(editor) {
return result; return result;
}; };
this.createRange = function (start, end) { this.createRange = (start, end) => {
const range = document.createRange(); const range = document.createRange();
const startContainer = isNaN(start) ? start : this.findContainer(start < 0 ? 0 : start); const startContainer = isNaN(start) ? start : this.findContainer(start < 0 ? 0 : start);
let endContainer = startContainer; let endContainer = startContainer;
@ -72,14 +71,14 @@ function SelectionMgr(editor) {
adjustScroll = false; adjustScroll = false;
}); });
this.updateCursorCoordinates = function (adjustScrollParam) { this.updateCursorCoordinates = (adjustScrollParam) => {
adjustScroll = adjustScroll || adjustScrollParam; adjustScroll = adjustScroll || adjustScrollParam;
debouncedUpdateCursorCoordinates(); debouncedUpdateCursorCoordinates();
}; };
let oldSelectionRange; let oldSelectionRange;
function checkSelection(selectionRange) { const checkSelection = (selectionRange) => {
if (!oldSelectionRange || if (!oldSelectionRange ||
oldSelectionRange.startContainer !== selectionRange.startContainer || oldSelectionRange.startContainer !== selectionRange.startContainer ||
oldSelectionRange.startOffset !== selectionRange.startOffset || oldSelectionRange.startOffset !== selectionRange.startOffset ||
@ -87,11 +86,11 @@ function SelectionMgr(editor) {
oldSelectionRange.endOffset !== selectionRange.endOffset oldSelectionRange.endOffset !== selectionRange.endOffset
) { ) {
oldSelectionRange = selectionRange; oldSelectionRange = selectionRange;
self.$trigger('selectionChanged', self.selectionStart, self.selectionEnd, selectionRange); this.$trigger('selectionChanged', this.selectionStart, this.selectionEnd, selectionRange);
return true; return true;
} }
return false; return false;
} };
this.hasFocus = () => contentElt === document.activeElement; this.hasFocus = () => contentElt === document.activeElement;
@ -118,19 +117,22 @@ function SelectionMgr(editor) {
}; };
const saveLastSelection = debounce(() => { const saveLastSelection = debounce(() => {
lastSelectionStart = self.selectionStart; lastSelectionStart = this.selectionStart;
lastSelectionEnd = self.selectionEnd; lastSelectionEnd = this.selectionEnd;
}, 50); }, 50);
function setSelection(start = self.selectionStart, end = this.selectionEnd) { const setSelection = (start = this.selectionStart, end = this.selectionEnd) => {
self.selectionStart = start < 0 ? 0 : start; this.selectionStart = start < 0 ? 0 : start;
self.selectionEnd = end < 0 ? 0 : end; this.selectionEnd = end < 0 ? 0 : end;
saveLastSelection(); saveLastSelection();
} };
this.setSelectionStartEnd = (start, end) => { this.setSelectionStartEnd = (start, end, restoreSelection = true) => {
setSelection(start, end); setSelection(start, end);
return this.hasFocus() && this.restoreSelection(); if (restoreSelection && this.hasFocus()) {
return this.restoreSelection();
}
return null;
}; };
this.saveSelectionState = (() => { this.saveSelectionState = (() => {
@ -217,7 +219,7 @@ function SelectionMgr(editor) {
if (childA === childB) { if (childA === childB) {
// This shouldn't be possible // This shouldn't be possible
throw module.createError('comparePoints got to case 4 and childA and childB are the same!'); throw module.createError('comparePoints got to case 4 and childA and childB are the same!');
} else { }
n = root.firstChild; n = root.firstChild;
while (n) { while (n) {
if (n === childA) { if (n === childA) {
@ -227,172 +229,189 @@ function SelectionMgr(editor) {
} }
n = n.nextSibling; n = n.nextSibling;
} }
} return 0;
} }
function save() { const save = () => {
let result; let result;
if (self.hasFocus()) { if (this.hasFocus()) {
const selectionStart = self.selectionStart; let selectionStart = this.selectionStart;
const selectionEnd = self.selectionEnd; let selectionEnd = this.selectionEnd;
const selection = window.getSelection(); const selection = window.getSelection();
if (selection.rangeCount > 0) { if (selection.rangeCount > 0) {
const selectionRange = selection.getRangeAt(0); const selectionRange = selection.getRangeAt(0);
const node = selectionRange.startContainer; let node = selectionRange.startContainer;
if ((contentElt.compareDocumentPosition(node) & window.Node.DOCUMENT_POSITION_CONTAINED_BY) || contentElt === node) { // eslint-disable-next-line no-bitwise
var offset = selectionRange.startOffset if ((contentElt.compareDocumentPosition(node)
& window.Node.DOCUMENT_POSITION_CONTAINED_BY)
|| contentElt === node
) {
let offset = selectionRange.startOffset;
if (node.firstChild && offset > 0) { if (node.firstChild && offset > 0) {
node = node.childNodes[offset - 1] node = node.childNodes[offset - 1];
offset = node.textContent.length offset = node.textContent.length;
} }
var container = node let container = node;
while (node !== contentElt) { while (node !== contentElt) {
while ((node = node.previousSibling)) { node = node.previousSibling;
offset += (node.textContent || '').length while (node) {
offset += (node.textContent || '').length;
node = node.previousSibling;
} }
node = container = container.parentNode node = container.parentNode;
container = node;
} }
var selectionText = selectionRange + '' let selectionText = `${selectionRange}`;
// Fix end of line when only br is selected // Fix end of line when only br is selected
var brElt = selectionRange.endContainer.firstChild const brElt = selectionRange.endContainer.firstChild;
if (brElt && brElt.tagName === 'BR' && selectionRange.endOffset === 1) { if (brElt && brElt.tagName === 'BR' && selectionRange.endOffset === 1) {
selectionText += '\n' selectionText += '\n';
} }
if (comparePoints(selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset) === 1) { if (comparePoints(
selectionStart = offset + selectionText.length selection.anchorNode,
selectionEnd = offset selection.anchorOffset,
selection.focusNode,
selection.focusOffset) === 1
) {
selectionStart = offset + selectionText.length;
selectionEnd = offset;
} else { } else {
selectionStart = offset selectionStart = offset;
selectionEnd = offset + selectionText.length selectionEnd = offset + selectionText.length;
} }
if (selectionStart === selectionEnd && selectionStart === editor.getContent().length) { if (selectionStart === selectionEnd && selectionStart === editor.getContent().length) {
// If cursor is after the trailingNode // If cursor is after the trailingNode
selectionStart = --selectionEnd selectionEnd -= 1;
result = self.setSelectionStartEnd(selectionStart, selectionEnd) selectionStart = selectionEnd;
result = this.setSelectionStartEnd(selectionStart, selectionEnd);
} else { } else {
setSelection(selectionStart, selectionEnd) setSelection(selectionStart, selectionEnd);
result = checkSelection(selectionRange) result = checkSelection(selectionRange);
result = result || lastSelectionStart !== self.selectionStart // selectionRange doesn't change when selection is at the start of a section // selectionRange doesn't change when selection is at the start of a section
result = result || lastSelectionStart !== this.selectionStart;
} }
} }
} }
} }
return result return result;
} };
function saveCheckChange() { const saveCheckChange = () => save() && (
return save() && (lastSelectionStart !== self.selectionStart || lastSelectionEnd !== self.selectionEnd) lastSelectionStart !== this.selectionStart || lastSelectionEnd !== this.selectionEnd);
}
var nextTickAdjustScroll = false let nextTickAdjustScroll = false;
var debouncedSave = debounce(function () { const longerDebouncedSave = debounce(() => {
self.updateCursorCoordinates(saveCheckChange() && nextTickAdjustScroll) this.updateCursorCoordinates(saveCheckChange() && nextTickAdjustScroll);
// In some cases we have to wait a little longer to see the selection change (Cmd+A on Chrome OSX) nextTickAdjustScroll = false;
longerDebouncedSave() }, 10);
}) const debouncedSave = debounce(() => {
var longerDebouncedSave = debounce(function () { this.updateCursorCoordinates(saveCheckChange() && nextTickAdjustScroll);
self.updateCursorCoordinates(saveCheckChange() && nextTickAdjustScroll) // In some cases we have to wait a little longer to see the
nextTickAdjustScroll = false // selection change (Cmd+A on Chrome OSX)
}, 10) longerDebouncedSave();
});
return function (debounced, adjustScroll, forceAdjustScroll) { return (debounced, adjustScrollParam, forceAdjustScroll) => {
if (forceAdjustScroll) { if (forceAdjustScroll) {
lastSelectionStart = undefined lastSelectionStart = undefined;
lastSelectionEnd = undefined lastSelectionEnd = undefined;
} }
if (debounced) { if (debounced) {
nextTickAdjustScroll = nextTickAdjustScroll || adjustScroll nextTickAdjustScroll = nextTickAdjustScroll || adjustScrollParam;
return debouncedSave() debouncedSave();
} else { } else {
save() save();
} }
} };
})() })();
this.getSelectedText = function () { this.getSelectedText = () => {
var min = Math.min(this.selectionStart, this.selectionEnd) const min = Math.min(this.selectionStart, this.selectionEnd);
var max = Math.max(this.selectionStart, this.selectionEnd) const max = Math.max(this.selectionStart, this.selectionEnd);
return editor.getContent().substring(min, max) return editor.getContent().substring(min, max);
} };
this.getCoordinates = function (inputOffset, container, offsetInContainer) { this.getCoordinates = (inputOffset, containerParam, offsetInContainerParam) => {
let container = containerParam;
let offsetInContainer = offsetInContainerParam;
if (!container) { if (!container) {
var offset = this.findContainer(inputOffset) const offset = this.findContainer(inputOffset);
container = offset.container container = offset.container;
offsetInContainer = offset.offsetInContainer offsetInContainer = offset.offsetInContainer;
} }
var containerElt = container let containerElt = container;
if (!containerElt.hasChildNodes()) { if (!containerElt.hasChildNodes()) {
containerElt = container.parentNode containerElt = container.parentNode;
} }
var isInvisible = false let isInvisible = false;
var index = editor.$allElements.indexOf(containerElt) while (containerElt.offsetHeight === 0) {
while (containerElt.offsetHeight === 0 && index > 0) { isInvisible = true;
isInvisible = true if (containerElt.previousSibling) {
containerElt = editor.$allElements[--index] containerElt = containerElt.previousSibling;
}
var rect
var contentRect
var left = 'left'
if (isInvisible || container.textContent === '\n') {
rect = containerElt.getBoundingClientRect()
} else { } else {
var selectedChar = editor.getContent()[inputOffset] containerElt = containerElt.parentNode;
var startOffset = {
container: container,
offsetInContainer: offsetInContainer
} }
var endOffset = {
container: container,
offsetInContainer: offsetInContainer
} }
let rect;
let left = 'left';
if (isInvisible || container.textContent === '\n') {
rect = containerElt.getBoundingClientRect();
} else {
const selectedChar = editor.getContent()[inputOffset];
let startOffset = {
container,
offsetInContainer,
};
let endOffset = {
container,
offsetInContainer,
};
if (inputOffset > 0 && (selectedChar === undefined || selectedChar === '\n')) { if (inputOffset > 0 && (selectedChar === undefined || selectedChar === '\n')) {
left = 'right' left = 'right';
if (startOffset.offsetInContainer === 0) { if (startOffset.offsetInContainer === 0) {
// Need to calculate offset-1 // Need to calculate offset-1
startOffset = inputOffset - 1 startOffset = inputOffset - 1;
} else { } else {
startOffset.offsetInContainer -= 1 startOffset.offsetInContainer -= 1;
} }
} else { } else if (endOffset.offsetInContainer === container.textContent.length) {
if (endOffset.offsetInContainer === container.textContent.length) {
// Need to calculate offset+1 // Need to calculate offset+1
endOffset = inputOffset + 1 endOffset = inputOffset + 1;
} else { } else {
endOffset.offsetInContainer += 1 endOffset.offsetInContainer += 1;
} }
const range = this.createRange(startOffset, endOffset);
rect = range.getBoundingClientRect();
} }
var range = this.createRange(startOffset, endOffset) const contentRect = contentElt.getBoundingClientRect();
rect = range.getBoundingClientRect()
}
contentRect = contentElt.getBoundingClientRect()
return { return {
top: Math.round(rect.top - contentRect.top + contentElt.scrollTop), top: Math.round((rect.top - contentRect.top) + contentElt.scrollTop),
height: Math.round(rect.height), height: Math.round(rect.height),
left: Math.round(rect[left] - contentRect.left + contentElt.scrollLeft) left: Math.round((rect[left] - contentRect.left) + contentElt.scrollLeft),
} };
} };
this.getClosestWordOffset = function (offset) { this.getClosestWordOffset = (offset) => {
var offsetStart = 0 let offsetStart = 0;
var offsetEnd = 0 let offsetEnd = 0;
var nextOffset = 0 let nextOffset = 0;
editor.getContent().split(/\s/).cl_some(function (word) { editor.getContent().split(/\s/).cl_some((word) => {
if (word) { if (word) {
offsetStart = nextOffset offsetStart = nextOffset;
offsetEnd = nextOffset + word.length offsetEnd = nextOffset + word.length;
if (offsetEnd > offset) { if (offsetEnd > offset) {
return true return true;
} }
} }
nextOffset += word.length + 1 nextOffset += word.length + 1;
}) return false;
});
return { return {
start: offsetStart, start: offsetStart,
end: offsetEnd end: offsetEnd,
} };
} };
} }
cledit.SelectionMgr = SelectionMgr cledit.SelectionMgr = SelectionMgr;

View File

@ -1,178 +1,176 @@
var DiffMatchPatch = require('diff-match-patch'); import DiffMatchPatch from 'diff-match-patch';
var cledit = require('./cleditCore') import cledit from './cleditCore';
function UndoMgr(editor) { function UndoMgr(editor) {
cledit.Utils.createEventHooks(this) cledit.Utils.createEventHooks(this);
/* eslint-disable new-cap */ /* eslint-disable new-cap */
var diffMatchPatch = new DiffMatchPatch() const diffMatchPatch = new DiffMatchPatch();
/* eslint-enable new-cap */ /* eslint-enable new-cap */
var self = this const self = this;
var selectionMgr let selectionMgr;
var undoStack = [] const undoStack = [];
var redoStack = [] const redoStack = [];
var currentState let currentState;
var previousPatches = [] let previousPatches = [];
var currentPatches = [] let currentPatches = [];
var debounce = cledit.Utils.debounce const debounce = cledit.Utils.debounce;
self.options = { this.options = {
undoStackMaxSize: 200, undoStackMaxSize: 200,
bufferStateUntilIdle: 1000, bufferStateUntilIdle: 1000,
patchHandler: { patchHandler: {
makePatches: function (oldContent, newContent, diffs) { makePatches(oldContent, newContent, diffs) {
return diffMatchPatch.patch_make(oldContent, diffs) return diffMatchPatch.patch_make(oldContent, diffs);
}, },
applyPatches: function (patches, content) { applyPatches(patches, content) {
return diffMatchPatch.patch_apply(patches, content)[0] return diffMatchPatch.patch_apply(patches, content)[0];
}, },
reversePatches: function (patches) { reversePatches(patches) {
patches = diffMatchPatch.patch_deepCopy(patches).reverse() const reversedPatches = diffMatchPatch.patch_deepCopy(patches).reverse();
patches.cl_each(function (patch) { reversedPatches.cl_each((patch) => {
patch.diffs.cl_each(function (diff) { patch.diffs.cl_each((diff) => {
diff[0] = -diff[0] diff[0] = -diff[0];
}) });
}) });
return patches return reversedPatches;
} },
} },
} };
function State() { }
let stateMgr;
function StateMgr() { function StateMgr() {
var currentTime, lastTime let currentTime;
var lastMode let lastTime;
let lastMode;
this.isBufferState = function () { this.isBufferState = () => {
currentTime = Date.now() currentTime = Date.now();
return this.currentMode !== 'single' && return this.currentMode !== 'single' &&
this.currentMode === lastMode && this.currentMode === lastMode &&
currentTime - lastTime < self.options.bufferStateUntilIdle currentTime - lastTime < self.options.bufferStateUntilIdle;
};
this.setDefaultMode = (mode) => {
this.currentMode = this.currentMode || mode;
};
this.resetMode = () => {
stateMgr.currentMode = undefined;
lastMode = undefined;
};
this.saveMode = () => {
lastMode = this.currentMode;
this.currentMode = undefined;
lastTime = currentTime;
};
} }
this.setDefaultMode = function (mode) { class State {
this.currentMode = this.currentMode || mode addToUndoStack() {
undoStack.push(this);
this.patches = previousPatches;
previousPatches = [];
} }
addToRedoStack() {
this.resetMode = function () { redoStack.push(this);
stateMgr.currentMode = undefined this.patches = previousPatches;
lastMode = undefined previousPatches = [];
}
this.saveMode = function () {
lastMode = this.currentMode
this.currentMode = undefined
lastTime = currentTime
} }
} }
function addToStack(stack) { stateMgr = new StateMgr();
return function () { this.setCurrentMode = (mode) => {
stack.push(this) stateMgr.currentMode = mode;
this.patches = previousPatches };
previousPatches = [] this.setDefaultMode = stateMgr.setDefaultMode.cl_bind(stateMgr);
}
}
State.prototype.addToUndoStack = addToStack(undoStack) this.addDiffs = (oldContent, newContent, diffs) => {
State.prototype.addToRedoStack = addToStack(redoStack) const patches = this.options.patchHandler.makePatches(oldContent, newContent, diffs);
patches.cl_each(patch => currentPatches.push(patch));
var stateMgr = new StateMgr() };
this.setCurrentMode = function (mode) {
stateMgr.currentMode = mode
}
this.setDefaultMode = stateMgr.setDefaultMode.cl_bind(stateMgr)
this.addDiffs = function (oldContent, newContent, diffs) {
var patches = self.options.patchHandler.makePatches(oldContent, newContent, diffs)
currentPatches.push.apply(currentPatches, patches)
}
function saveCurrentPatches() { function saveCurrentPatches() {
// Move currentPatches into previousPatches // Move currentPatches into previousPatches
Array.prototype.push.apply(previousPatches, currentPatches) Array.prototype.push.apply(previousPatches, currentPatches);
currentPatches = [] currentPatches = [];
} }
this.saveState = debounce(function () { this.saveState = debounce(() => {
redoStack.length = 0 redoStack.length = 0;
if (!stateMgr.isBufferState()) { if (!stateMgr.isBufferState()) {
currentState.addToUndoStack() currentState.addToUndoStack();
// Limit the size of the stack // Limit the size of the stack
while (undoStack.length > self.options.undoStackMaxSize) { while (undoStack.length > this.options.undoStackMaxSize) {
undoStack.shift() undoStack.shift();
} }
} }
saveCurrentPatches() saveCurrentPatches();
currentState = new State() currentState = new State();
stateMgr.saveMode() stateMgr.saveMode();
self.$trigger('undoStateChange') this.$trigger('undoStateChange');
}) });
this.canUndo = function () { this.canUndo = () => !!undoStack.length;
return !!undoStack.length this.canRedo = () => !!redoStack.length;
}
this.canRedo = function () { const restoreState = (patchesParam, isForward) => {
return !!redoStack.length let patches = patchesParam;
}
function restoreState(patches, isForward) {
// Update editor // Update editor
var content = editor.getContent() const content = editor.getContent();
if (!isForward) { if (!isForward) {
patches = self.options.patchHandler.reversePatches(patches) patches = this.options.patchHandler.reversePatches(patches);
} }
var newContent = self.options.patchHandler.applyPatches(patches, content) const newContent = this.options.patchHandler.applyPatches(patches, content);
var newContentText = newContent.text || newContent const newContentText = newContent.text || newContent;
var range = editor.setContent(newContentText, true) const range = editor.setContent(newContentText, true);
var selection = newContent.selection || { const selection = newContent.selection || {
start: range.end, start: range.end,
end: range.end end: range.end,
} };
selectionMgr.setSelectionStartEnd(selection.start, selection.end) selectionMgr.setSelectionStartEnd(selection.start, selection.end);
selectionMgr.updateCursorCoordinates(true) selectionMgr.updateCursorCoordinates(true);
stateMgr.resetMode() stateMgr.resetMode();
self.$trigger('undoStateChange') this.$trigger('undoStateChange');
editor.adjustCursorPosition() editor.adjustCursorPosition();
} };
this.undo = function () { this.undo = () => {
var state = undoStack.pop() const state = undoStack.pop();
if (!state) { if (!state) {
return return;
}
saveCurrentPatches()
currentState.addToRedoStack()
restoreState(currentState.patches)
previousPatches = state.patches
currentState = state
} }
saveCurrentPatches();
currentState.addToRedoStack();
restoreState(currentState.patches);
previousPatches = state.patches;
currentState = state;
};
this.redo = function () { this.redo = () => {
var state = redoStack.pop() const state = redoStack.pop();
if (!state) { if (!state) {
return return;
}
currentState.addToUndoStack()
restoreState(state.patches, true)
previousPatches = state.patches
currentState = state
} }
currentState.addToUndoStack();
restoreState(state.patches, true);
previousPatches = state.patches;
currentState = state;
};
this.init = function (options) { this.init = (options) => {
self.options.cl_extend(options || {}) this.options.cl_extend(options || {});
selectionMgr = editor.selectionMgr selectionMgr = editor.selectionMgr;
if (!currentState) { if (!currentState) {
currentState = new State() currentState = new State();
}
} }
};
} }
cledit.UndoMgr = UndoMgr cledit.UndoMgr = UndoMgr;

View File

@ -1,123 +1,128 @@
var cledit = require('./cleditCore') import cledit from './cleditCore';
var Utils = { const Utils = {
isGecko: 'MozAppearance' in document.documentElement.style, isGecko: 'MozAppearance' in document.documentElement.style,
isWebkit: 'WebkitAppearance' in document.documentElement.style, isWebkit: 'WebkitAppearance' in document.documentElement.style,
isMsie: 'msTransform' in document.documentElement.style, isMsie: 'msTransform' in document.documentElement.style,
isMac: navigator.userAgent.indexOf('Mac OS X') !== -1 isMac: navigator.userAgent.indexOf('Mac OS X') !== -1,
} };
// Faster than setTimeout(0). Credit: https://github.com/stefanpenner/es6-promise // Faster than setTimeout(0). Credit: https://github.com/stefanpenner/es6-promise
Utils.defer = (function () { Utils.defer = (() => {
var queue = new Array(1000) const queue = new Array(1000);
var queueLength = 0 let queueLength = 0;
function flush() { function flush() {
for (var i = 0; i < queueLength; i++) { for (let i = 0; i < queueLength; i += 1) {
try { try {
queue[i]() queue[i]();
} catch (e) { } catch (e) {
console.error(e.message, e.stack) // eslint-disable-next-line no-console
console.error(e.message, e.stack);
} }
queue[i] = undefined queue[i] = undefined;
} }
queueLength = 0 queueLength = 0;
} }
var iterations = 0 let iterations = 0;
var observer = new window.MutationObserver(flush) const observer = new window.MutationObserver(flush);
var node = document.createTextNode('') const node = document.createTextNode('');
observer.observe(node, { characterData: true }) observer.observe(node, { characterData: true });
return function (fn) { return (fn) => {
queue[queueLength++] = fn queue[queueLength] = fn;
queueLength += 1;
if (queueLength === 1) { if (queueLength === 1) {
node.data = (iterations = ++iterations % 2) iterations = (iterations + 1) % 2;
node.data = iterations;
} }
} };
})() })();
Utils.debounce = function (func, wait) { Utils.debounce = (func, wait) => {
var timeoutId, isExpected let timeoutId;
let isExpected;
return wait return wait
? function () { ? () => {
clearTimeout(timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(func, wait) timeoutId = setTimeout(func, wait);
} }
: function () { : () => {
if (!isExpected) { if (!isExpected) {
isExpected = true isExpected = true;
Utils.defer(function () { Utils.defer(() => {
isExpected = false isExpected = false;
func() func();
}) });
}
}
} }
};
};
Utils.createEventHooks = function (object) { Utils.createEventHooks = (object) => {
var listenerMap = Object.create(null) const listenerMap = Object.create(null);
object.$trigger = function (eventType) { object.$trigger = (eventType, ...args) => {
var listeners = listenerMap[eventType] const listeners = listenerMap[eventType];
if (listeners) { if (listeners) {
var args = Array.prototype.slice.call(arguments, 1) listeners.cl_each((listener) => {
listeners.cl_each(function (listener) {
try { try {
listener.apply(object, args) listener.apply(object, args);
} catch (e) { } catch (e) {
console.error(e.message, e.stack) console.error(e.message, e.stack);
} }
}) });
} }
} };
object.on = function (eventType, listener) { object.on = (eventType, listener) => {
var listeners = listenerMap[eventType] let listeners = listenerMap[eventType];
if (!listeners) { if (!listeners) {
listeners = [] listeners = [];
listenerMap[eventType] = listeners listenerMap[eventType] = listeners;
} }
listeners.push(listener) listeners.push(listener);
} };
object.off = function (eventType, listener) { object.off = (eventType, listener) => {
var listeners = listenerMap[eventType] const listeners = listenerMap[eventType];
if (listeners) { if (listeners) {
var index = listeners.indexOf(listener) const index = listeners.indexOf(listener);
if (~index) { if (index !== -1) {
listeners.splice(index, 1) listeners.splice(index, 1);
}
}
} }
} }
};
};
Utils.findContainer = function (elt, offset) { Utils.findContainer = (elt, offset) => {
var containerOffset = 0 let containerOffset = 0;
var container let container;
let child = elt;
do { do {
container = elt container = child;
elt = elt.firstChild child = child.firstChild;
if (elt) { if (child) {
do { do {
var len = elt.textContent.length const len = child.textContent.length;
if (containerOffset <= offset && containerOffset + len > offset) { if (containerOffset <= offset && containerOffset + len > offset) {
break break;
} }
containerOffset += len containerOffset += len;
} while ((elt = elt.nextSibling)) child = child.nextSibling;
} while (child);
} }
} while (elt && elt.firstChild && elt.nodeType !== 3) } while (child && child.firstChild && child.nodeType !== 3);
if (elt) { if (child) {
return { return {
container: elt, container: child,
offsetInContainer: offset - containerOffset offsetInContainer: offset - containerOffset,
} };
} }
while (container.lastChild) { while (container.lastChild) {
container = container.lastChild container = container.lastChild;
} }
return { return {
container: container, container,
offsetInContainer: container.nodeType === 3 ? container.textContent.length : 0 offsetInContainer: container.nodeType === 3 ? container.textContent.length : 0,
} };
} };
cledit.Utils = Utils cledit.Utils = Utils;

View File

@ -1,33 +1,34 @@
var cledit = require('./cleditCore') import cledit from './cleditCore';
function Watcher(editor, listener) { function Watcher(editor, listener) {
this.isWatching = false this.isWatching = false;
var contentObserver let contentObserver;
this.startWatching = function () { this.startWatching = () => {
this.stopWatching() this.stopWatching();
this.isWatching = true this.isWatching = true;
contentObserver = new window.MutationObserver(listener) contentObserver = new window.MutationObserver(listener);
contentObserver.observe(editor.$contentElt, { contentObserver.observe(editor.$contentElt, {
childList: true, childList: true,
subtree: true, subtree: true,
characterData: true characterData: true,
}) });
} };
this.stopWatching = function () { this.stopWatching = () => {
if (contentObserver) { if (contentObserver) {
contentObserver.disconnect() contentObserver.disconnect();
contentObserver = undefined contentObserver = undefined;
} }
this.isWatching = false this.isWatching = false;
} };
this.noWatch = function (cb) { this.noWatch = (cb) => {
if (this.isWatching === true) { if (this.isWatching === true) {
this.stopWatching() this.stopWatching();
cb() cb();
return this.startWatching() this.startWatching();
} } else {
cb() cb();
} }
};
} }
cledit.Watcher = Watcher cledit.Watcher = Watcher;

View File

@ -2,7 +2,7 @@ import Vue from 'vue';
import DiffMatchPatch from 'diff-match-patch'; import DiffMatchPatch from 'diff-match-patch';
import Prism from 'prismjs'; import Prism from 'prismjs';
import markdownItPandocRenderer from 'markdown-it-pandoc-renderer'; import markdownItPandocRenderer from 'markdown-it-pandoc-renderer';
import cledit from '../libs/cledit'; import cledit from './cledit';
import pagedown from '../libs/pagedown'; import pagedown from '../libs/pagedown';
import htmlSanitizer from '../libs/htmlSanitizer'; import htmlSanitizer from '../libs/htmlSanitizer';
import markdownConversionSvc from './markdownConversionSvc'; import markdownConversionSvc from './markdownConversionSvc';

View File

@ -1,5 +1,5 @@
import DiffMatchPatch from 'diff-match-patch'; import DiffMatchPatch from 'diff-match-patch';
import cledit from '../libs/cledit'; import cledit from './cledit';
import utils from './utils'; import utils from './utils';
import diffUtils from './diffUtils'; import diffUtils from './diffUtils';
import store from '../store'; import store from '../store';

View File

@ -1,5 +1,5 @@
import DiffMatchPatch from 'diff-match-patch'; import DiffMatchPatch from 'diff-match-patch';
import cledit from '../libs/cledit'; import cledit from './cledit';
import animationSvc from './animationSvc'; import animationSvc from './animationSvc';
import store from '../store'; import store from '../store';

View File

@ -1,4 +1,4 @@
import cledit from '../../libs/cledit'; import cledit from '../cledit';
import editorSvc from '../editorSvc'; import editorSvc from '../editorSvc';
const Keystroke = cledit.Keystroke; const Keystroke = cledit.Keystroke;

View File

@ -2,7 +2,7 @@ import DiffMatchPatch from 'diff-match-patch';
import moduleTemplate from './moduleTemplate'; import moduleTemplate from './moduleTemplate';
import empty from '../data/emptyContent'; import empty from '../data/emptyContent';
import utils from '../services/utils'; import utils from '../services/utils';
import cledit from '../libs/cledit'; import cledit from '../services/cledit';
const diffMatchPatch = new DiffMatchPatch(); const diffMatchPatch = new DiffMatchPatch();

View File

@ -1,4 +1,4 @@
import createLogger from 'vuex/dist/logger'; // import createLogger from 'vuex/dist/logger';
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import utils from '../services/utils'; import utils from '../services/utils';
@ -120,7 +120,7 @@ const store = new Vuex.Store({
}, },
}, },
strict: debug, strict: debug,
plugins: debug ? [createLogger()] : [], // plugins: debug ? [createLogger()] : [],
}); });
setInterval(() => { setInterval(() => {