Stackedit/js/extensions/scrollLink.js

187 lines
7.3 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",
"text!html/scrollLinkSettingsBlock.html",
2013-06-03 22:19:52 +00:00
"libs/css_browser_selector",
"libs/jquery.mousewheel"
2013-06-22 23:48:57 +00:00
], function($, _, Extension, scrollLinkSettingsBlockHTML) {
2013-05-29 19:55:23 +00:00
2013-06-22 23:48:57 +00:00
var scrollLink = new Extension("scrollLink", "Scroll Link", true);
scrollLink.settingsBlock = scrollLinkSettingsBlockHTML;
2013-07-26 00:44:12 +00:00
var sectionList = undefined;
scrollLink.onSectionsCreated = function(sectionListParam) {
sectionList = sectionListParam;
};
2013-05-29 19:55:23 +00:00
var mdSectionList = [];
var htmlSectionList = [];
function pxToFloat(px) {
return parseFloat(px.substring(0, px.length - 2));
}
2013-06-05 22:29:32 +00:00
var lastEditorScrollTop = undefined;
var lastPreviewScrollTop = undefined;
2013-05-29 19:55:23 +00:00
var buildSections = _.debounce(function() {
// Try to find Markdown sections by looking for titles
var editorElt = $("#wmd-input");
mdSectionList = [];
// This textarea is used to measure sections height
var textareaElt = $("#md-section-helper");
2013-06-05 22:29:32 +00:00
// It has to be the same width as wmd-input
2013-05-29 19:55:23 +00:00
textareaElt.width(editorElt.width());
2013-06-05 22:29:32 +00:00
// Consider wmd-input top padding (will be used for 1st and last section)
2013-05-29 19:55:23 +00:00
var padding = pxToFloat(editorElt.css('padding-top'));
2013-07-26 00:44:12 +00:00
var mdSectionOffset = 0;
2013-05-29 19:55:23 +00:00
function addMdSection(sectionText) {
var sectionHeight = padding;
if(sectionText !== undefined) {
textareaElt.val(sectionText);
sectionHeight += textareaElt.prop('scrollHeight');
}
var newSectionOffset = mdSectionOffset + sectionHeight;
mdSectionList.push({
startOffset: mdSectionOffset,
endOffset: newSectionOffset,
height: sectionHeight
});
mdSectionOffset = newSectionOffset;
padding = 0;
}
2013-07-26 00:44:12 +00:00
_.each(sectionList, function(sectionText, index) {
if(index === sectionList.length - 1) {
// Last section
// Consider wmd-input bottom padding and exclude \n\n previously added
padding += pxToFloat(editorElt.css('padding-bottom'));
2013-05-29 19:55:23 +00:00
}
2013-07-26 00:44:12 +00:00
addMdSection(sectionText);
2013-05-29 19:55:23 +00:00
});
// Try to find corresponding sections in the preview
2013-06-02 00:38:23 +00:00
var previewElt = $(".preview-container");
2013-05-29 19:55:23 +00:00
htmlSectionList = [];
var htmlSectionOffset = 0;
var previewScrollTop = previewElt.scrollTop();
// Each title element is a section separator
2013-07-26 00:44:12 +00:00
$("#preview-contents > .preview-content").children(".wmd-title").each(function() {
2013-05-29 19:55:23 +00:00
// Consider div scroll position and header element top margin
var newSectionOffset = $(this).position().top + previewScrollTop + pxToFloat($(this).css('margin-top'));
htmlSectionList.push({
startOffset: htmlSectionOffset,
endOffset: newSectionOffset,
height: newSectionOffset - htmlSectionOffset
});
htmlSectionOffset = newSectionOffset;
});
// Last section
var scrollHeight = previewElt.prop('scrollHeight');
htmlSectionList.push({
startOffset: htmlSectionOffset,
endOffset: scrollHeight,
height: scrollHeight - htmlSectionOffset
});
2013-06-05 22:42:30 +00:00
// apply Scroll Link (-10 to have a gap > 9 px)
lastEditorScrollTop = -10;
lastPreviewScrollTop = -10;
2013-05-29 19:55:23 +00:00
runScrollLink();
}, 500);
2013-06-03 22:19:52 +00:00
var isScrollEditor = false;
2013-05-29 19:55:23 +00:00
var isScrollPreview = false;
var runScrollLink = _.debounce(function() {
2013-06-02 00:38:23 +00:00
if(mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) {
2013-05-29 19:55:23 +00:00
return;
}
var editorElt = $("#wmd-input");
var editorScrollTop = editorElt.scrollTop();
2013-06-02 00:38:23 +00:00
var previewElt = $(".preview-container");
2013-05-29 19:55:23 +00:00
var previewScrollTop = previewElt.scrollTop();
2013-06-05 22:42:30 +00:00
function animate(srcScrollTop, srcSectionList, destElt, destSectionList, currentDestScrollTop, callback) {
2013-05-29 19:55:23 +00:00
// Find the section corresponding to the offset
var sectionIndex = undefined;
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;
var destSection = destSectionList[sectionIndex];
var destScrollTop = destSection.startOffset + destSection.height * posInSection;
destScrollTop = _.min([
destScrollTop,
destElt.prop('scrollHeight') - destElt.outerHeight()
]);
2013-06-05 22:42:30 +00:00
if(Math.abs(destScrollTop - currentDestScrollTop) <= 9) {
2013-05-29 19:55:23 +00:00
// Skip the animation in case it's not necessary
2013-06-05 22:42:30 +00:00
callback(currentDestScrollTop);
2013-05-29 19:55:23 +00:00
return;
}
destElt.animate({
scrollTop: destScrollTop
2013-06-03 22:19:52 +00:00
}, 500, function() {
2013-05-29 19:55:23 +00:00
callback(destScrollTop);
});
}
2013-05-29 23:04:52 +00:00
// Perform the animation if diff > 9px
2013-06-03 22:19:52 +00:00
if(isScrollEditor === true && Math.abs(editorScrollTop - lastEditorScrollTop) > 9) {
isScrollEditor = false;
2013-05-29 19:55:23 +00:00
// Animate the preview
lastEditorScrollTop = editorScrollTop;
2013-06-05 22:42:30 +00:00
animate(editorScrollTop, mdSectionList, previewElt, htmlSectionList, previewScrollTop, function(destScrollTop) {
2013-05-29 19:55:23 +00:00
lastPreviewScrollTop = destScrollTop;
});
}
2013-06-03 22:19:52 +00:00
else if(isScrollPreview === true && Math.abs(previewScrollTop - lastPreviewScrollTop) > 9) {
isScrollPreview = false;
2013-05-29 19:55:23 +00:00
// Animate the editor
lastPreviewScrollTop = previewScrollTop;
2013-06-05 22:42:30 +00:00
animate(previewScrollTop, htmlSectionList, editorElt, mdSectionList, editorScrollTop, function(destScrollTop) {
2013-05-29 19:55:23 +00:00
lastEditorScrollTop = destScrollTop;
});
}
2013-06-03 22:19:52 +00:00
}, 500);
2013-05-29 19:55:23 +00:00
scrollLink.onLayoutConfigure = function(layoutConfig) {
2013-06-03 22:19:52 +00:00
layoutConfig.onresize = function() {
isScrollEditor = true;
buildSections();
};
2013-05-29 19:55:23 +00:00
};
scrollLink.onLayoutCreated = function() {
2013-06-05 22:29:32 +00:00
$(".preview-container").bind("keyup mouseup mousewheel", function() {
2013-05-29 19:55:23 +00:00
isScrollPreview = true;
2013-06-03 22:19:52 +00:00
isScrollEditor = false;
2013-05-29 19:55:23 +00:00
runScrollLink();
});
2013-06-05 22:29:32 +00:00
$("#wmd-input").bind("keyup mouseup mousewheel", function() {
2013-06-03 22:19:52 +00:00
isScrollEditor = true;
2013-05-29 19:55:23 +00:00
isScrollPreview = false;
runScrollLink();
});
};
scrollLink.onEditorConfigure = function(editor) {
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-07-24 23:20:56 +00:00
var previewElt = $("#preview-contents");
2013-06-05 22:29:32 +00:00
previewElt.height(previewElt.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-07-24 23:20:56 +00:00
$("#preview-contents").height("auto");
2013-06-03 22:19:52 +00:00
isScrollEditor = true;
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
});