提交 348d784c 编写于 作者: J Johan Preynat

Merge pull request #19 from GitbookIO/summary/scrolling

Automatically update summary when scrolling
{
"rules": {
"no-extra-boolean-cast": [ 0 ],
"indent": [ 2, 4 ],
"quotes": [ 2, "single" ],
"linebreak-style": [ 2, "unix" ],
......
......@@ -11,7 +11,7 @@ var state = {};
themes after page is loaded and when navigation changed
*/
function hasChanged(ctx) {
console.log('page has changed', ctx);
console.log('page has changed', ctx); // eslint-disable-line no-console
setState(ctx);
if (!started) {
......@@ -26,7 +26,7 @@ function hasChanged(ctx) {
/*
Update current state
data-level="{{ page.level }}"
data-level="{{ page.level }}"
data-chapter-title="{{ page.title }}"
data-filepath="{{ file.path }}"
data-basepath="{{ './'|resolveFile }}"
......@@ -43,11 +43,11 @@ function setState(newState) {
state.book = newState.book;
// Deprecated
state.$book = $('.book');
state.revision = state.gitbook.time;
state.level = state.page.level;
state.filepath = state.file.path;
state.chapterTitle = state.page.title;
state.$book = $('.book');
state.revision = state.gitbook.time;
state.level = state.page.level;
state.filepath = state.file.path;
state.chapterTitle = state.page.title;
state.innerLanguage = state.book.language || '';
// Absolute url to the root of the book (inner book)
......@@ -70,6 +70,6 @@ function getState() {
module.exports = {
hasChanged: hasChanged,
setState: setState,
getState: getState
setState: setState,
getState: getState
};
......@@ -12,7 +12,7 @@ var usePushState = (typeof history.pushState !== 'undefined');
Get current scroller element
*/
function getScroller() {
if (platform.isMobile()) {
if (platform.isSmallScreen()) {
return $('.book-body');
} else {
return $('.body-inner');
......@@ -23,16 +23,144 @@ function getScroller() {
Scroll to a specific hash tag in the content
*/
function scrollToHash(hash) {
var $scroller = getScroller();
var dest = 0;
var $scroller = getScroller(),
dest = 0;
if (hash) {
dest = $scroller.find(hash).position().top;
dest = getElementTopPosition(hash);
}
// Unbind scroll detection
$scroller.unbind('scroll');
$scroller.animate({
scrollTop: dest
}, 800, 'swing');
}, 800, 'swing', function() {
// Reset scroll binding when finished
$scroller.scroll(handleScrolling);
});
// Directly set chapter as active
setChapterActive(null, hash);
}
/*
Return the top position of an element
*/
function getElementTopPosition(id) {
// Get actual position of element if nested
var $scroller = getScroller(),
$container = $scroller.find('.page-inner'),
$el = $scroller.find(id),
$parent = $el.offsetParent(),
dest = 0;
dest = $el.position().top;
while (!$parent.is($container)) {
$el = $parent;
dest += $el.position().top;
$parent = $el.offsetParent();
}
// Return rounded value since
// jQuery scrollTop() returns an integer
return Math.floor(dest);
}
/*
Handle updating summary at scrolling
*/
var $chapters,
$activeChapter;
// Set a chapter as active in summary and update state
function setChapterActive($chapter, hash) {
// No chapter and no hash means first chapter
if (!$chapter && !hash) {
$chapter = $chapters.first();
}
// If hash is provided, set as active chapter
if (!!hash) {
$chapter = $chapters.filter(function() {
var titleId = getChapterHash($(this));
return titleId == hash;
}).first();
}
// Don't update current chapter
if ($chapter.is($activeChapter)) {
return;
}
// Update current active chapter
$activeChapter = $chapter;
// Add class to selected chapter
$chapters.removeClass('active');
$chapter.addClass('active');
// Update history state if needed
hash = getChapterHash($chapter);
var oldUri = window.location.pathname + window.location.hash,
uri = window.location.pathname + hash;
if (uri != oldUri) {
history.replaceState({ path: uri }, null, uri);
}
}
// Return the hash of link for a chapter
function getChapterHash($chapter) {
var $link = $chapter.children('a'),
hash = $link.attr('href').split('#')[1];
if (hash) hash = '#'+hash;
return (!!hash)? hash : '';
}
// Handle user scrolling
function handleScrolling() {
// Get current page scroll
var $scroller = getScroller(),
scrollTop = $scroller.scrollTop(),
scrollHeight = $scroller.prop('scrollHeight'),
clientHeight = $scroller.prop('clientHeight'),
nbChapters = $chapters.length,
$chapter = null;
// Find each title position in reverse order
$($chapters.get().reverse()).each(function(index) {
var titleId = getChapterHash($(this)),
titleTop;
if (!!titleId && !$chapter) {
titleTop = getElementTopPosition(titleId);
// Set current chapter as active if scroller passed it
if (scrollTop >= titleTop) {
$chapter = $(this);
}
}
// If no active chapter when reaching first chapter, set it as active
if (index == (nbChapters - 1) && !$chapter) {
$chapter = $(this);
}
});
// ScrollTop is at 0, set first chapter anyway
if (!$chapter && !scrollTop) {
$chapter = $chapters.first();
}
// Set last chapter as active if scrolled to bottom of page
if (!!scrollTop && (scrollHeight - scrollTop == clientHeight)) {
$chapter = $chapters.last();
}
setChapterActive($chapter);
}
/*
......@@ -133,22 +261,20 @@ function handleNavigation(relativeUrl, push) {
$('.book').attr('class', bodyClass);
$('.book-summary').scrollTop(scrollPosition);
// Update state
gitbook.state.$book = $('.book');
preparePage(!hash);
// Scroll to hashtag position
if (hash) {
scrollToHash(hash);
}
// Update state
gitbook.state.$book = $('.book');
preparePage(!hash);
deferred.resolve();
}
});
}).promise();
return loading.show(
promise
.fail(function (e) {
......@@ -164,6 +290,12 @@ function updateNavigationPosition() {
bodyInnerWidth = parseInt($('.body-inner').css('width'), 10);
pageWrapperWidth = parseInt($('.page-wrapper').css('width'), 10);
$('.navigation-next').css('margin-right', (bodyInnerWidth - pageWrapperWidth) + 'px');
// Reset scroll to get current scroller
var $scroller = getScroller();
// Unbind existing scroll event
$scroller.unbind('scroll');
$scroller.scroll(handleScrolling);
}
function preparePage(resetScroll) {
......@@ -180,6 +312,30 @@ function preparePage(resetScroll) {
// Reset scroll
if (resetScroll !== false) $bookInner.scrollTop(0);
$bookBody.scrollTop(0);
// Get current page summary chapters
$chapters = $('.book-summary .summary .chapter')
.filter(function() {
var $link = $(this).children('a'),
href = null;
// Chapter doesn't have a link
if (!$link.length) {
return false;
}
else {
href = $link.attr('href').split('#')[0];
}
var resolvedRef = url.resolve(window.location.pathname, href);
return window.location.pathname == resolvedRef;
});
// Bind scrolling if summary contains more than one link to this page
var $scroller = getScroller();
if ($chapters.length > 1) {
$scroller.scroll(handleScrolling);
}
}
function isLeftClickEvent(e) {
......
var $ = require('jquery');
module.exports = {
isMobile: function() {
return ($(document).width() <= 600);
},
// Breakpoint for navigation links position
isSmallScreen: function() {
return ($(document).width() <= 1240);
}
};
......@@ -28,6 +28,10 @@
color: @header-button-hover-color;
background: @header-button-hover-background;
}
&:focus {
outline: none;
}
}
h1 {
......
......@@ -4,6 +4,7 @@
}
.page-inner {
position: relative;
max-width: 800px;
margin: 0px auto;
padding: 20px 15px 40px 15px;
......
......@@ -75,6 +75,10 @@
text-decoration: underline;
}
a:focus {
outline: none;
}
&.active > a {
color: @sidebar-link-hover-color;
background: @sidebar-link-hover-background;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册