Stackedit/public/res/editor.js

846 lines
30 KiB
JavaScript
Raw Normal View History

2014-03-16 02:13:42 +00:00
/* jshint -W084, -W099 */
define([
'jquery',
2014-03-17 02:01:46 +00:00
'underscore',
2014-03-18 01:10:22 +00:00
'settings',
2014-03-16 02:13:42 +00:00
'eventMgr',
'prism-core',
2014-03-25 00:23:42 +00:00
'diff_match_patch_uncompressed',
2014-03-27 00:20:08 +00:00
'jsondiffpatch',
2014-03-17 02:01:46 +00:00
'crel',
2014-03-21 00:36:28 +00:00
'MutationObservers',
2014-03-16 02:13:42 +00:00
'libs/prism-markdown'
2014-03-27 00:20:08 +00:00
], function ($, _, settings, eventMgr, Prism, diff_match_patch, jsondiffpatch, crel) {
2014-03-19 00:33:57 +00:00
2014-03-23 02:33:41 +00:00
function strSplice(str, i, remove, add) {
2014-03-17 02:01:46 +00:00
remove = +remove || 0;
add = add || '';
2014-03-23 02:33:41 +00:00
return str.slice(0, i) + add + str.slice(i + remove);
}
2014-03-17 02:01:46 +00:00
2014-03-18 01:10:22 +00:00
var editor = {};
var selectionStart = 0;
var selectionEnd = 0;
var scrollTop = 0;
var inputElt;
2014-03-19 22:10:59 +00:00
var $inputElt;
2014-03-18 01:10:22 +00:00
var previewElt;
var pagedownEditor;
var refreshPreviewLater = (function() {
var elapsedTime = 0;
var refreshPreview = function() {
var startTime = Date.now();
pagedownEditor.refreshPreview();
elapsedTime = Date.now() - startTime;
};
if(settings.lazyRendering === true) {
return _.debounce(refreshPreview, 500);
}
return function() {
setTimeout(refreshPreview, elapsedTime < 2000 ? elapsedTime : 2000);
};
})();
eventMgr.addListener('onPagedownConfigure', function(editor) {
pagedownEditor = editor;
2014-03-16 02:13:42 +00:00
});
2014-03-17 02:01:46 +00:00
eventMgr.addListener('onSectionsCreated', function(newSectionList) {
updateSectionList(newSectionList);
highlightSections();
2014-03-18 01:10:22 +00:00
if(fileChanged === true) {
// Refresh preview synchronously
pagedownEditor.refreshPreview();
}
else {
refreshPreviewLater();
}
2014-03-17 02:01:46 +00:00
});
2014-03-16 02:13:42 +00:00
2014-03-18 01:10:22 +00:00
var fileChanged = true;
var fileDesc;
eventMgr.addListener('onFileSelected', function(selectedFileDesc) {
2014-03-17 02:01:46 +00:00
fileChanged = true;
2014-03-18 01:10:22 +00:00
fileDesc = selectedFileDesc;
2014-03-17 02:01:46 +00:00
});
2014-03-16 02:13:42 +00:00
2014-03-30 01:44:51 +00:00
// Watcher used to detect editor changes
function Watcher() {
this.isWatching = false;
var contentObserver;
this.startWatching = function() {
this.isWatching = true;
contentObserver = contentObserver || new MutationObserver(checkContentChange);
2014-03-29 01:22:24 +00:00
contentObserver.observe(editor.contentElt, {
childList: true,
subtree: true,
characterData: true
});
2014-03-30 01:44:51 +00:00
};
this.stopWatching = function() {
contentObserver.disconnect();
this.isWatching = false;
};
this.noWatch = function(cb) {
if(this.isWatching === true) {
this.stopWatching();
cb();
this.startWatching();
}
else {
cb();
}
};
}
var watcher = new Watcher();
editor.watcher = watcher;
function setValue(value) {
var startOffset = diffMatchPatch.diff_commonPrefix(previousTextContent, value);
var endOffset = Math.min(
diffMatchPatch.diff_commonSuffix(previousTextContent, value),
previousTextContent.length - startOffset,
value.length - startOffset
);
var replacement = value.substring(startOffset, value.length - endOffset);
var range = createRange(startOffset, previousTextContent.length - endOffset);
range.deleteContents();
range.insertNode(document.createTextNode(replacement));
}
function setValueNoWatch(value) {
setValue(value);
previousTextContent = value;
}
editor.setValueNoWatch = setValueNoWatch;
function setSelectionStartEnd(start, end) {
selectionStart = start;
selectionEnd = end;
fileDesc.editorStart = selectionStart;
fileDesc.editorEnd = selectionEnd;
var range = createRange(start, end);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
function createRange(start, end) {
var range = document.createRange();
var offset = _.isObject(start) ? start : findOffset(start);
range.setStart(offset.element, offset.offset);
if (end && end != start) {
offset = _.isObject(end) ? end : findOffset(end);
2014-03-29 01:22:24 +00:00
}
2014-03-30 01:44:51 +00:00
range.setEnd(offset.element, offset.offset);
return range;
2014-03-27 00:20:08 +00:00
}
2014-03-28 00:49:49 +00:00
var diffMatchPatch = new diff_match_patch();
var jsonDiffPatch = jsondiffpatch.create({
objectHash: function(obj) {
return JSON.stringify(obj);
},
arrays: {
detectMove: false,
},
textDiff: {
minLength: 9999999
}
});
2014-03-25 00:23:42 +00:00
var previousTextContent;
2014-03-30 01:44:51 +00:00
function UndoManager() {
2014-03-26 00:29:34 +00:00
var undoStack = [];
var redoStack = [];
2014-03-25 00:23:42 +00:00
var lastTime;
2014-03-26 00:29:34 +00:00
var lastMode;
var currentState;
var selectionStartBefore;
var selectionEndBefore;
2014-03-30 01:44:51 +00:00
this.setCommandMode = function() {
this.currentMode = 'command';
2014-03-26 00:29:34 +00:00
};
2014-03-30 01:44:51 +00:00
this.setMode = function() {}; // For compatibility with PageDown
this.onButtonStateChange = function() {}; // To be overridden by PageDown
this.saveState = function() {
2014-03-26 00:29:34 +00:00
redoStack = [];
2014-03-25 00:23:42 +00:00
var currentTime = Date.now();
2014-03-30 01:44:51 +00:00
if(this.currentMode == 'comment' || (this.currentMode != lastMode && lastMode != 'newlines') || currentTime - lastTime > 1000) {
2014-03-26 00:29:34 +00:00
undoStack.push(currentState);
// Limit the size of the stack
if(undoStack.length === 100) {
undoStack.shift();
}
2014-03-25 00:23:42 +00:00
}
2014-03-26 00:29:34 +00:00
else {
selectionStartBefore = currentState.selectionStartBefore;
selectionEndBefore = currentState.selectionEndBefore;
2014-03-25 00:23:42 +00:00
}
2014-03-26 00:29:34 +00:00
currentState = {
selectionStartBefore: selectionStartBefore,
selectionEndBefore: selectionEndBefore,
selectionStartAfter: selectionStart,
selectionEndAfter: selectionEnd,
content: previousTextContent,
2014-03-27 00:20:08 +00:00
discussionListJSON: fileDesc.discussionListJSON
2014-03-26 00:29:34 +00:00
};
lastTime = currentTime;
2014-03-30 01:44:51 +00:00
lastMode = this.currentMode;
this.currentMode = undefined;
this.onButtonStateChange();
2014-03-26 00:29:34 +00:00
};
2014-03-30 01:44:51 +00:00
this.saveSelectionState = _.debounce(function() {
if(this.currentMode === undefined) {
2014-03-26 00:29:34 +00:00
selectionStartBefore = selectionStart;
selectionEndBefore = selectionEnd;
2014-03-25 00:23:42 +00:00
}
2014-03-26 00:29:34 +00:00
}, 10);
2014-03-30 01:44:51 +00:00
this.canUndo = function() {
2014-03-26 00:29:34 +00:00
return undoStack.length;
};
2014-03-30 01:44:51 +00:00
this.canRedo = function() {
2014-03-26 00:29:34 +00:00
return redoStack.length;
};
2014-03-30 01:44:51 +00:00
var self = this;
2014-03-26 00:29:34 +00:00
function restoreState(state, selectionStart, selectionEnd) {
2014-03-27 00:20:08 +00:00
// Update editor
2014-03-30 01:44:51 +00:00
watcher.noWatch(function() {
2014-03-27 00:20:08 +00:00
if(previousTextContent != state.content) {
2014-03-30 01:44:51 +00:00
setValueNoWatch(state.content);
fileDesc.content = state.content;
eventMgr.onContentChanged(fileDesc, state.content);
2014-03-27 00:20:08 +00:00
}
2014-03-30 01:44:51 +00:00
setSelectionStartEnd(selectionStart, selectionEnd);
2014-03-27 00:20:08 +00:00
var discussionListJSON = fileDesc.discussionListJSON;
if(discussionListJSON != state.discussionListJSON) {
var oldDiscussionList = fileDesc.discussionList;
fileDesc.discussionListJSON = state.discussionListJSON;
var newDiscussionList = fileDesc.discussionList;
var diff = jsonDiffPatch.diff(oldDiscussionList, newDiscussionList);
var commentsChanged = false;
_.each(diff, function(discussionDiff, discussionIndex) {
if(!_.isArray(discussionDiff)) {
commentsChanged = true;
}
else if(discussionDiff.length === 1) {
eventMgr.onDiscussionCreated(fileDesc, newDiscussionList[discussionIndex]);
}
else {
eventMgr.onDiscussionRemoved(fileDesc, oldDiscussionList[discussionIndex]);
}
});
commentsChanged && eventMgr.onCommentsChanged(fileDesc);
}
});
2014-03-26 00:29:34 +00:00
selectionStartBefore = selectionStart;
selectionEndBefore = selectionEnd;
currentState = state;
2014-03-30 01:44:51 +00:00
self.currentMode = undefined;
2014-03-26 00:29:34 +00:00
lastMode = undefined;
2014-03-30 01:44:51 +00:00
self.onButtonStateChange();
2014-03-26 00:29:34 +00:00
adjustCursorPosition();
2014-03-25 00:23:42 +00:00
}
2014-03-30 01:44:51 +00:00
this.undo = function() {
2014-03-26 00:29:34 +00:00
var state = undoStack.pop();
if(!state) {
return;
}
redoStack.push(currentState);
restoreState(state, currentState.selectionStartBefore, currentState.selectionEndBefore);
2014-03-25 00:23:42 +00:00
};
2014-03-30 01:44:51 +00:00
this.redo = function() {
2014-03-26 00:29:34 +00:00
var state = redoStack.pop();
if(!state) {
return;
}
undoStack.push(currentState);
restoreState(state, state.selectionStartAfter, state.selectionEndAfter);
};
2014-03-30 01:44:51 +00:00
this.init = function() {
2014-03-26 00:29:34 +00:00
var content = fileDesc.content;
undoStack = [];
redoStack = [];
lastTime = 0;
currentState = {
selectionStartAfter: fileDesc.selectionStart,
selectionEndAfter: fileDesc.selectionEnd,
content: content,
2014-03-27 00:20:08 +00:00
discussionListJSON: fileDesc.discussionListJSON
2014-03-26 00:29:34 +00:00
};
2014-03-30 01:44:51 +00:00
this.currentMode = undefined;
2014-03-26 00:29:34 +00:00
lastMode = undefined;
editor.contentElt.textContent = content;
};
2014-03-30 01:44:51 +00:00
}
var undoManager = new UndoManager();
editor.undoManager = undoManager;
2014-03-25 00:23:42 +00:00
2014-03-27 00:20:08 +00:00
function onComment() {
2014-03-30 01:44:51 +00:00
if(watcher.isWatching === true) {
undoManager.currentMode = 'comment';
undoManager.saveState();
2014-03-27 00:20:08 +00:00
}
}
eventMgr.addListener('onDiscussionCreated', onComment);
eventMgr.addListener('onDiscussionRemoved', onComment);
eventMgr.addListener('onCommentsChanged', onComment);
2014-03-24 00:22:46 +00:00
function saveSelectionState() {
2014-03-26 00:29:34 +00:00
if(fileChanged === false) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
var element = range.startContainer;
if ((inputElt.compareDocumentPosition(element) & 0x10)) {
var container = element;
var offset = range.startOffset;
do {
while (element = element.previousSibling) {
if (element.textContent) {
offset += element.textContent.length;
}
2014-03-24 00:22:46 +00:00
}
2014-03-26 00:29:34 +00:00
element = container = container.parentNode;
} while (element && element != inputElt);
selectionStart = offset;
selectionEnd = offset + (range + '').length;
}
2014-03-24 00:22:46 +00:00
}
2014-03-18 01:10:22 +00:00
fileDesc.editorStart = selectionStart;
fileDesc.editorEnd = selectionEnd;
2014-03-21 00:36:28 +00:00
}
2014-03-30 01:44:51 +00:00
undoManager.saveSelectionState();
2014-03-21 00:36:28 +00:00
}
function checkContentChange() {
2014-03-24 00:22:46 +00:00
saveSelectionState();
2014-03-21 00:36:28 +00:00
var currentTextContent = inputElt.textContent;
if(fileChanged === false) {
2014-03-18 01:10:22 +00:00
if(currentTextContent == previousTextContent) {
return;
}
2014-03-19 00:33:57 +00:00
if(!/\n$/.test(currentTextContent)) {
currentTextContent += '\n';
}
2014-03-30 01:44:51 +00:00
undoManager.currentMode = undoManager.currentMode || 'typing';
2014-03-27 00:20:08 +00:00
var changes = diffMatchPatch.diff_main(previousTextContent, currentTextContent);
// Move comments according to changes
var updateDiscussionList = false;
var startOffset = 0;
var discussionList = _.values(fileDesc.discussionList);
fileDesc.newDiscussion && discussionList.push(fileDesc.newDiscussion);
changes.forEach(function(change) {
var changeType = change[0];
var changeText = change[1];
if(changeType === 0) {
startOffset += changeText.length;
return;
}
var endOffset = startOffset;
var diffOffset = changeText.length;
if(changeType === -1) {
endOffset += diffOffset;
diffOffset = -diffOffset;
}
discussionList.forEach(function(discussion) {
// selectionEnd
if(discussion.selectionEnd >= endOffset) {
discussion.selectionEnd += diffOffset;
updateDiscussionList = true;
2014-03-25 00:23:42 +00:00
}
2014-03-27 00:20:08 +00:00
else if(discussion.selectionEnd > startOffset) {
discussion.selectionEnd = startOffset;
updateDiscussionList = true;
}
// selectionStart
if(discussion.selectionStart >= endOffset) {
discussion.selectionStart += diffOffset;
updateDiscussionList = true;
}
else if(discussion.selectionStart > startOffset) {
discussion.selectionStart = startOffset;
updateDiscussionList = true;
2014-03-25 00:23:42 +00:00
}
});
2014-03-27 00:20:08 +00:00
startOffset = endOffset;
});
if(updateDiscussionList === true) {
fileDesc.discussionList = fileDesc.discussionList; // Write discussionList in localStorage
2014-03-24 00:22:46 +00:00
}
2014-03-18 01:10:22 +00:00
fileDesc.content = currentTextContent;
2014-03-23 02:33:41 +00:00
eventMgr.onContentChanged(fileDesc, currentTextContent);
2014-03-27 00:20:08 +00:00
updateDiscussionList && eventMgr.onCommentsChanged(fileDesc);
2014-03-26 00:29:34 +00:00
previousTextContent = currentTextContent;
2014-03-30 01:44:51 +00:00
undoManager.saveState();
2014-03-18 01:10:22 +00:00
}
else {
2014-03-19 00:33:57 +00:00
if(!/\n$/.test(currentTextContent)) {
currentTextContent += '\n';
fileDesc.content = currentTextContent;
}
2014-03-18 01:10:22 +00:00
selectionStart = fileDesc.editorStart;
selectionEnd = fileDesc.editorEnd;
2014-03-26 00:29:34 +00:00
eventMgr.onFileOpen(fileDesc, currentTextContent);
previewElt.scrollTop = fileDesc.previewScrollTop;
2014-03-18 01:10:22 +00:00
scrollTop = fileDesc.editorScrollTop;
inputElt.scrollTop = scrollTop;
2014-03-26 00:29:34 +00:00
previousTextContent = currentTextContent;
2014-03-18 01:10:22 +00:00
fileChanged = false;
}
}
2014-03-19 00:33:57 +00:00
2014-03-30 01:44:51 +00:00
function findOffset(offset) {
var walker = document.createTreeWalker(editor.contentElt, 4);
while(walker.nextNode()) {
var text = walker.currentNode.nodeValue || '';
if (text.length > offset) {
2014-03-23 02:33:41 +00:00
return {
2014-03-30 01:44:51 +00:00
element: walker.currentNode,
offset: offset
2014-03-23 02:33:41 +00:00
};
}
2014-03-30 01:44:51 +00:00
offset -= text.length;
2014-03-21 00:36:28 +00:00
}
2014-03-23 02:33:41 +00:00
return {
element: editor.contentElt,
offset: 0,
error: true
};
}
function getCoordinates(inputOffset, element, offset) {
var x = 0;
var y = 0;
if(element.textContent == '\n') {
y = element.parentNode.offsetTop + element.parentNode.offsetHeight / 2;
2014-03-21 00:36:28 +00:00
}
else {
2014-03-23 02:33:41 +00:00
var selectedChar = inputElt.textContent[inputOffset];
var selectionRange;
2014-03-21 00:36:28 +00:00
if(selectedChar === undefined || selectedChar == '\n') {
2014-03-30 01:44:51 +00:00
selectionRange = createRange(inputOffset - 1, {
2014-03-23 02:33:41 +00:00
element: element,
offset: offset
});
2014-03-19 00:33:57 +00:00
}
else {
2014-03-30 01:44:51 +00:00
selectionRange = createRange({
2014-03-23 02:33:41 +00:00
element: element,
offset: offset
}, inputOffset + 1);
2014-03-19 00:33:57 +00:00
}
2014-03-21 00:36:28 +00:00
var selectionRect = selectionRange.getBoundingClientRect();
2014-03-23 02:33:41 +00:00
y = selectionRect.top + selectionRect.height / 2 - inputElt.offsetTop + inputElt.scrollTop;
2014-03-21 00:36:28 +00:00
selectionRange.detach();
}
2014-03-23 02:33:41 +00:00
return {
x: x,
y: y
};
}
var cursorY = 0;
var isBackwardSelection = false;
function updateCursorCoordinates() {
2014-03-24 00:22:46 +00:00
saveSelectionState();
2014-03-23 02:33:41 +00:00
$inputElt.toggleClass('has-selection', selectionStart !== selectionEnd);
var element;
var offset;
var inputOffset;
if(inputElt.focused) {
isBackwardSelection = false;
var selection = window.getSelection();
if(!selection.rangeCount) {
return;
}
if (!selection.isCollapsed) {
var range = document.createRange();
range.setStart(selection.anchorNode, selection.anchorOffset);
range.setEnd(selection.focusNode, selection.focusOffset);
isBackwardSelection = range.collapsed;
range.detach();
}
var selectionRange = selection.getRangeAt(0);
element = isBackwardSelection ? selectionRange.startContainer : selectionRange.endContainer;
offset = isBackwardSelection ? selectionRange.startOffset : selectionRange.endOffset;
inputOffset = isBackwardSelection ? selectionStart : selectionEnd;
}
else {
inputOffset = isBackwardSelection ? selectionStart : selectionEnd;
var elementOffset = findOffset(inputOffset);
element = elementOffset.element;
offset = elementOffset.offset;
}
var coordinates = getCoordinates(inputOffset, element, offset);
cursorY = coordinates.y;
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
2014-03-21 00:36:28 +00:00
}
2014-03-24 00:22:46 +00:00
var adjustCursorPosition = _.debounce(function() {
if(inputElt === undefined) {
return;
}
updateCursorCoordinates();
2014-03-19 00:33:57 +00:00
2014-03-24 00:22:46 +00:00
var adjust = inputElt.offsetHeight / 2;
if(adjust > 130) {
adjust = 130;
}
var cursorMinY = inputElt.scrollTop + adjust;
var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjust;
if(cursorY < cursorMinY) {
inputElt.scrollTop += cursorY - cursorMinY;
}
else if(cursorY > cursorMaxY) {
inputElt.scrollTop += cursorY - cursorMaxY;
}
}, 0);
2014-03-19 00:33:57 +00:00
eventMgr.addListener('onLayoutResize', adjustCursorPosition);
2014-03-18 01:10:22 +00:00
editor.init = function(elt1, elt2) {
inputElt = elt1;
2014-03-19 22:10:59 +00:00
$inputElt = $(inputElt);
2014-03-18 01:10:22 +00:00
previewElt = elt2;
2014-03-21 00:36:28 +00:00
2014-03-18 01:10:22 +00:00
editor.contentElt = crel('div', {
2014-03-19 22:10:59 +00:00
class: 'editor-content',
2014-03-18 01:10:22 +00:00
contenteditable: true
});
inputElt.appendChild(editor.contentElt);
2014-03-21 00:36:28 +00:00
editor.$contentElt = $(editor.contentElt);
2014-03-19 00:33:57 +00:00
2014-03-21 00:36:28 +00:00
editor.marginElt = crel('div', {
class: 'editor-margin'
});
inputElt.appendChild(editor.marginElt);
editor.$marginElt = $(editor.marginElt);
2014-03-30 01:44:51 +00:00
watcher.startWatching();
2014-03-21 00:36:28 +00:00
2014-03-24 00:22:46 +00:00
$(inputElt).scroll(function() {
scrollTop = inputElt.scrollTop;
if(fileChanged === false) {
fileDesc.editorScrollTop = scrollTop;
}
});
2014-03-18 01:10:22 +00:00
$(previewElt).scroll(function() {
if(fileChanged === false) {
fileDesc.previewScrollTop = previewElt.scrollTop;
}
});
2014-03-19 00:33:57 +00:00
2014-03-18 01:10:22 +00:00
inputElt.focus = function() {
editor.$contentElt.focus();
2014-03-30 01:44:51 +00:00
setSelectionStartEnd(selectionStart, selectionEnd);
2014-03-18 01:10:22 +00:00
inputElt.scrollTop = scrollTop;
2014-03-16 02:13:42 +00:00
};
2014-03-18 01:10:22 +00:00
editor.$contentElt.focus(function() {
inputElt.focused = true;
2014-03-16 02:13:42 +00:00
});
2014-03-18 01:10:22 +00:00
editor.$contentElt.blur(function() {
inputElt.focused = false;
2014-03-16 02:13:42 +00:00
});
2014-03-19 00:33:57 +00:00
2014-03-18 01:10:22 +00:00
Object.defineProperty(inputElt, 'value', {
2014-03-16 02:13:42 +00:00
get: function () {
return this.textContent;
},
2014-03-30 01:44:51 +00:00
set: setValue
2014-03-16 02:13:42 +00:00
});
2014-03-19 00:33:57 +00:00
2014-03-18 01:10:22 +00:00
Object.defineProperty(inputElt, 'selectionStart', {
2014-03-16 02:13:42 +00:00
get: function () {
2014-03-24 00:22:46 +00:00
return selectionStart;
2014-03-16 02:13:42 +00:00
},
set: function (value) {
2014-03-30 01:44:51 +00:00
setSelectionStartEnd(value, selectionEnd);
2014-03-16 02:13:42 +00:00
},
enumerable: true,
configurable: true
});
2014-03-18 01:10:22 +00:00
Object.defineProperty(inputElt, 'selectionEnd', {
2014-03-16 02:13:42 +00:00
get: function () {
2014-03-24 00:22:46 +00:00
return selectionEnd;
2014-03-16 02:13:42 +00:00
},
set: function (value) {
2014-03-30 01:44:51 +00:00
setSelectionStartEnd(selectionStart, value);
2014-03-16 02:13:42 +00:00
},
enumerable: true,
configurable: true
});
2014-03-30 01:44:51 +00:00
inputElt.setSelectionStartEnd = setSelectionStartEnd;
inputElt.createRange = createRange;
2014-03-23 02:33:41 +00:00
inputElt.getOffsetCoordinates = function(ss) {
var offset = findOffset(ss);
return getCoordinates(ss, offset.element, offset.offset);
};
2014-03-19 22:10:59 +00:00
var clearNewline = false;
2014-03-18 01:10:22 +00:00
editor.$contentElt.on('keydown', function (evt) {
2014-03-21 00:36:28 +00:00
if(
evt.which === 17 || // Ctrl
evt.which === 91 || // Cmd
evt.which === 18 || // Alt
evt.which === 16 // Shift
) {
return;
2014-03-19 00:33:57 +00:00
}
2014-03-24 00:22:46 +00:00
saveSelectionState();
2014-03-21 00:36:28 +00:00
var cmdOrCtrl = evt.metaKey || evt.ctrlKey;
2014-03-30 01:44:51 +00:00
if(!cmdOrCtrl) {
adjustCursorPosition();
}
2014-03-19 00:33:57 +00:00
2014-03-21 00:36:28 +00:00
switch (evt.which) {
2014-03-18 01:10:22 +00:00
case 9: // Tab
if (!cmdOrCtrl) {
action('indent', {
inverse: evt.shiftKey
});
evt.preventDefault();
}
break;
case 13:
action('newline');
evt.preventDefault();
break;
}
2014-03-21 00:36:28 +00:00
if(evt.which !== 13) {
2014-03-19 22:10:59 +00:00
clearNewline = false;
}
2014-03-21 00:36:28 +00:00
})
2014-03-22 01:57:31 +00:00
.on('mouseup', function() {
setTimeout(function() {
2014-03-23 02:33:41 +00:00
updateCursorCoordinates();
2014-03-22 01:57:31 +00:00
}, 0);
})
2014-03-21 00:36:28 +00:00
.on('paste', function () {
2014-03-30 01:44:51 +00:00
undoManager.currentMode = 'paste';
2014-03-21 00:36:28 +00:00
adjustCursorPosition();
})
.on('cut', function () {
2014-03-30 01:44:51 +00:00
undoManager.currentMode = 'cut';
2014-03-21 00:36:28 +00:00
adjustCursorPosition();
2014-03-18 01:10:22 +00:00
});
2014-03-19 00:33:57 +00:00
2014-03-16 02:13:42 +00:00
var action = function (action, options) {
options = options || {};
2014-03-18 01:10:22 +00:00
var text = inputElt.value,
ss = options.start || selectionStart,
se = options.end || selectionEnd,
2014-03-16 02:13:42 +00:00
state = {
ss: ss,
se: se,
before: text.slice(0, ss),
after: text.slice(se),
selection: text.slice(ss, se)
};
actions[action](state, options);
2014-03-18 01:10:22 +00:00
inputElt.value = state.before + state.selection + state.after;
2014-03-30 01:44:51 +00:00
setSelectionStartEnd(state.ss, state.se);
2014-03-19 22:10:59 +00:00
$inputElt.trigger('input');
2014-03-16 02:13:42 +00:00
};
var actions = {
indent: function (state, options) {
var lf = state.before.lastIndexOf('\n') + 1;
if (options.inverse) {
if (/\s/.test(state.before.charAt(lf))) {
2014-03-23 02:33:41 +00:00
state.before = strSplice(state.before, lf, 1);
2014-03-16 02:13:42 +00:00
state.ss--;
state.se--;
}
state.selection = state.selection.replace(/^[ \t]/gm, '');
} else if (state.selection) {
2014-03-23 02:33:41 +00:00
state.before = strSplice(state.before, lf, 0, '\t');
2014-03-16 02:13:42 +00:00
state.selection = state.selection.replace(/\r?\n(?=[\s\S])/g, '\n\t');
state.ss++;
state.se++;
} else {
state.before += '\t';
state.ss++;
state.se++;
return;
}
state.se = state.ss + state.selection.length;
},
newline: function (state) {
var lf = state.before.lastIndexOf('\n') + 1;
2014-03-19 22:10:59 +00:00
if(clearNewline) {
state.before = state.before.substring(0, lf);
state.selection = '';
state.ss = lf;
state.se = lf;
clearNewline = false;
return;
}
clearNewline = false;
var previousLine = state.before.slice(lf);
var indentMatch = previousLine.match(/^ {0,3}>[ ]*|^[ \t]*(?:[*+\-]|(\d+)\.)[ \t]|^\s+/);
var indent = (indentMatch || [''])[0];
if(indentMatch && indentMatch[1]) {
var number = parseInt(indentMatch[1], 10);
indent = indent.replace(/\d+/, number + 1);
}
if(indent.length) {
clearNewline = true;
}
2014-03-16 02:13:42 +00:00
2014-03-30 01:44:51 +00:00
undoManager.currentMode = 'newlines';
2014-03-16 02:13:42 +00:00
2014-03-19 00:33:57 +00:00
state.before += '\n' + indent;
2014-03-16 02:13:42 +00:00
state.selection = '';
state.ss += indent.length + 1;
state.se = state.ss;
},
};
2014-03-17 02:01:46 +00:00
};
2014-03-16 02:13:42 +00:00
2014-03-17 02:01:46 +00:00
var sectionList = [];
var sectionsToRemove = [];
var modifiedSections = [];
var insertBeforeSection;
function updateSectionList(newSectionList) {
modifiedSections = [];
sectionsToRemove = [];
insertBeforeSection = undefined;
// Render everything if file changed
if(fileChanged === true) {
sectionsToRemove = sectionList;
sectionList = newSectionList;
modifiedSections = newSectionList;
return;
}
// Find modified section starting from top
var leftIndex = sectionList.length;
_.some(sectionList, function(section, index) {
2014-03-19 00:33:57 +00:00
var newSection = newSectionList[index];
if(index >= newSectionList.length ||
// Check modified
section.textWithFrontMatter != newSection.textWithFrontMatter ||
2014-03-25 00:23:42 +00:00
// Check that section has not been detached or moved
section.elt.parentNode !== editor.contentElt ||
2014-03-21 00:36:28 +00:00
// Check also the content since nodes can be injected in sections via copy/paste
2014-03-25 00:23:42 +00:00
section.elt.textContent != newSection.textWithFrontMatter) {
2014-03-17 02:01:46 +00:00
leftIndex = index;
return true;
}
});
2014-03-19 00:33:57 +00:00
2014-03-17 02:01:46 +00:00
// Find modified section starting from bottom
var rightIndex = -sectionList.length;
_.some(sectionList.slice().reverse(), function(section, index) {
2014-03-19 00:33:57 +00:00
var newSection = newSectionList[newSectionList.length - index - 1];
if(index >= newSectionList.length ||
// Check modified
section.textWithFrontMatter != newSection.textWithFrontMatter ||
2014-03-25 00:23:42 +00:00
// Check that section has not been detached or moved
section.elt.parentNode !== editor.contentElt ||
2014-03-21 00:36:28 +00:00
// Check also the content since nodes can be injected in sections via copy/paste
2014-03-25 00:23:42 +00:00
section.elt.textContent != newSection.textWithFrontMatter) {
2014-03-17 02:01:46 +00:00
rightIndex = -index;
return true;
}
});
2014-03-19 00:33:57 +00:00
2014-03-17 02:01:46 +00:00
if(leftIndex - rightIndex > sectionList.length) {
// Prevent overlap
rightIndex = leftIndex - sectionList.length;
}
2014-03-19 00:33:57 +00:00
2014-03-17 02:01:46 +00:00
// Create an array composed of left unmodified, modified, right
// unmodified sections
var leftSections = sectionList.slice(0, leftIndex);
modifiedSections = newSectionList.slice(leftIndex, newSectionList.length + rightIndex);
var rightSections = sectionList.slice(sectionList.length + rightIndex, sectionList.length);
insertBeforeSection = _.first(rightSections);
sectionsToRemove = sectionList.slice(leftIndex, sectionList.length + rightIndex);
sectionList = leftSections.concat(modifiedSections).concat(rightSections);
}
2014-03-19 00:33:57 +00:00
function highlightSections() {
2014-03-18 01:10:22 +00:00
var newSectionEltList = document.createDocumentFragment();
modifiedSections.forEach(function(section) {
highlight(section);
2014-03-25 00:23:42 +00:00
newSectionEltList.appendChild(section.elt);
2014-03-18 01:10:22 +00:00
});
2014-03-30 01:44:51 +00:00
watcher.noWatch(function() {
2014-03-27 00:20:08 +00:00
if(fileChanged === true) {
editor.contentElt.innerHTML = '';
2014-03-18 01:10:22 +00:00
editor.contentElt.appendChild(newSectionEltList);
2014-03-30 01:44:51 +00:00
setSelectionStartEnd(selectionStart, selectionEnd);
2014-03-18 01:10:22 +00:00
}
2014-03-27 00:20:08 +00:00
else {
// Remove outdated sections
sectionsToRemove.forEach(function(section) {
// section can be already removed
section.elt.parentNode === editor.contentElt && editor.contentElt.removeChild(section.elt);
});
2014-03-19 00:33:57 +00:00
2014-03-27 00:20:08 +00:00
if(insertBeforeSection !== undefined) {
editor.contentElt.insertBefore(newSectionEltList, insertBeforeSection.elt);
}
else {
editor.contentElt.appendChild(newSectionEltList);
2014-03-18 01:10:22 +00:00
}
2014-03-21 00:36:28 +00:00
2014-03-27 00:20:08 +00:00
// Remove unauthorized nodes (text nodes outside of sections or duplicated sections via copy/paste)
var childNode = editor.contentElt.firstChild;
while(childNode) {
var nextNode = childNode.nextSibling;
if(!childNode.generated) {
editor.contentElt.removeChild(childNode);
}
childNode = nextNode;
}
2014-03-30 01:44:51 +00:00
setSelectionStartEnd(selectionStart, selectionEnd);
2014-03-27 00:20:08 +00:00
}
2014-03-25 00:23:42 +00:00
});
2014-03-17 02:01:46 +00:00
}
2014-03-19 00:33:57 +00:00
2014-03-17 02:01:46 +00:00
function highlight(section) {
2014-03-19 22:10:59 +00:00
var text = section.text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
text = Prism.highlight(text, Prism.languages.md);
2014-03-23 02:33:41 +00:00
var frontMatter = section.textWithFrontMatter.substring(0, section.textWithFrontMatter.length - section.text.length);
2014-03-19 22:10:59 +00:00
if(frontMatter.length) {
2014-03-21 00:36:28 +00:00
// Front matter highlighting
2014-03-19 22:10:59 +00:00
frontMatter = frontMatter.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
frontMatter = frontMatter.replace(/\n/g, '<span class="token lf">\n</span>');
text = '<span class="token md">' + frontMatter + '</span>' + text;
}
2014-03-18 01:10:22 +00:00
var sectionElt = crel('span', {
2014-03-17 02:01:46 +00:00
id: 'wmd-input-section-' + section.id,
class: 'wmd-input-section'
});
2014-03-21 00:36:28 +00:00
sectionElt.generated = true;
2014-03-19 22:10:59 +00:00
sectionElt.innerHTML = text;
2014-03-25 00:23:42 +00:00
section.elt = sectionElt;
2014-03-16 02:13:42 +00:00
}
2014-03-18 01:10:22 +00:00
return editor;
2014-03-19 00:33:57 +00:00
});