Stackedit/public/res/extensions/scrollLink.js

331 lines
12 KiB
JavaScript
Raw Normal View History

2013-05-27 19:45:33 +00:00
define([
"jquery",
"underscore",
2013-06-22 23:48:57 +00:00
"classes/Extension",
2013-09-10 16:52:27 +00:00
"text!html/scrollLinkSettingsBlock.html"
2013-06-22 23:48:57 +00:00
], function($, _, Extension, scrollLinkSettingsBlockHTML) {
2013-05-29 19:55:23 +00:00
2013-12-05 00:25:17 +00:00
var scrollLink = new Extension("scrollLink", "Scroll Link", true, true);
2013-06-22 23:48:57 +00:00
scrollLink.settingsBlock = scrollLinkSettingsBlockHTML;
2013-07-28 10:35:04 +00:00
2013-11-07 23:10:38 +00:00
var aceEditor;
2013-09-09 23:32:24 +00:00
scrollLink.onAceCreated = function(aceEditorParam) {
aceEditor = aceEditorParam;
};
2013-11-07 23:10:38 +00:00
var sectionList;
2013-07-26 00:44:12 +00:00
scrollLink.onSectionsCreated = function(sectionListParam) {
sectionList = sectionListParam;
};
var offsetBegin = 0;
scrollLink.onMarkdownTrim = function(offsetBeginParam) {
offsetBegin = offsetBeginParam;
};
2013-05-29 19:55:23 +00:00
2013-12-05 00:25:17 +00:00
var $textareaElt;
var $textareaHelperElt;
2013-11-07 23:10:38 +00:00
var $previewElt;
2013-05-29 19:55:23 +00:00
var mdSectionList = [];
var htmlSectionList = [];
2013-11-07 23:10:38 +00:00
var lastEditorScrollTop;
var lastPreviewScrollTop;
2013-05-29 19:55:23 +00:00
var buildSections = _.debounce(function() {
mdSectionList = [];
2013-09-09 23:32:24 +00:00
var mdTextOffset = 0;
2013-07-26 00:44:12 +00:00
var mdSectionOffset = 0;
var firstSectionOffset = offsetBegin;
2013-12-05 20:59:57 +00:00
var padding = 0;
2013-12-05 00:25:17 +00:00
function addTextareaSection(sectionText) {
2013-12-05 20:59:57 +00:00
var sectionHeight = padding;
2013-12-05 00:25:17 +00:00
if(sectionText !== undefined) {
2013-12-05 20:59:57 +00:00
var textNode = document.createTextNode(sectionText);
$textareaHelperElt.empty().append(textNode);
2013-12-05 00:25:17 +00:00
sectionHeight += $textareaHelperElt.prop('scrollHeight');
}
var newSectionOffset = mdSectionOffset + sectionHeight;
2013-05-29 19:55:23 +00:00
mdSectionList.push({
startOffset: mdSectionOffset,
endOffset: newSectionOffset,
height: sectionHeight
});
mdSectionOffset = newSectionOffset;
2013-12-05 00:25:17 +00:00
}
if(window.lightMode) {
// Special treatment for light mode
$textareaHelperElt.innerWidth($textareaElt.innerWidth());
_.each(sectionList, function(section, index) {
var sectionText = section.text;
if(index !== sectionList.length - 1) {
if(sectionText.length === 0) {
sectionText = undefined;
}
2013-12-05 20:59:57 +00:00
}
else {
if(/\n$/.test(sectionText)) {
// Need to add a line break to take into account a final empty line
sectionText += '\n';
}
2013-12-05 00:25:17 +00:00
}
addTextareaSection(sectionText);
});
// Apply a coef to manage divergence in some browsers
var theoricalHeight = _.last(mdSectionList).endOffset;
var realHeight = $textareaElt[0].scrollHeight;
var coef = realHeight/theoricalHeight;
mdSectionList = _.map(mdSectionList, function(mdSection) {
return {
startOffset: mdSection.startOffset * coef,
endOffset: mdSection.endOffset * coef,
height: mdSection.height * coef,
};
});
}
else {
// Everything's much simpler with ACE
_.each(sectionList, function(section) {
mdTextOffset += section.text.length + firstSectionOffset;
firstSectionOffset = 0;
var documentPosition = aceEditor.session.doc.indexToPosition(mdTextOffset);
var screenPosition = aceEditor.session.documentToScreenPosition(documentPosition.row, documentPosition.column);
var newSectionOffset = screenPosition.row * aceEditor.renderer.lineHeight;
var sectionHeight = newSectionOffset - mdSectionOffset;
mdSectionList.push({
startOffset: mdSectionOffset,
endOffset: newSectionOffset,
height: sectionHeight
});
mdSectionOffset = newSectionOffset;
});
}
2013-05-29 19:55:23 +00:00
// Try to find corresponding sections in the preview
htmlSectionList = [];
2013-11-17 22:59:03 +00:00
var htmlSectionOffset;
2013-08-22 00:19:59 +00:00
var previewScrollTop = $previewElt.scrollTop();
$previewElt.find(".preview-content > .se-section-delimiter").each(function() {
if(htmlSectionOffset === undefined) {
// Force start to 0 for the first section
htmlSectionOffset = 0;
return;
}
2013-11-17 22:59:03 +00:00
var $delimiterElt = $(this);
// Consider div scroll position
var newSectionOffset = $delimiterElt.position().top + previewScrollTop;
htmlSectionList.push({
startOffset: htmlSectionOffset,
endOffset: newSectionOffset,
height: newSectionOffset - htmlSectionOffset
});
2013-05-29 19:55:23 +00:00
htmlSectionOffset = newSectionOffset;
});
// Last section
2013-08-22 00:19:59 +00:00
var scrollHeight = $previewElt.prop('scrollHeight');
2013-05-29 19:55:23 +00:00
htmlSectionList.push({
startOffset: htmlSectionOffset,
endOffset: scrollHeight,
height: scrollHeight - htmlSectionOffset
});
2013-09-15 14:14:42 +00:00
// apply Scroll Link (-10 to have a gap > 9px)
2013-06-05 22:42:30 +00:00
lastEditorScrollTop = -10;
lastPreviewScrollTop = -10;
2013-08-22 00:19:59 +00:00
doScrollLink();
2013-05-29 19:55:23 +00:00
}, 500);
2013-06-03 22:19:52 +00:00
var isScrollEditor = false;
2013-05-29 19:55:23 +00:00
var isScrollPreview = false;
2013-09-10 16:52:27 +00:00
var isEditorMoving = false;
var isPreviewMoving = false;
var scrollingHelper = $('<div>');
var doScrollLink = _.throttle(function() {
2013-06-02 00:38:23 +00:00
if(mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) {
2013-09-09 23:32:24 +00:00
// Delay
doScrollLink();
2013-05-29 19:55:23 +00:00
return;
}
2013-12-05 00:25:17 +00:00
var editorScrollTop = window.lightMode ? $textareaElt.scrollTop() : aceEditor.renderer.getScrollTop();
editorScrollTop < 0 && (editorScrollTop = 0);
2013-08-22 00:19:59 +00:00
var previewScrollTop = $previewElt.scrollTop();
2013-09-09 23:32:24 +00:00
function getDestScrollTop(srcScrollTop, srcSectionList, destSectionList) {
2013-05-29 19:55:23 +00:00
// Find the section corresponding to the offset
2013-11-07 23:10:38 +00:00
var sectionIndex;
2013-05-29 19:55:23 +00:00
var srcSection = _.find(srcSectionList, function(section, index) {
sectionIndex = index;
return srcScrollTop < section.endOffset;
});
if(srcSection === undefined) {
// Something wrong in the algorithm...
2013-06-03 22:19:52 +00:00
return;
2013-05-29 19:55:23 +00:00
}
var posInSection = (srcScrollTop - srcSection.startOffset) / (srcSection.height || 1);
2013-05-29 19:55:23 +00:00
var destSection = destSectionList[sectionIndex];
2013-09-09 23:32:24 +00:00
return destSection.startOffset + destSection.height * posInSection;
2013-05-29 19:55:23 +00:00
}
2013-11-07 23:10:38 +00:00
var destScrollTop;
2013-05-29 23:04:52 +00:00
// Perform the animation if diff > 9px
if(isScrollEditor === true) {
if(Math.abs(editorScrollTop - lastEditorScrollTop) <= 9) {
return;
}
2013-06-03 22:19:52 +00:00
isScrollEditor = false;
2013-05-29 19:55:23 +00:00
// Animate the preview
lastEditorScrollTop = editorScrollTop;
2013-11-07 23:10:38 +00:00
destScrollTop = getDestScrollTop(editorScrollTop, mdSectionList, htmlSectionList);
2013-09-09 23:32:24 +00:00
destScrollTop = _.min([
destScrollTop,
$previewElt.prop('scrollHeight') - $previewElt.outerHeight()
]);
if(Math.abs(destScrollTop - previewScrollTop) <= 9) {
// Skip the animation if diff is <= 9
lastPreviewScrollTop = previewScrollTop;
return;
2013-09-09 23:32:24 +00:00
}
$previewElt.stop(true).animate({
scrollTop: destScrollTop
}, {
easing: 'easeOutSine',
duration: 200,
step: function(now) {
isPreviewMoving = true;
lastPreviewScrollTop = previewScrollTop + now;
},
done: function() {
_.defer(function() {
isPreviewMoving = false;
});
},
});
2013-05-29 19:55:23 +00:00
}
else if(isScrollPreview === true) {
if(Math.abs(previewScrollTop - lastPreviewScrollTop) <= 9) {
return;
}
2013-06-03 22:19:52 +00:00
isScrollPreview = false;
2013-05-29 19:55:23 +00:00
// Animate the editor
lastPreviewScrollTop = previewScrollTop;
2013-11-07 23:10:38 +00:00
destScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
2013-12-05 00:25:17 +00:00
if(window.lightMode) {
destScrollTop = _.min([
destScrollTop,
$textareaElt.prop('scrollHeight') - $textareaElt.outerHeight()
]);
}
else {
destScrollTop = _.min([
destScrollTop,
aceEditor.session.getScreenLength() * aceEditor.renderer.lineHeight + aceEditor.renderer.scrollMargin.bottom - aceEditor.renderer.$size.scrollerHeight
]);
// If negative, set it to zero
destScrollTop < 0 && (destScrollTop = 0);
}
2013-09-09 23:32:24 +00:00
if(Math.abs(destScrollTop - editorScrollTop) <= 9) {
// Skip the animation if diff is <= 9
lastEditorScrollTop = editorScrollTop;
return;
2013-09-09 23:32:24 +00:00
}
2013-12-05 00:25:17 +00:00
if(window.lightMode) {
$textareaElt.stop(true).animate({
scrollTop: destScrollTop
}, {
easing: 'easeOutSine',
duration: 200,
step: function(now) {
isEditorMoving = true;
lastEditorScrollTop = editorScrollTop + now;
},
done: function() {
_.defer(function() {
isEditorMoving = false;
});
},
});
}
else {
scrollingHelper.stop(true).css('value', 0).animate({
value: destScrollTop - editorScrollTop
}, {
easing: 'easeOutSine',
duration: 200,
step: function(now) {
isEditorMoving = true;
lastEditorScrollTop = editorScrollTop + now;
aceEditor.session.setScrollTop(lastEditorScrollTop);
},
done: function() {
_.defer(function() {
isEditorMoving = false;
});
},
});
}
2013-05-29 19:55:23 +00:00
}
}, 100);
2013-05-29 19:55:23 +00:00
2013-09-09 00:08:55 +00:00
scrollLink.onLayoutResize = function() {
isScrollEditor = true;
buildSections();
2013-05-29 19:55:23 +00:00
};
2013-09-09 23:32:24 +00:00
scrollLink.onFileClosed = function() {
mdSectionList = [];
};
2013-09-15 14:14:42 +00:00
var scrollAdjust = false;
2013-08-22 00:19:59 +00:00
scrollLink.onReady = function() {
$previewElt = $(".preview-container");
2013-12-05 00:25:17 +00:00
$textareaElt = $("#wmd-input");
// This helper is used to measure sections height in light mode
$textareaHelperElt = $('.textarea-helper');
2013-09-09 23:32:24 +00:00
2013-09-10 16:52:27 +00:00
$previewElt.scroll(function() {
2013-09-15 14:14:42 +00:00
if(isPreviewMoving === false && scrollAdjust === false) {
2013-09-10 16:52:27 +00:00
isScrollPreview = true;
isScrollEditor = false;
doScrollLink();
}
2013-09-15 14:14:42 +00:00
scrollAdjust = false;
2013-08-22 00:19:59 +00:00
});
2013-12-05 00:25:17 +00:00
var handleEditorScroll = function() {
2013-09-10 16:52:27 +00:00
if(isEditorMoving === false) {
isScrollEditor = true;
isScrollPreview = false;
doScrollLink();
}
2013-12-05 00:25:17 +00:00
};
if(window.lightMode) {
$textareaElt.scroll(handleEditorScroll);
}
else {
aceEditor.session.on("changeScrollTop", handleEditorScroll);
}
2013-05-29 19:55:23 +00:00
};
2013-09-10 16:52:27 +00:00
2013-11-07 23:10:38 +00:00
var $previewContentsElt;
2013-09-09 23:32:24 +00:00
scrollLink.onPagedownConfigure = function(editor) {
2013-08-22 00:19:59 +00:00
$previewContentsElt = $("#preview-contents");
2013-06-02 00:38:23 +00:00
editor.getConverter().hooks.chain("postConversion", function(text) {
2013-06-03 22:19:52 +00:00
// To avoid losing scrolling position before elements are fully
// loaded
2013-08-22 00:19:59 +00:00
$previewContentsElt.height($previewContentsElt.height());
2013-06-02 00:38:23 +00:00
return text;
2013-05-29 19:55:23 +00:00
});
};
scrollLink.onPreviewFinished = function() {
2013-06-02 00:38:23 +00:00
// Now set the correct height
2013-09-15 14:14:42 +00:00
var previousHeight = $previewContentsElt.height();
2013-08-22 00:19:59 +00:00
$previewContentsElt.height("auto");
2013-09-15 14:14:42 +00:00
var newHeight = $previewContentsElt.height();
2013-06-03 22:19:52 +00:00
isScrollEditor = true;
2013-09-15 14:14:42 +00:00
if(newHeight < previousHeight) {
// We expect a scroll adjustment
scrollAdjust = true;
}
2013-06-03 22:19:52 +00:00
buildSections();
2013-05-29 19:55:23 +00:00
};
2013-05-25 00:34:04 +00:00
2013-05-29 19:55:23 +00:00
return scrollLink;
2013-05-25 00:34:04 +00:00
});