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

Merge pull request #19 from GitbookIO/summary/scrolling

Automatically update summary when scrolling
{ {
"rules": { "rules": {
"no-extra-boolean-cast": [ 0 ],
"indent": [ 2, 4 ], "indent": [ 2, 4 ],
"quotes": [ 2, "single" ], "quotes": [ 2, "single" ],
"linebreak-style": [ 2, "unix" ], "linebreak-style": [ 2, "unix" ],
......
...@@ -11,7 +11,7 @@ var state = {}; ...@@ -11,7 +11,7 @@ var state = {};
themes after page is loaded and when navigation changed themes after page is loaded and when navigation changed
*/ */
function hasChanged(ctx) { function hasChanged(ctx) {
console.log('page has changed', ctx); console.log('page has changed', ctx); // eslint-disable-line no-console
setState(ctx); setState(ctx);
if (!started) { if (!started) {
...@@ -26,7 +26,7 @@ function hasChanged(ctx) { ...@@ -26,7 +26,7 @@ function hasChanged(ctx) {
/* /*
Update current state Update current state
data-level="{{ page.level }}" data-level="{{ page.level }}"
data-chapter-title="{{ page.title }}" data-chapter-title="{{ page.title }}"
data-filepath="{{ file.path }}" data-filepath="{{ file.path }}"
data-basepath="{{ './'|resolveFile }}" data-basepath="{{ './'|resolveFile }}"
...@@ -43,11 +43,11 @@ function setState(newState) { ...@@ -43,11 +43,11 @@ function setState(newState) {
state.book = newState.book; state.book = newState.book;
// Deprecated // Deprecated
state.$book = $('.book'); state.$book = $('.book');
state.revision = state.gitbook.time; state.revision = state.gitbook.time;
state.level = state.page.level; state.level = state.page.level;
state.filepath = state.file.path; state.filepath = state.file.path;
state.chapterTitle = state.page.title; state.chapterTitle = state.page.title;
state.innerLanguage = state.book.language || ''; state.innerLanguage = state.book.language || '';
// Absolute url to the root of the book (inner book) // Absolute url to the root of the book (inner book)
...@@ -70,6 +70,6 @@ function getState() { ...@@ -70,6 +70,6 @@ function getState() {
module.exports = { module.exports = {
hasChanged: hasChanged, hasChanged: hasChanged,
setState: setState, setState: setState,
getState: getState getState: getState
}; };
...@@ -12,7 +12,7 @@ var usePushState = (typeof history.pushState !== 'undefined'); ...@@ -12,7 +12,7 @@ var usePushState = (typeof history.pushState !== 'undefined');
Get current scroller element Get current scroller element
*/ */
function getScroller() { function getScroller() {
if (platform.isMobile()) { if (platform.isSmallScreen()) {
return $('.book-body'); return $('.book-body');
} else { } else {
return $('.body-inner'); return $('.body-inner');
...@@ -23,16 +23,144 @@ function getScroller() { ...@@ -23,16 +23,144 @@ function getScroller() {
Scroll to a specific hash tag in the content Scroll to a specific hash tag in the content
*/ */
function scrollToHash(hash) { function scrollToHash(hash) {
var $scroller = getScroller(); var $scroller = getScroller(),
var dest = 0; dest = 0;
if (hash) { if (hash) {
dest = $scroller.find(hash).position().top; dest = getElementTopPosition(hash);
} }
// Unbind scroll detection
$scroller.unbind('scroll');
$scroller.animate({ $scroller.animate({
scrollTop: dest 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) { ...@@ -133,22 +261,20 @@ function handleNavigation(relativeUrl, push) {
$('.book').attr('class', bodyClass); $('.book').attr('class', bodyClass);
$('.book-summary').scrollTop(scrollPosition); $('.book-summary').scrollTop(scrollPosition);
// Update state
gitbook.state.$book = $('.book');
preparePage(!hash);
// Scroll to hashtag position // Scroll to hashtag position
if (hash) { if (hash) {
scrollToHash(hash); scrollToHash(hash);
} }
// Update state
gitbook.state.$book = $('.book');
preparePage(!hash);
deferred.resolve(); deferred.resolve();
} }
}); });
}).promise(); }).promise();
return loading.show( return loading.show(
promise promise
.fail(function (e) { .fail(function (e) {
...@@ -164,6 +290,12 @@ function updateNavigationPosition() { ...@@ -164,6 +290,12 @@ function updateNavigationPosition() {
bodyInnerWidth = parseInt($('.body-inner').css('width'), 10); bodyInnerWidth = parseInt($('.body-inner').css('width'), 10);
pageWrapperWidth = parseInt($('.page-wrapper').css('width'), 10); pageWrapperWidth = parseInt($('.page-wrapper').css('width'), 10);
$('.navigation-next').css('margin-right', (bodyInnerWidth - pageWrapperWidth) + 'px'); $('.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) { function preparePage(resetScroll) {
...@@ -180,6 +312,30 @@ function preparePage(resetScroll) { ...@@ -180,6 +312,30 @@ function preparePage(resetScroll) {
// Reset scroll // Reset scroll
if (resetScroll !== false) $bookInner.scrollTop(0); if (resetScroll !== false) $bookInner.scrollTop(0);
$bookBody.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) { function isLeftClickEvent(e) {
......
var $ = require('jquery');
module.exports = { module.exports = {
isMobile: function() { isMobile: function() {
return ($(document).width() <= 600); return ($(document).width() <= 600);
},
// Breakpoint for navigation links position
isSmallScreen: function() {
return ($(document).width() <= 1240);
} }
}; };
...@@ -28,6 +28,10 @@ ...@@ -28,6 +28,10 @@
color: @header-button-hover-color; color: @header-button-hover-color;
background: @header-button-hover-background; background: @header-button-hover-background;
} }
&:focus {
outline: none;
}
} }
h1 { h1 {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
} }
.page-inner { .page-inner {
position: relative;
max-width: 800px; max-width: 800px;
margin: 0px auto; margin: 0px auto;
padding: 20px 15px 40px 15px; padding: 20px 15px 40px 15px;
......
...@@ -75,6 +75,10 @@ ...@@ -75,6 +75,10 @@
text-decoration: underline; text-decoration: underline;
} }
a:focus {
outline: none;
}
&.active > a { &.active > a {
color: @sidebar-link-hover-color; color: @sidebar-link-hover-color;
background: @sidebar-link-hover-background; background: @sidebar-link-hover-background;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册