summaryrefslogtreecommitdiffstats
path: root/theme/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'theme/javascript')
-rw-r--r--theme/javascript/core/events.js7
-rw-r--r--theme/javascript/core/font-settings.js103
-rwxr-xr-xtheme/javascript/core/keyboard.js38
-rw-r--r--theme/javascript/core/loading.js16
-rwxr-xr-xtheme/javascript/core/navigation.js174
-rwxr-xr-xtheme/javascript/core/progress.js73
-rwxr-xr-xtheme/javascript/core/search.js114
-rwxr-xr-xtheme/javascript/core/sidebar.js56
-rwxr-xr-xtheme/javascript/core/state.js18
-rw-r--r--theme/javascript/dropdown.js25
-rw-r--r--theme/javascript/events.js4
-rwxr-xr-xtheme/javascript/gitbook.js49
-rwxr-xr-xtheme/javascript/index.js81
-rw-r--r--theme/javascript/keyboard.js36
-rw-r--r--theme/javascript/loading.js14
-rw-r--r--theme/javascript/navigation.js160
-rw-r--r--theme/javascript/platform.js3
-rw-r--r--theme/javascript/sidebar.js51
-rw-r--r--theme/javascript/state.js16
-rw-r--r--theme/javascript/storage.js37
-rw-r--r--theme/javascript/toolbar.js157
-rw-r--r--theme/javascript/utils/dropdown.js27
-rwxr-xr-xtheme/javascript/utils/platform.js5
-rwxr-xr-xtheme/javascript/utils/sharing.js39
-rwxr-xr-xtheme/javascript/utils/storage.js31
-rw-r--r--theme/javascript/utils/url.js33
26 files changed, 584 insertions, 783 deletions
diff --git a/theme/javascript/core/events.js b/theme/javascript/core/events.js
deleted file mode 100644
index 855755f..0000000
--- a/theme/javascript/core/events.js
+++ /dev/null
@@ -1,7 +0,0 @@
-define([
- "jQuery"
-], function($) {
- var events = $({});
-
- return events;
-}); \ No newline at end of file
diff --git a/theme/javascript/core/font-settings.js b/theme/javascript/core/font-settings.js
deleted file mode 100644
index 63177ee..0000000
--- a/theme/javascript/core/font-settings.js
+++ /dev/null
@@ -1,103 +0,0 @@
-define([
- "jQuery",
- "utils/storage"
-], function($, storage) {
- var fontState;
-
- var THEMES = {
- "white": 0,
- "sepia": 1,
- "night": 2
- };
-
- var FAMILY = {
- "serif": 0,
- "sans": 1
- };
-
- var enlargeFontSize = function(e){
- if (fontState.size < 4){
- fontState.size++;
- fontState.save();
- }
- };
-
- var reduceFontSize = function(e){
- if (fontState.size > 0){
- fontState.size--;
- fontState.save();
- }
- };
-
- var changeFontFamily = function(){
- var index = $(this).data("font");
-
- fontState.family = index;
- fontState.save();
- };
-
- var changeColorTheme = function(){
- var $book = $(".book");
- var index = $(this).data("theme");
-
- if (fontState.theme !== 0)
- $book.removeClass("color-theme-"+fontState.theme);
-
- fontState.theme = index;
- if (fontState.theme !== 0)
- $book.addClass("color-theme-"+fontState.theme);
-
- fontState.save();
- };
-
- var update = function() {
- var $book = $(".book");
-
- $(".font-settings .font-family-list li").removeClass("active");
- $(".font-settings .font-family-list li:nth-child("+(fontState.family+1)+")").addClass("active");
-
- $book[0].className = $book[0].className.replace(/\bfont-\S+/g, '');
- $book.addClass("font-size-"+fontState.size);
- $book.addClass("font-family-"+fontState.family);
-
- if(fontState.theme !== 0) {
- $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, '');
- $book.addClass("color-theme-"+fontState.theme);
- }
- };
-
- var init = function(config) {
- var $toggle, $bookBody, $dropdown, $book;
-
- //Find DOM elements.
- $book = $(".book");
- $toggle = $(".book-header .toggle-font-settings");
- $dropdown = $("#font-settings-wrapper .dropdown-menu");
- $bookBody = $(".book-body");
-
- // Instantiate font state object
- fontState = storage.get("fontState", {
- size: config.size || 2,
- family: FAMILY[config.family || "sans"],
- theme: THEMES[config.theme || "white"]
- });
- fontState.save = function(){
- storage.set("fontState",fontState);
- update();
- };
-
- update();
-
- //Add event listeners
- $(document).on('click', "#enlarge-font-size", enlargeFontSize);
- $(document).on('click', "#reduce-font-size", reduceFontSize);
-
- $(document).on('click', "#font-settings-wrapper .font-family-list .button", changeFontFamily);
- $(document).on('click', "#font-settings-wrapper .color-theme-list .button", changeColorTheme);
- };
-
- return {
- init: init,
- update: update
- }
-}); \ No newline at end of file
diff --git a/theme/javascript/core/keyboard.js b/theme/javascript/core/keyboard.js
deleted file mode 100755
index 22fe953..0000000
--- a/theme/javascript/core/keyboard.js
+++ /dev/null
@@ -1,38 +0,0 @@
-define([
- "jQuery",
- "Mousetrap",
- "core/navigation",
- "core/sidebar",
- "core/search"
-], function($, Mousetrap, navigation, sidebar, search){
- // Bind keyboard shortcuts
- var init = function() {
- // Next
- Mousetrap.bind(['right'], function(e) {
- navigation.goNext();
- return false;
- });
-
- // Prev
- Mousetrap.bind(['left'], function(e) {
- navigation.goPrev();
- return false;
- });
-
- // Toggle Summary
- Mousetrap.bind(['s'], function(e) {
- sidebar.toggle();
- return false;
- });
-
- // Toggle Search
- Mousetrap.bind(['f'], function(e) {
- search.toggle();
- return false;
- });
- };
-
- return {
- init: init
- };
-}); \ No newline at end of file
diff --git a/theme/javascript/core/loading.js b/theme/javascript/core/loading.js
deleted file mode 100644
index 1ddb213..0000000
--- a/theme/javascript/core/loading.js
+++ /dev/null
@@ -1,16 +0,0 @@
-define([
- "jQuery"
-], function($) {
- var showLoading = function(p) {
- $(".book").addClass("is-loading");
- p.always(function() {
- $(".book").removeClass("is-loading");
- });
-
- return p;
- };
-
- return {
- show: showLoading
- };
-}); \ No newline at end of file
diff --git a/theme/javascript/core/navigation.js b/theme/javascript/core/navigation.js
deleted file mode 100755
index c1766ab..0000000
--- a/theme/javascript/core/navigation.js
+++ /dev/null
@@ -1,174 +0,0 @@
-define([
- "jQuery",
- "utils/url",
- "core/events",
- "core/state",
- "core/progress",
- "core/loading",
- "core/search"
-], function($, URL, events, state, progress, loading, search) {
- var prev, next;
-
- var usePushState = (typeof history.pushState !== "undefined");
-
- var handleNavigation = function(relativeUrl, push) {
- var url = URL.join(window.location.pathname, relativeUrl);
- console.log("navigate to ", url, "baseurl="+relativeUrl, "current="+window.location.pathname);
-
- if (!usePushState) {
- // Refresh the page to the new URL if pushState not supported
- location.href = relativeUrl;
- return
- }
-
- return loading.show($.get(url)
- .done(function (html) {
- // Push url to history
- if (push) history.pushState({ path: url }, null, url);
-
- // Replace html content
- html = html.replace( /<(\/?)(html|head|body)([^>]*)>/ig, function(a,b,c,d){
- return '<' + b + 'div' + ( b ? '' : ' data-element="' + c + '"' ) + d + '>';
- });
-
- var $page = $(html);
- var $pageHead = $page.find("[data-element=head]");
- var $pageBody = $page.find('.book');
-
- ////
- // Merge heads
- // !! Warning !!: we only update necessary portions to avoid strange behavior (page flickering etc ...)
- ////
-
- // Update title
- document.title = $pageHead.find("title").text();
-
- // Reference to $("head");
- var $head = $("head");
-
- // Update next & prev <link> tags
- // Remove old
- $head.find("link[rel=prev]").remove();
- $head.find("link[rel=next]").remove();
-
- // Add new next * prev <link> tags
- $head.append($pageHead.find("link[rel=prev]"));
- $head.append($pageHead.find("link[rel=next]"));
-
- // Merge body
- var bodyClass = $(".book").attr("class");
- var scrollPosition = $('.book-summary .summary').scrollTop();
- $pageBody.toggleClass("with-summary", $(".book").hasClass("with-summary"))
-
- $(".book").replaceWith($pageBody);
- $(".book").attr("class", bodyClass);
- $('.book-summary .summary').scrollTop(scrollPosition);
-
- // Update state
- state.update($("html"));
- // recover search keyword
- search.recover();
- preparePage();
- })
- .fail(function (e) {
- location.href = relativeUrl;
- }));
- };
-
- var updateNavigationPosition = function() {
- var bodyInnerWidth, pageWrapperWidth;
-
- bodyInnerWidth = parseInt($('.body-inner').css('width'), 10);
- pageWrapperWidth = parseInt($('.page-wrapper').css('width'), 10);
- $('.navigation-next').css('margin-right', (bodyInnerWidth - pageWrapperWidth) + 'px');
- };
-
- var preparePage = function() {
- var $bookBody = $(".book-body");
- var $bookInner = $bookBody.find(".body-inner");
- var $pageWrapper = $bookInner.find(".page-wrapper");
-
- // Show progress
- progress.show();
-
- // Update navigation position
- updateNavigationPosition();
-
- // Focus on content
- $pageWrapper.focus();
-
- // Reset scroll
- $bookInner.scrollTop(0);
- $bookBody.scrollTop(0);
-
- // Notify
- events.trigger("page.change");
- };
-
- var isLeftClickEvent = function (e) {
- return e.button === 0;
- };
-
- var isModifiedEvent = function (e) {
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
- };
-
- var handlePagination = function (e) {
- if (isModifiedEvent(e) || !isLeftClickEvent(e)) {
- return;
- }
-
- e.stopPropagation();
- e.preventDefault();
-
- var url = $(this).attr('href');
- if (url) handleNavigation(url, true);
- };
-
- var goNext = function() {
- var url = $(".navigation-next").attr("href");
- if (url) handleNavigation(url, true);
- };
-
- var goPrev = function() {
- var url = $(".navigation-prev").attr("href");
- if (url) handleNavigation(url, true);
- };
-
-
-
- var init = function() {
- // Prevent cache so that using the back button works
- // See: http://stackoverflow.com/a/15805399/983070
- $.ajaxSetup({
- cache: false
- });
-
- // Recreate first page when the page loads.
- history.replaceState({ path: window.location.href }, '');
-
- // Back Button Hijacking :(
- window.onpopstate = function (event) {
- if (event.state === null) {
- return;
- }
- return handleNavigation(event.state.path, false);
- };
-
- $(document).on('click', ".navigation-prev", handlePagination);
- $(document).on('click', ".navigation-next", handlePagination);
- $(document).on('click', ".summary [data-path] a", handlePagination);
-
- $(window).resize(updateNavigationPosition);
-
- // Prepare current page
- preparePage();
- };
-
- return {
- init: init,
- goNext: goNext,
- goPrev: goPrev
- };
-});
-
diff --git a/theme/javascript/core/progress.js b/theme/javascript/core/progress.js
deleted file mode 100755
index a409a11..0000000
--- a/theme/javascript/core/progress.js
+++ /dev/null
@@ -1,73 +0,0 @@
-define([
- "lodash",
- "jQuery",
- "utils/storage",
- "core/state"
-], function(_, $, storage, state) {
- // Get current level
- var getCurrentLevel = function() {
- return state.level;
- };
-
- // Return all levels
- var getLevels = function () {
- var levels = $(".book-summary li[data-level]");
-
- return _.map(levels, function(level) {
- return $(level).data("level").toString();
- });
- };
-
- // Return a map chapter -> number (timestamp)
- var getProgress = function () {
- // Current level
- var progress = storage.get("progress", {});
-
- // Levels
- var levels = getLevels();
-
- _.each(levels, function(level) {
- progress[level] = progress[level] || 0;
- });
-
- return progress;
- };
-
- // Change value of progress for a level
- var markProgress = function (level, state) {
- var progress = getProgress();
-
- if (state == null) {
- state = true;
- }
-
- progress[level] = state
- ? Date.now()
- : 0;
-
- storage.set("progress", progress);
- };
-
- // Show progress
- var showProgress = function () {
- var progress = getProgress();
- var $summary = $(".book-summary");
-
- _.each(progress, function (value, level) {
- $summary.find("li[data-level='"+level+"']").toggleClass("done", value > 0);
- });
-
- // Mark current progress if we have not already
- if (!progress[getCurrentLevel()]) {
- markProgress(getCurrentLevel(), true);
- }
- };
-
- return {
- 'current': getCurrentLevel,
- 'levels': getLevels,
- 'get': getProgress,
- 'mark': markProgress,
- 'show': showProgress
- };
-}); \ No newline at end of file
diff --git a/theme/javascript/core/search.js b/theme/javascript/core/search.js
deleted file mode 100755
index 4cbc3ed..0000000
--- a/theme/javascript/core/search.js
+++ /dev/null
@@ -1,114 +0,0 @@
-define([
- "jQuery",
- "lodash",
- "lunr",
- "utils/storage",
- "core/state",
- "core/sidebar"
-], function($, _, lunr, storage, state, sidebar) {
- var index = null;
-
- // Use a specific idnex
- var useIndex = function(data) {
- index = lunr.Index.load(data);
- };
-
- // Load complete index
- var loadIndex = function() {
- $.getJSON(state.basePath+"/search_index.json")
- .then(useIndex);
- };
-
- // Search for a term
- var search = function(q) {
- if (!index) return;
- var results = _.chain(index.search(q))
- .map(function(result) {
- var parts = result.ref.split("#")
- return {
- path: parts[0],
- hash: parts[1]
- }
- })
- .value();
-
- return results;
- };
-
- // Toggle search bar
- var toggleSearch = function(_state) {
- if (state != null && isSearchOpen() == _state) return;
-
- var $searchInput = $(".book-search input");
- state.$book.toggleClass("with-search", _state);
-
- // If search bar is open: focus input
- if (isSearchOpen()) {
- sidebar.toggle(true);
- $searchInput.focus();
- } else {
- $searchInput.blur();
- $searchInput.val("");
- sidebar.filter(null);
- }
- };
-
- // Return true if search bar is open
- var isSearchOpen = function() {
- return state.$book.hasClass("with-search");
- };
-
-
- var init = function() {
- loadIndex();
-
- // Toggle search
- $(document).on("click", ".book-header .toggle-search", function(e) {
- e.preventDefault();
- toggleSearch();
- });
-
-
- // Type in search bar
- $(document).on("keyup", ".book-search input", function(e) {
- var key = (e.keyCode ? e.keyCode : e.which);
- var q = $(this).val();
-
- if (key == 27) {
- e.preventDefault();
- toggleSearch(false);
- return;
- }
- if (q.length == 0) {
- sidebar.filter(null);
- storage.remove("keyword");
- } else {
- var results = search(q);
- sidebar.filter(
- _.pluck(results, "path")
- );
- storage.set("keyword", q);
- }
- })
-
- };
-
- // filter sidebar menu with current search keyword
- var recoverSearch = function() {
- var keyword = storage.get("keyword", "");
- if(keyword.length > 0) {
- if(!isSearchOpen()){
- toggleSearch();
- }
- sidebar.filter(_.pluck(search(keyword), "path"));
- }
- $(".book-search input").val(keyword);
- };
-
- return {
- init: init,
- search: search,
- toggle: toggleSearch,
- recover:recoverSearch
- };
-});
diff --git a/theme/javascript/core/sidebar.js b/theme/javascript/core/sidebar.js
deleted file mode 100755
index 828dc73..0000000
--- a/theme/javascript/core/sidebar.js
+++ /dev/null
@@ -1,56 +0,0 @@
-define([
- "jQuery",
- "lodash",
- "utils/storage",
- "utils/platform",
- "core/state"
-], function($, _, storage, platform, state) {
- // Toggle sidebar with or withour animation
- var toggleSidebar = function(_state, animation) {
- if (state != null && isOpen() == _state) return;
- if (animation == null) animation = true;
-
- state.$book.toggleClass("without-animation", !animation);
- state.$book.toggleClass("with-summary", _state);
-
- storage.set("sidebar", isOpen());
- };
-
- // Return true if sidebar is open
- var isOpen = function() {
- return state.$book.hasClass("with-summary");
- };
-
- // Prepare sidebar: state and toggle button
- var init = function() {
- // Toggle summary
- $(document).on("click", ".book-header .toggle-summary", function(e) {
- e.preventDefault();
- toggleSidebar();
- });
-
- // Init last state if not mobile
- if (!platform.isMobile) {
- toggleSidebar(storage.get("sidebar", true), false);
- }
- };
-
- // Filter summary with a list of path
- var filterSummary = function(paths) {
- var $summary = $(".book-summary");
-
- $summary.find("li").each(function() {
- var path = $(this).data("path");
- var st = paths == null || _.contains(paths, path);
-
- $(this).toggle(st);
- if (st) $(this).parents("li").show();
- });
- };
-
- return {
- init: init,
- toggle: toggleSidebar,
- filter: filterSummary
- }
-}); \ No newline at end of file
diff --git a/theme/javascript/core/state.js b/theme/javascript/core/state.js
deleted file mode 100755
index de5c65c..0000000
--- a/theme/javascript/core/state.js
+++ /dev/null
@@ -1,18 +0,0 @@
-define([
- "jQuery"
-], function() {
- var state = {};
-
- state.update = function(dom) {
- var $book = $(dom.find(".book"));
-
- state.$book = $book;
- state.level = $book.data("level");
- state.basePath = $book.data("basepath");
- state.revision = $book.data("revision");
- };
-
- state.update($);
-
- return state;
-}); \ No newline at end of file
diff --git a/theme/javascript/dropdown.js b/theme/javascript/dropdown.js
new file mode 100644
index 0000000..d6fc548
--- /dev/null
+++ b/theme/javascript/dropdown.js
@@ -0,0 +1,25 @@
+var $ = require('jquery');
+
+function toggleDropdown(e) {
+ var $dropdown = $(e.currentTarget).parent().find('.dropdown-menu');
+
+ $dropdown.toggleClass('open');
+ e.stopPropagation();
+ e.preventDefault();
+}
+
+function closeDropdown(e) {
+ $('.dropdown-menu').removeClass('open');
+}
+
+// Bind all dropdown
+function init() {
+ $(document).on('click', '.toggle-dropdown', toggleDropdown);
+ $(document).on('click', '.dropdown-menu', function(e){ e.stopPropagation(); });
+ $(document).on('click', closeDropdown);
+}
+
+module.exports = {
+ init: init
+};
+
diff --git a/theme/javascript/events.js b/theme/javascript/events.js
new file mode 100644
index 0000000..9b9a730
--- /dev/null
+++ b/theme/javascript/events.js
@@ -0,0 +1,4 @@
+var $ = require('jquery');
+
+module.exports = $({});
+
diff --git a/theme/javascript/gitbook.js b/theme/javascript/gitbook.js
deleted file mode 100755
index 495afac..0000000
--- a/theme/javascript/gitbook.js
+++ /dev/null
@@ -1,49 +0,0 @@
-define([
- "jQuery",
- "utils/storage",
- "utils/sharing",
- "utils/dropdown",
-
- "core/events",
- "core/font-settings",
- "core/state",
- "core/keyboard",
- "core/navigation",
- "core/progress",
- "core/sidebar",
- "core/search"
-], function($, storage, sharing, dropdown, events, fontSettings, state, keyboard, navigation, progress, sidebar, search){
- var start = function(config) {
- var $book;
- $book = state.$book;
-
- // Init sidebar
- sidebar.init();
-
- // Load search
- search.init();
-
- // Init keyboard
- keyboard.init();
-
- // Bind sharing button
- sharing.init();
-
- // Bind dropdown
- dropdown.init();
-
- // Init navigation
- navigation.init();
-
- //Init font settings
- fontSettings.init(config.fontSettings || {});
-
- events.trigger("start", config);
- }
-
- return {
- start: start,
- events: events,
- state: state
- };
-});
diff --git a/theme/javascript/index.js b/theme/javascript/index.js
new file mode 100755
index 0000000..6e601c6
--- /dev/null
+++ b/theme/javascript/index.js
@@ -0,0 +1,81 @@
+var $ = require('jquery');
+var _ = require('lodash');
+
+var storage = require('./storage');
+var dropdown = require('./dropdown');
+var events = require('./events');
+var state = require('./state');
+var keyboard = require('./keyboard');
+var navigation = require('./navigation');
+var sidebar = require('./sidebar');
+var toolbar = require('./toolbar');
+
+
+function start(config) {
+ // Init sidebar
+ sidebar.init();
+
+ // Init keyboard
+ keyboard.init();
+
+ // Bind dropdown
+ dropdown.init();
+
+ // Init navigation
+ navigation.init();
+
+
+ // Add action to toggle sidebar
+ toolbar.createButton({
+ icon: 'fa fa-align-justify',
+ onClick: function(e) {
+ e.preventDefault();
+ sidebar.toggle();
+ }
+ });
+
+ events.trigger('start', config);
+}
+
+// Export APIs for plugins
+var gitbook = {
+ start: start,
+ events: events,
+ state: state,
+
+ // UI sections
+ toolbar: toolbar,
+ sidebar: sidebar,
+
+ // Read/Write the localstorage
+ storage: storage,
+
+ // Create keyboard shortcuts
+ keyboard: keyboard
+};
+
+
+// Modules mapping for plugins
+var MODULES = {
+ 'gitbook': gitbook,
+ 'jQuery': $,
+ 'lodash': _
+};
+
+window.gitbook = gitbook;
+window.$ = $;
+window.jQuery = $;
+window.require = function(mods, fn) {
+ mods = _.map(mods, function(mod) {
+ if (!MODULES[mod]) {
+ throw new Error('GitBook module '+mod+' doesn\'t exist');
+ }
+
+ return MODULES[mod];
+ });
+
+ fn.apply(null, mods);
+};
+
+module.exports = {};
+
diff --git a/theme/javascript/keyboard.js b/theme/javascript/keyboard.js
new file mode 100644
index 0000000..ab0cef6
--- /dev/null
+++ b/theme/javascript/keyboard.js
@@ -0,0 +1,36 @@
+var Mousetrap = require('mousetrap');
+
+var navigation = require('./navigation');
+var sidebar = require('./sidebar');
+
+// Bind a keyboard shortcuts
+function bindShortcut(keys, fn) {
+ Mousetrap.bind(keys, function(e) {
+ fn();
+ return false;
+ });
+}
+
+
+// Bind keyboard shortcuts
+function init() {
+ // Next
+ bindShortcut(['right'], function(e) {
+ navigation.goNext();
+ });
+
+ // Prev
+ bindShortcut(['left'], function(e) {
+ navigation.goPrev();
+ });
+
+ // Toggle Summary
+ bindShortcut(['s'], function(e) {
+ sidebar.toggle();
+ });
+}
+
+module.exports = {
+ init: init,
+ bind: bindShortcut
+};
diff --git a/theme/javascript/loading.js b/theme/javascript/loading.js
new file mode 100644
index 0000000..797b487
--- /dev/null
+++ b/theme/javascript/loading.js
@@ -0,0 +1,14 @@
+var state = require('./state');
+
+function showLoading(p) {
+ state.$book.addClass('is-loading');
+ p.always(function() {
+ state.$book.removeClass('is-loading');
+ });
+
+ return p;
+}
+
+module.exports = {
+ show: showLoading
+};
diff --git a/theme/javascript/navigation.js b/theme/javascript/navigation.js
new file mode 100644
index 0000000..8cc420e
--- /dev/null
+++ b/theme/javascript/navigation.js
@@ -0,0 +1,160 @@
+var $ = require('jquery');
+var url = require('url');
+
+var events = require('./events');
+var state = require('./state');
+var loading = require('./loading');
+
+
+var usePushState = (typeof history.pushState !== 'undefined');
+
+function handleNavigation(relativeUrl, push) {
+ var uri = url.resolve(window.location.pathname, relativeUrl);
+
+ if (!usePushState) {
+ // Refresh the page to the new URL if pushState not supported
+ location.href = relativeUrl;
+ return;
+ }
+
+ return loading.show($.get(uri)
+ .done(function (html) {
+ // Push url to history
+ if (push) history.pushState({ path: uri }, null, uri);
+
+ // Replace html content
+ html = html.replace( /<(\/?)(html|head|body)([^>]*)>/ig, function(a,b,c,d){
+ return '<' + b + 'div' + ( b ? '' : ' data-element="' + c + '"' ) + d + '>';
+ });
+
+ var $page = $(html);
+ var $pageHead = $page.find('[data-element=head]');
+ var $pageBody = $page.find('.book');
+
+ // Merge heads
+ // !! Warning !!: we only update necessary portions to avoid strange behavior (page flickering etc ...)
+
+ // Update title
+ document.title = $pageHead.find('title').text();
+
+ // Reference to $('head');
+ var $head = $('head');
+
+ // Update next & prev <link> tags
+ // Remove old
+ $head.find('link[rel=prev]').remove();
+ $head.find('link[rel=next]').remove();
+
+ // Add new next * prev <link> tags
+ $head.append($pageHead.find('link[rel=prev]'));
+ $head.append($pageHead.find('link[rel=next]'));
+
+ // Merge body
+ var bodyClass = $('.book').attr('class');
+ var scrollPosition = $('.book-summary .summary').scrollTop();
+ $pageBody.toggleClass('with-summary', $('.book').hasClass('with-summary'));
+
+ $('.book').replaceWith($pageBody);
+ $('.book').attr('class', bodyClass);
+ $('.book-summary .summary').scrollTop(scrollPosition);
+
+ // Update state
+ state.update($('html'));
+ preparePage();
+ })
+ .fail(function (e) {
+ location.href = relativeUrl;
+ }));
+}
+
+function updateNavigationPosition() {
+ var bodyInnerWidth, pageWrapperWidth;
+
+ bodyInnerWidth = parseInt($('.body-inner').css('width'), 10);
+ pageWrapperWidth = parseInt($('.page-wrapper').css('width'), 10);
+ $('.navigation-next').css('margin-right', (bodyInnerWidth - pageWrapperWidth) + 'px');
+}
+
+function preparePage() {
+ var $bookBody = $('.book-body');
+ var $bookInner = $bookBody.find('.body-inner');
+ var $pageWrapper = $bookInner.find('.page-wrapper');
+
+ // Update navigation position
+ updateNavigationPosition();
+
+ // Focus on content
+ $pageWrapper.focus();
+
+ // Reset scroll
+ $bookInner.scrollTop(0);
+ $bookBody.scrollTop(0);
+
+ // Notify
+ events.trigger('page.change');
+}
+
+function isLeftClickEvent(e) {
+ return e.button === 0;
+}
+
+function isModifiedEvent(e) {
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
+}
+
+function handlePagination(e) {
+ if (isModifiedEvent(e) || !isLeftClickEvent(e)) {
+ return;
+ }
+
+ e.stopPropagation();
+ e.preventDefault();
+
+ var url = $(this).attr('href');
+ if (url) handleNavigation(url, true);
+}
+
+function goNext() {
+ var url = $('.navigation-next').attr('href');
+ if (url) handleNavigation(url, true);
+}
+
+function goPrev() {
+ var url = $('.navigation-prev').attr('href');
+ if (url) handleNavigation(url, true);
+}
+
+
+function init() {
+ // Prevent cache so that using the back button works
+ // See: http://stackoverflow.com/a/15805399/983070
+ $.ajaxSetup({
+ cache: false
+ });
+
+ // Recreate first page when the page loads.
+ history.replaceState({ path: window.location.href }, '');
+
+ // Back Button Hijacking :(
+ window.onpopstate = function (event) {
+ if (event.state === null) {
+ return;
+ }
+ return handleNavigation(event.state.path, false);
+ };
+
+ $(document).on('click', '.navigation-prev', handlePagination);
+ $(document).on('click', '.navigation-next', handlePagination);
+ $(document).on('click', '.summary [data-path] a', handlePagination);
+
+ $(window).resize(updateNavigationPosition);
+
+ // Prepare current page
+ preparePage();
+}
+
+module.exports = {
+ init: init,
+ goNext: goNext,
+ goPrev: goPrev
+};
diff --git a/theme/javascript/platform.js b/theme/javascript/platform.js
new file mode 100644
index 0000000..9721fb7
--- /dev/null
+++ b/theme/javascript/platform.js
@@ -0,0 +1,3 @@
+module.exports = {
+ isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
+};
diff --git a/theme/javascript/sidebar.js b/theme/javascript/sidebar.js
new file mode 100644
index 0000000..d381822
--- /dev/null
+++ b/theme/javascript/sidebar.js
@@ -0,0 +1,51 @@
+var $ = require('jquery');
+var _ = require('lodash');
+
+var storage = require('./storage');
+var platform = require('./platform');
+var state = require('./state');
+
+
+// Toggle sidebar with or withour animation
+function toggleSidebar(_state, animation) {
+ if (state != null && isOpen() == _state) return;
+ if (animation == null) animation = true;
+
+ state.$book.toggleClass('without-animation', !animation);
+ state.$book.toggleClass('with-summary', _state);
+
+ storage.set('sidebar', isOpen());
+}
+
+// Return true if sidebar is open
+function isOpen() {
+ return state.$book.hasClass('with-summary');
+}
+
+// Prepare sidebar: state and toggle button
+function init() {
+ // Init last state if not mobile
+ if (!platform.isMobile) {
+ toggleSidebar(storage.get('sidebar', true), false);
+ }
+}
+
+// Filter summary with a list of path
+function filterSummary(paths) {
+ var $summary = $('.book-summary');
+
+ $summary.find('li').each(function() {
+ var path = $(this).data('path');
+ var st = paths == null || _.contains(paths, path);
+
+ $(this).toggle(st);
+ if (st) $(this).parents('li').show();
+ });
+}
+
+module.exports = {
+ init: init,
+ isOpen: isOpen,
+ toggle: toggleSidebar,
+ filter: filterSummary
+};
diff --git a/theme/javascript/state.js b/theme/javascript/state.js
new file mode 100644
index 0000000..52b9ab3
--- /dev/null
+++ b/theme/javascript/state.js
@@ -0,0 +1,16 @@
+var $ = require('jquery');
+
+var state = {};
+
+state.update = function(dom) {
+ var $book = $(dom.find(".book"));
+
+ state.$book = $book;
+ state.level = $book.data("level");
+ state.basePath = $book.data("basepath");
+ state.revision = $book.data("revision");
+};
+
+state.update($);
+
+module.exports = state;
diff --git a/theme/javascript/storage.js b/theme/javascript/storage.js
new file mode 100644
index 0000000..7a7643c
--- /dev/null
+++ b/theme/javascript/storage.js
@@ -0,0 +1,37 @@
+var baseKey = '';
+
+/*
+ * Simple module for storing data in the browser's local storage
+ */
+module.exports = {
+ setBaseKey: function(key) {
+ baseKey = key;
+ },
+
+ // Write something in localstorage
+ set: function(key, value) {
+ key = baseKey+':'+key;
+
+ try {
+ localStorage[key] = JSON.stringify(value);
+ } catch(e) {}
+ },
+
+ // Read a value from localstorage
+ get: function(key, def) {
+ key = baseKey+':'+key;
+ if (localStorage[key] === undefined) return def;
+ try {
+ var v = JSON.parse(localStorage[key]);
+ return v == null ? def : v;;
+ } catch(err) {
+ return localStorage[key] || def;
+ }
+ },
+
+ // Remove a key from localstorage
+ remove: function(key) {
+ key = baseKey+':'+key;
+ localStorage.removeItem(key);
+ }
+};
diff --git a/theme/javascript/toolbar.js b/theme/javascript/toolbar.js
new file mode 100644
index 0000000..b987c88
--- /dev/null
+++ b/theme/javascript/toolbar.js
@@ -0,0 +1,157 @@
+var $ = require('jquery');
+var _ = require('lodash');
+
+var events = require('./events');
+
+// List of created buttons
+var buttons = [];
+
+
+// Default click handler
+function defaultOnClick(e) {
+ e.preventDefault();
+}
+
+// Create a dropdown menu
+function createDropdownMenu(dropdown) {
+ var $menu = $('<div>', {
+ 'class': 'dropdown-menu',
+ 'html': '<div class="dropdown-caret"><span class="caret-outer"></span><span class="caret-inner"></span></div>'
+ });
+
+ if (_.isString(dropdown)) {
+ $menu.append(dropdown);
+ } else {
+ var groups = _.map(dropdown, function(group) {
+ if (_.isArray(group)) return group;
+ else return [group];
+ });
+
+ // Create buttons groups
+ _.each(groups, function(group) {
+ var $group = $('<div>', {
+ 'class': 'buttons'
+ });
+ var sizeClass = 'size-'+group.length;
+
+ // Append buttons
+ _.each(group, function(btn) {
+ btn = _.defaults(btn || {}, {
+ text: '',
+ className: '',
+ onClick: defaultOnClick
+ });
+
+ var $btn = $('<button>', {
+ 'class': 'button '+sizeClass+' '+btn.className,
+ 'text': btn.text
+ });
+ $btn.click(btn.onClick);
+
+ $group.append($btn);
+ });
+
+
+ $menu.append($group);
+ });
+
+ }
+
+
+ return $menu;
+}
+
+// Create a new button in the toolbar
+function createButton(opts) {
+ opts = _.defaults(opts || {}, {
+ // Aria label for the button
+ label: '',
+
+ // Icon to show
+ icon: '',
+
+ // Inner text
+ text: '',
+
+ // Right or left position
+ position: 'left',
+
+ // Other class name to add to the button
+ className: '',
+
+ // Triggered when user click on the button
+ onClick: defaultOnClick,
+
+ // Button is a dropdown
+ dropdown: null
+ });
+
+ buttons.push(opts);
+ updateButton(opts);
+}
+
+// Update a button
+function updateButton(opts) {
+ var $toolbar = $('.book-header');
+ var $title = $toolbar.find('h1');
+
+ // Build class name
+ var positionClass = 'pull-'+opts.position;
+
+ // Create button
+ var $btn = $('<a>', {
+ 'class': 'btn',
+ 'text': opts.text,
+ 'aria-label': opts.label,
+ 'href': '#'
+ });
+
+ // Bind click
+ $btn.click(opts.onClick);
+
+ // Prepend icon
+ if (opts.icon) {
+ $('<i>', {
+ 'class': opts.icon
+ }).prependTo($btn);
+ }
+
+ // Prepare dropdown
+ if (opts.dropdown) {
+ var $container = $('<div>', {
+ 'class': 'dropdown '+positionClass+' '+opts.className
+ });
+
+ // Add button to container
+ $btn.addClass('toggle-dropdown');
+ $container.append($btn);
+
+ // Create inner menu
+ var $menu = createDropdownMenu(opts.dropdown);
+
+ // Menu position
+ $menu.addClass('dropdown-'+(opts.position == 'right'? 'left' : 'right'));
+
+ $container.append($menu);
+
+ $container.insertBefore($title);
+ } else {
+ $btn.addClass(positionClass);
+ $btn.addClass(opts.className);
+ $btn.insertBefore($title);
+ }
+}
+
+// Update all buttons
+function updateAllButtons() {
+ _.each(buttons, updateButton);
+}
+
+// When page changed, reset buttons
+events.bind('page.change', function() {
+ updateAllButtons();
+});
+
+module.exports = {
+ createButton: createButton
+};
diff --git a/theme/javascript/utils/dropdown.js b/theme/javascript/utils/dropdown.js
deleted file mode 100644
index fe4e1f4..0000000
--- a/theme/javascript/utils/dropdown.js
+++ /dev/null
@@ -1,27 +0,0 @@
-define([
- "jQuery"
-], function($) {
-
- var toggleDropdown = function(e) {
- var $dropdown = $(e.currentTarget).parent().find(".dropdown-menu");
-
- $dropdown.toggleClass("open");
- e.stopPropagation();
- e.preventDefault();
- };
-
- var closeDropdown = function(e) {
- $(".dropdown-menu").removeClass("open");
- };
-
- // Bind all dropdown
- var init = function() {
- $(document).on('click', ".toggle-dropdown", toggleDropdown);
- $(document).on('click', ".dropdown-menu", function(e){ e.stopPropagation(); });
- $(document).on("click", closeDropdown);
- };
-
- return {
- init: init
- };
-});
diff --git a/theme/javascript/utils/platform.js b/theme/javascript/utils/platform.js
deleted file mode 100755
index ad5f3b4..0000000
--- a/theme/javascript/utils/platform.js
+++ /dev/null
@@ -1,5 +0,0 @@
-define([], function() {
- return {
- isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
- };
-}); \ No newline at end of file
diff --git a/theme/javascript/utils/sharing.js b/theme/javascript/utils/sharing.js
deleted file mode 100755
index a0cfb59..0000000
--- a/theme/javascript/utils/sharing.js
+++ /dev/null
@@ -1,39 +0,0 @@
-define([
- "jQuery"
-], function($) {
- var types = {
- "twitter": function($el) {
- window.open("http://twitter.com/home?status="+encodeURIComponent($("title").text()+" "+location.href))
- },
- "facebook": function($el) {
- window.open("http://www.facebook.com/sharer/sharer.php?s=100&p[url]="+encodeURIComponent(location.href))
- },
- "google-plus": function($el) {
- window.open("https://plus.google.com/share?url="+encodeURIComponent(location.href))
- },
- "weibo": function($el) {
- window.open("http://service.weibo.com/share/share.php?content=utf-8&url="+encodeURIComponent(location.href)+"&title="+encodeURIComponent($("title").text()))
- },
- "instapaper": function($el) {
- window.open("http://www.instapaper.com/text?u="+encodeURIComponent(location.href));
- },
- "vk": function($el) {
- window.open("http://vkontakte.ru/share.php?url="+encodeURIComponent(location.href));
- }
- };
-
-
- // Bind all sharing button
- var init = function() {
- $(document).on("click", "a[data-sharing],button[data-sharing]", function(e) {
- if (e) e.preventDefault();
- var type = $(this).data("sharing");
-
- types[type]($(this));
- })
- };
-
- return {
- init: init
- };
-});
diff --git a/theme/javascript/utils/storage.js b/theme/javascript/utils/storage.js
deleted file mode 100755
index 57f5878..0000000
--- a/theme/javascript/utils/storage.js
+++ /dev/null
@@ -1,31 +0,0 @@
-define(function(){
- var baseKey = "";
-
- /*
- * Simple module for storing data in the browser's local storage
- */
- return {
- setBaseKey: function(key) {
- baseKey = key;
- },
- set: function(key, value) {
- key = baseKey+":"+key;
- localStorage[key] = JSON.stringify(value);
- },
- get: function(key, def) {
- key = baseKey+":"+key;
- if (localStorage[key] === undefined) return def;
- try {
- var v = JSON.parse(localStorage[key]);
- return v == null ? def : v;;
- } catch(err) {
- console.error(err);
- return localStorage[key] || def;
- }
- },
- remove: function(key) {
- key = baseKey+":"+key;
- localStorage.removeItem(key);
- }
- };
-}); \ No newline at end of file
diff --git a/theme/javascript/utils/url.js b/theme/javascript/utils/url.js
deleted file mode 100644
index 0254299..0000000
--- a/theme/javascript/utils/url.js
+++ /dev/null
@@ -1,33 +0,0 @@
-define([
- "URIjs/URI"
-], function(URI) {
- // Joins path segments. Preserves initial "/" and resolves ".." and "."
- // Does not support using ".." to go above/outside the root.
- // This means that join("foo", "../../bar") will not resolve to "../bar"
- function join(baseUrl, url) {
- var theUrl = new URI(url);
- if (theUrl.is("relative")) {
- theUrl = theUrl.absoluteTo(baseUrl);
- }
- return theUrl.toString();
- }
-
- // A simple function to get the dirname of a path
- // Trailing slashes are ignored. Leading slash is preserved.
- function dirname(path) {
- return join(path, "..");
- }
-
- // test if a path or url is absolute
- function isAbsolute(path) {
- if (!path) return false;
-
- return (path[0] == "/" || path.indexOf("http://") == 0 || path.indexOf("https://") == 0);
- }
-
- return {
- dirname: dirname,
- join: join,
- isAbsolute: isAbsolute
- };
-}) \ No newline at end of file