Stackedit/public/res/extensions/scrollSync.js

273 lines
9.7 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",
2014-04-17 18:51:41 +00:00
"text!html/scrollSyncSettingsBlock.html"
], function($, _, Extension, scrollSyncSettingsBlockHTML) {
2013-05-29 19:55:23 +00:00
2014-04-17 23:41:35 +00:00
var scrollSync = new Extension("scrollSync", "Scroll Sync", true, true);
2014-04-17 18:51:41 +00:00
scrollSync.settingsBlock = scrollSyncSettingsBlockHTML;
2013-07-28 10:35:04 +00:00
2014-04-14 00:21:06 +00:00
$.easing.easeOutSine = function( p ) {
return Math.cos((1 - p) * Math.PI / 2 );
};
2013-11-07 23:10:38 +00:00
var sectionList;
2014-04-17 18:51:41 +00:00
scrollSync.onSectionsCreated = function(sectionListParam) {
2013-07-26 00:44:12 +00:00
sectionList = sectionListParam;
};
2014-03-19 00:33:57 +00:00
2014-03-17 02:01:46 +00:00
var $editorElt;
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 = [];
2014-03-17 02:01:46 +00:00
var mdSectionOffset;
var scrollHeight;
2014-03-20 00:24:56 +00:00
var editorScrollTop = $editorElt.scrollTop();
$editorElt.find(".wmd-input-section").each(function() {
if(mdSectionOffset === undefined) {
// Force start to 0 for the first section
mdSectionOffset = 0;
return;
}
var $delimiterElt = $(this.firstChild);
// Consider div scroll position
var newSectionOffset = $delimiterElt.position().top + editorScrollTop;
2014-03-17 02:01:46 +00:00
mdSectionList.push({
startOffset: mdSectionOffset,
2014-03-20 00:24:56 +00:00
endOffset: newSectionOffset,
height: newSectionOffset - mdSectionOffset
2013-12-05 00:25:17 +00:00
});
2014-03-20 00:24:56 +00:00
mdSectionOffset = newSectionOffset;
});
// Last section
scrollHeight = $editorElt.prop('scrollHeight');
mdSectionList.push({
startOffset: mdSectionOffset,
endOffset: scrollHeight,
height: scrollHeight - mdSectionOffset
});
2013-05-29 19:55:23 +00:00
2014-03-17 02:01:46 +00:00
// Find corresponding sections in the preview
2013-05-29 19:55:23 +00:00
htmlSectionList = [];
2013-11-17 22:59:03 +00:00
var htmlSectionOffset;
2013-08-22 00:19:59 +00:00
var previewScrollTop = $previewElt.scrollTop();
2014-03-17 02:01:46 +00:00
$previewElt.find(".wmd-preview-section").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
2014-03-17 02:01:46 +00:00
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;
2014-04-17 18:51:41 +00:00
doScrollSync();
2013-05-29 19:55:23 +00:00
}, 500);
var isPreviewVisible = true;
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;
function getDestScrollTop(srcScrollTop, srcSectionList, destSectionList) {
// Find the section corresponding to the offset
var sectionIndex;
var srcSection = _.find(srcSectionList, function(section, index) {
sectionIndex = index;
return srcScrollTop < section.endOffset;
});
if(srcSection === undefined) {
// Something very bad happened
return;
}
var posInSection = (srcScrollTop - srcSection.startOffset) / (srcSection.height || 1);
var destSection = destSectionList[sectionIndex];
return destSection.startOffset + destSection.height * posInSection;
}
2014-05-19 18:05:01 +00:00
var timeoutId;
var currentEndCb;
function animate(elt, startValue, endValue, stepCb, endCb) {
if(currentEndCb) {
clearTimeout(timeoutId);
currentEndCb();
}
currentEndCb = endCb;
var diff = endValue - startValue;
var startTime = Date.now();
function tick() {
var currentTime = Date.now();
var progress = (currentTime - startTime) / 200;
if(progress < 1) {
var scrollTop = startValue + diff * Math.cos((1 - progress) * Math.PI / 2 );
elt.scrollTop = scrollTop;
stepCb(scrollTop);
timeoutId = setTimeout(tick, 1);
}
else {
currentEndCb = undefined;
elt.scrollTop = endValue;
endCb();
}
}
tick();
}
2014-04-17 18:51:41 +00:00
var doScrollSync = _.throttle(function() {
2014-03-17 02:01:46 +00:00
if(!isPreviewVisible || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) {
2013-05-29 19:55:23 +00:00
return;
}
2014-03-20 00:24:56 +00:00
var editorScrollTop = $editorElt.scrollTop();
editorScrollTop < 0 && (editorScrollTop = 0);
2013-08-22 00:19:59 +00:00
var previewScrollTop = $previewElt.scrollTop();
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
}
2014-05-19 18:05:01 +00:00
animate($previewElt[0], previewScrollTop, destScrollTop, function(currentScrollTop) {
isPreviewMoving = true;
lastPreviewScrollTop = currentScrollTop;
}, 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);
2014-03-20 00:24:56 +00:00
destScrollTop = _.min([
destScrollTop,
$editorElt.prop('scrollHeight') - $editorElt.outerHeight()
]);
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
}
2014-05-19 18:05:01 +00:00
animate($editorElt[0], editorScrollTop, destScrollTop, function(currentScrollTop) {
isEditorMoving = true;
lastEditorScrollTop = currentScrollTop;
}, function() {
isEditorMoving = false;
});
2013-05-29 19:55:23 +00:00
}
}, 100);
2013-05-29 19:55:23 +00:00
2014-04-17 18:51:41 +00:00
scrollSync.onLayoutResize = function() {
2013-09-09 00:08:55 +00:00
isScrollEditor = true;
buildSections();
2013-05-29 19:55:23 +00:00
};
2014-03-19 00:33:57 +00:00
2014-04-17 18:51:41 +00:00
scrollSync.onFileClosed = function() {
2013-09-09 23:32:24 +00:00
mdSectionList = [];
};
2013-09-15 14:14:42 +00:00
var scrollAdjust = false;
2014-04-17 18:51:41 +00:00
scrollSync.onReady = function() {
2013-08-22 00:19:59 +00:00
$previewElt = $(".preview-container");
2014-03-17 02:01:46 +00:00
$editorElt = $("#wmd-input");
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;
2014-04-17 18:51:41 +00:00
doScrollSync();
2013-09-10 16:52:27 +00:00
}
2013-09-15 14:14:42 +00:00
scrollAdjust = false;
2013-08-22 00:19:59 +00:00
});
2014-04-16 23:29:51 +00:00
$editorElt.scroll(function() {
2013-09-10 16:52:27 +00:00
if(isEditorMoving === false) {
isScrollEditor = true;
isScrollPreview = false;
2014-04-17 18:51:41 +00:00
doScrollSync();
2013-09-10 16:52:27 +00:00
}
2014-04-16 23:29:51 +00:00
});
$(".preview-panel").on('hide.layout.toggle', function() {
isPreviewVisible = false;
}).on('shown.layout.toggle', function() {
isPreviewVisible = true;
});
// Reimplement anchor scrolling to work without preview
$('.extension-preview-buttons .table-of-contents').on('click', 'a', function(evt) {
evt.preventDefault();
var id = this.hash;
var $anchorElt = $previewElt.find(id);
if(!$anchorElt.length) {
return;
}
var previewScrollTop = $anchorElt.offset().top - $previewElt.offset().top + $previewElt.scrollTop();
$previewElt.scrollTop(previewScrollTop);
var editorScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList);
$editorElt.scrollTop(editorScrollTop);
});
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;
2014-04-17 18:51:41 +00:00
scrollSync.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) {
2014-04-16 23:29:51 +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
});
};
2014-04-17 18:51:41 +00:00
scrollSync.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
2014-04-17 18:51:41 +00:00
return scrollSync;
2014-03-19 00:33:57 +00:00
});