diff --git a/public/res/extensions/scrollSync.js b/public/res/extensions/scrollSync.js index 4e272a3f..cdcb254c 100644 --- a/public/res/extensions/scrollSync.js +++ b/public/res/extensions/scrollSync.js @@ -1,272 +1,270 @@ define([ - "jquery", - "underscore", - "classes/Extension", - "text!html/scrollSyncSettingsBlock.html" + "jquery", + "underscore", + "classes/Extension", + "text!html/scrollSyncSettingsBlock.html" ], function($, _, Extension, scrollSyncSettingsBlockHTML) { - var scrollSync = new Extension("scrollSync", "Scroll Sync", true, true); - scrollSync.settingsBlock = scrollSyncSettingsBlockHTML; + var scrollSync = new Extension("scrollSync", "Scroll Sync", true, true); + scrollSync.settingsBlock = scrollSyncSettingsBlockHTML; - $.easing.easeOutSine = function( p ) { - return Math.cos((1 - p) * Math.PI / 2 ); - }; + var sectionList; + scrollSync.onSectionsCreated = function(sectionListParam) { + sectionList = sectionListParam; + }; - var sectionList; - scrollSync.onSectionsCreated = function(sectionListParam) { - sectionList = sectionListParam; - }; + var editorElt; + var previewElt; + var mdSectionList = []; + var htmlSectionList = []; + var lastEditorScrollTop; + var lastPreviewScrollTop; + var buildSections = _.debounce(function() { + mdSectionList = []; + var mdSectionOffset; + var scrollHeight; + _.each(editorElt.querySelectorAll(".wmd-input-section"), function(delimiterElt) { + if(mdSectionOffset === undefined) { + // Force start to 0 for the first section + mdSectionOffset = 0; + return; + } + delimiterElt = delimiterElt.firstChild; + // Consider div scroll position + var newSectionOffset = delimiterElt.offsetTop; + mdSectionList.push({ + startOffset: mdSectionOffset, + endOffset: newSectionOffset, + height: newSectionOffset - mdSectionOffset + }); + mdSectionOffset = newSectionOffset; + }); + // Last section + scrollHeight = editorElt.scrollHeight; + mdSectionList.push({ + startOffset: mdSectionOffset, + endOffset: scrollHeight, + height: scrollHeight - mdSectionOffset + }); - var $editorElt; - var $previewElt; - var mdSectionList = []; - var htmlSectionList = []; - var lastEditorScrollTop; - var lastPreviewScrollTop; - var buildSections = _.debounce(function() { - mdSectionList = []; - var mdSectionOffset; - var scrollHeight; - 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; - mdSectionList.push({ - startOffset: mdSectionOffset, - endOffset: newSectionOffset, - height: newSectionOffset - mdSectionOffset - }); - mdSectionOffset = newSectionOffset; - }); - // Last section - scrollHeight = $editorElt.prop('scrollHeight'); - mdSectionList.push({ - startOffset: mdSectionOffset, - endOffset: scrollHeight, - height: scrollHeight - mdSectionOffset - }); + // Find corresponding sections in the preview + htmlSectionList = []; + var htmlSectionOffset; + _.each(previewElt.querySelectorAll(".wmd-preview-section"), function(delimiterElt) { + if(htmlSectionOffset === undefined) { + // Force start to 0 for the first section + htmlSectionOffset = 0; + return; + } + // Consider div scroll position + var newSectionOffset = delimiterElt.offsetTop; + htmlSectionList.push({ + startOffset: htmlSectionOffset, + endOffset: newSectionOffset, + height: newSectionOffset - htmlSectionOffset + }); + htmlSectionOffset = newSectionOffset; + }); + // Last section + scrollHeight = previewElt.scrollHeight; + htmlSectionList.push({ + startOffset: htmlSectionOffset, + endOffset: scrollHeight, + height: scrollHeight - htmlSectionOffset + }); - // Find corresponding sections in the preview - htmlSectionList = []; - var htmlSectionOffset; - var previewScrollTop = $previewElt.scrollTop(); - $previewElt.find(".wmd-preview-section").each(function() { - if(htmlSectionOffset === undefined) { - // Force start to 0 for the first section - htmlSectionOffset = 0; - return; - } - var $delimiterElt = $(this); - // Consider div scroll position - var newSectionOffset = $delimiterElt.position().top + previewScrollTop; - htmlSectionList.push({ - startOffset: htmlSectionOffset, - endOffset: newSectionOffset, - height: newSectionOffset - htmlSectionOffset - }); - htmlSectionOffset = newSectionOffset; - }); - // Last section - scrollHeight = $previewElt.prop('scrollHeight'); - htmlSectionList.push({ - startOffset: htmlSectionOffset, - endOffset: scrollHeight, - height: scrollHeight - htmlSectionOffset - }); + // apply Scroll Link (-10 to have a gap > 9px) + lastEditorScrollTop = -10; + lastPreviewScrollTop = -10; + doScrollSync(); + }, 500); - // apply Scroll Link (-10 to have a gap > 9px) - lastEditorScrollTop = -10; - lastPreviewScrollTop = -10; - doScrollSync(); - }, 500); + var isPreviewVisible = true; + var isScrollEditor = false; + var isScrollPreview = false; + var isEditorMoving = false; + var isPreviewMoving = false; - var isPreviewVisible = true; - var isScrollEditor = false; - var isScrollPreview = false; - 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; - } + 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; + } - 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(); - } + var timeoutId; + var currentEndCb; - var doScrollSync = _.throttle(function() { - if(!isPreviewVisible || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) { - return; - } - var editorScrollTop = $editorElt.scrollTop(); - editorScrollTop < 0 && (editorScrollTop = 0); - var previewScrollTop = $previewElt.scrollTop(); - var destScrollTop; - // Perform the animation if diff > 9px - if(isScrollEditor === true) { - if(Math.abs(editorScrollTop - lastEditorScrollTop) <= 9) { - return; - } - isScrollEditor = false; - // Animate the preview - lastEditorScrollTop = editorScrollTop; - destScrollTop = getDestScrollTop(editorScrollTop, mdSectionList, htmlSectionList); - destScrollTop = _.min([ - destScrollTop, - $previewElt.prop('scrollHeight') - $previewElt.outerHeight() - ]); - if(Math.abs(destScrollTop - previewScrollTop) <= 9) { - // Skip the animation if diff is <= 9 - lastPreviewScrollTop = previewScrollTop; - return; - } - animate($previewElt[0], previewScrollTop, destScrollTop, function(currentScrollTop) { - isPreviewMoving = true; - lastPreviewScrollTop = currentScrollTop; - }, function() { - isPreviewMoving = false; - }); - } - else if(isScrollPreview === true) { - if(Math.abs(previewScrollTop - lastPreviewScrollTop) <= 9) { - return; - } - isScrollPreview = false; - // Animate the editor - lastPreviewScrollTop = previewScrollTop; - destScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList); - destScrollTop = _.min([ - destScrollTop, - $editorElt.prop('scrollHeight') - $editorElt.outerHeight() - ]); - if(Math.abs(destScrollTop - editorScrollTop) <= 9) { - // Skip the animation if diff is <= 9 - lastEditorScrollTop = editorScrollTop; - return; - } - animate($editorElt[0], editorScrollTop, destScrollTop, function(currentScrollTop) { - isEditorMoving = true; - lastEditorScrollTop = currentScrollTop; - }, function() { - isEditorMoving = false; - }); - } - }, 100); + function animate(elt, startValue, endValue, stepCb, endCb) { + if(currentEndCb) { + clearTimeout(timeoutId); + currentEndCb(); + } + currentEndCb = endCb; + var diff = endValue - startValue; + var startTime = Date.now(); - scrollSync.onLayoutResize = function() { - isScrollEditor = true; - buildSections(); - }; + 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(); + } + } - scrollSync.onFileClosed = function() { - mdSectionList = []; - }; + tick(); + } - var scrollAdjust = false; - scrollSync.onReady = function() { - $previewElt = $(".preview-container"); - $editorElt = $("#wmd-input"); + var doScrollSync = _.throttle(function() { + if(!isPreviewVisible || mdSectionList.length === 0 || mdSectionList.length !== htmlSectionList.length) { + return; + } + var editorScrollTop = editorElt.scrollTop; + editorScrollTop < 0 && (editorScrollTop = 0); + var previewScrollTop = previewElt.scrollTop; + var destScrollTop; + // Perform the animation if diff > 9px + if(isScrollEditor === true) { + if(Math.abs(editorScrollTop - lastEditorScrollTop) <= 9) { + return; + } + isScrollEditor = false; + // Animate the preview + lastEditorScrollTop = editorScrollTop; + destScrollTop = getDestScrollTop(editorScrollTop, mdSectionList, htmlSectionList); + destScrollTop = _.min([ + destScrollTop, + previewElt.scrollHeight - previewElt.offsetHeight + ]); + if(Math.abs(destScrollTop - previewScrollTop) <= 9) { + // Skip the animation if diff is <= 9 + lastPreviewScrollTop = previewScrollTop; + return; + } + animate(previewElt, previewScrollTop, destScrollTop, function(currentScrollTop) { + isPreviewMoving = true; + lastPreviewScrollTop = currentScrollTop; + }, function() { + isPreviewMoving = false; + }); + } + else if(isScrollPreview === true) { + if(Math.abs(previewScrollTop - lastPreviewScrollTop) <= 9) { + return; + } + isScrollPreview = false; + // Animate the editor + lastPreviewScrollTop = previewScrollTop; + destScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList); + destScrollTop = _.min([ + destScrollTop, + editorElt.scrollHeight - editorElt.offsetHeight + ]); + if(Math.abs(destScrollTop - editorScrollTop) <= 9) { + // Skip the animation if diff is <= 9 + lastEditorScrollTop = editorScrollTop; + return; + } + animate(editorElt, editorScrollTop, destScrollTop, function(currentScrollTop) { + isEditorMoving = true; + lastEditorScrollTop = currentScrollTop; + }, function() { + isEditorMoving = false; + }); + } + }, 100); - $previewElt.scroll(function() { - if(isPreviewMoving === false && scrollAdjust === false) { - isScrollPreview = true; - isScrollEditor = false; - doScrollSync(); - } - scrollAdjust = false; - }); - $editorElt.scroll(function() { - if(isEditorMoving === false) { - isScrollEditor = true; - isScrollPreview = false; - doScrollSync(); - } - }); + scrollSync.onLayoutResize = function() { + isScrollEditor = true; + buildSections(); + }; - $(".preview-panel").on('hide.layout.toggle', function() { - isPreviewVisible = false; - }).on('shown.layout.toggle', function() { - isPreviewVisible = true; - }); + scrollSync.onFileClosed = function() { + mdSectionList = []; + }; - // 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); - }); - }; + var scrollAdjust = false; + scrollSync.onReady = function() { + previewElt = document.querySelector(".preview-container"); + editorElt = document.querySelector("#wmd-input"); - var $previewContentsElt; - scrollSync.onPagedownConfigure = function(editor) { - $previewContentsElt = $("#preview-contents"); - editor.getConverter().hooks.chain("postConversion", function(text) { - // To avoid losing scrolling position before elements are fully loaded - $previewContentsElt.height($previewContentsElt.height()); - return text; - }); - }; + $(previewElt).scroll(function() { + if(isPreviewMoving === false && scrollAdjust === false) { + isScrollPreview = true; + isScrollEditor = false; + doScrollSync(); + } + scrollAdjust = false; + }); + $(editorElt).scroll(function() { + if(isEditorMoving === false) { + isScrollEditor = true; + isScrollPreview = false; + doScrollSync(); + } + }); - scrollSync.onPreviewFinished = function() { - // Now set the correct height - var previousHeight = $previewContentsElt.height(); - $previewContentsElt.height("auto"); - var newHeight = $previewContentsElt.height(); - isScrollEditor = true; - if(newHeight < previousHeight) { - // We expect a scroll adjustment - scrollAdjust = true; - } - buildSections(); - }; + $(".preview-panel").on('hide.layout.toggle', function() { + isPreviewVisible = false; + }).on('shown.layout.toggle', function() { + isPreviewVisible = true; + }); - return scrollSync; + // 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.querySelector(id); + if(!anchorElt) { + return; + } + var previewScrollTop = anchorElt.getBoundingClientRect().top - previewElt.getBoundingClientRect().top + previewElt.scrollTop; + previewElt.scrollTop = previewScrollTop; + var editorScrollTop = getDestScrollTop(previewScrollTop, htmlSectionList, mdSectionList); + editorElt.scrollTop = editorScrollTop; + }); + }; + + var previewContentsElt; + var previousHeight; + scrollSync.onPagedownConfigure = function(editor) { + previewContentsElt = document.getElementById("preview-contents"); + editor.getConverter().hooks.chain("postConversion", function(text) { + // To avoid losing scrolling position before elements are fully loaded + previousHeight = previewContentsElt.offsetHeight; + previewContentsElt.style.height = previousHeight + 'px'; + return text; + }); + }; + + scrollSync.onPreviewFinished = function() { + // Now set the correct height + previewContentsElt.style.removeProperty('height'); + var newHeight = previewContentsElt.offsetHeight; + isScrollEditor = true; + if(newHeight < previousHeight) { + // We expect a scroll adjustment + scrollAdjust = true; + } + buildSections(); + }; + + return scrollSync; });