Compare commits

..

188 Commits

Author SHA1 Message Date
Aaron O'Mullan 9a66f56c79 Bump version to 0.5.1 2014-06-07 11:42:37 -07:00
Samy Pessé 087ca28ca2 Fix style for main languages index page 2014-06-07 16:30:11 +02:00
Samy Pessé 3d5281ff3a Fix GitbookIO/gitbook.io#69: copy cover files at multi-langs root 2014-06-07 16:27:40 +02:00
Samy Pessé f62cf340c5 Fix title and description for pages in website 2014-06-07 16:04:03 +02:00
Samy Pessé 7e12bd3588 Don't use cdn for ace 2014-06-07 16:02:02 +02:00
Samy Pessé 4dbdb95ebe Show quiz inside ebook 2014-06-06 22:20:02 +02:00
Samy Pessé 2c5ddbcd8f Use base style for ebook instead of bootstrap 2014-06-06 21:47:59 +02:00
Samy Pessé be4fd74f90 Improve navigation links design 2014-06-06 20:06:03 +02:00
Samy Pessé 57491e3a8b Improve meta tags for books 2014-06-06 20:00:38 +02:00
Samy Pessé f7a442d297 Improve font sizes and spacing on content 2014-06-06 19:33:41 +02:00
Samy Pessé cfebdc6f6b Fix #296: fix static links on windows 2014-06-06 18:37:35 +02:00
Samy Pessé 65319e8fd6 Bump version to 0.5.0 2014-06-03 22:30:42 +02:00
Samy Pessé 55db44a3b4 Fix #289: center images on ebook 2014-06-03 22:20:51 +02:00
Samy Pessé 13b6045300 Font Merriweather 600 doesn't exists 2014-06-03 20:45:55 +02:00
Samy Pessé b7ff879c31 Remove fonts Anonymous Pro and EB Garamond 2014-06-03 20:43:47 +02:00
Samy Pessé 57ade87fe1 Fix #275: fix emphasized font path 2014-06-03 20:30:04 +02:00
Samy Pessé 907e9ea5a7 Improve font on title
Set sans-serif as default
2014-06-03 20:14:44 +02:00
Samy Pessé 215488dc45 Improve design (more flat) 2014-06-03 18:43:13 +02:00
Samy Pessé a195b3eac2 Improve less files organisation 2014-06-03 18:11:03 +02:00
Samy Pessé 77c6f0e967 Merge pull request #291 from bebraw/book.js
Allow book.js to be used in addition to book.json
2014-06-03 17:20:26 +02:00
Juho Vepsalainen f0e18afec1 In case there's no config file, do not fail 2014-06-03 17:59:26 +03:00
Juho Vepsalainen b6d11d88b7 Allow book.js to be used in addition to book.json
Now it's possible to comment configuration properly and do some dynamic
things if needed.

Closes #287.
2014-06-03 17:42:30 +03:00
Samy Pessé c803270af8 Add option pdf.pageNumber
Fix GitbookIO/editor#38
2014-06-03 16:24:35 +02:00
Samy Pessé 5d51263a58 Remove mixpanel as a default addon 2014-06-03 15:29:15 +02:00
Samy Pessé 7e30720b2c Merge pull request #292 from bebraw/typofix
Fix build success message typo
2014-06-03 15:07:29 +02:00
Juho Vepsalainen 771c5e8b68 Fix build success message typo 2014-06-03 15:58:22 +03:00
Samy Pessé 8f53afab6b Fix #286: fix doc about formats 2014-06-02 20:12:56 +02:00
Samy Pessé 0ad0403d41 Merge pull request #283 from GitbookIO/feature/pdfoptions
Add more options for PDF
2014-06-02 18:22:07 +02:00
Samy Pessé 2515c1dd73 Remove useless command "gitbook ebook" 2014-06-02 18:17:02 +02:00
Samy Pessé 7dfcd102dd Fix calcul of output format for ebook 2014-06-02 18:16:44 +02:00
Samy Pessé a91245d575 Add options paperSize and margin for pdf 2014-06-02 17:57:47 +02:00
Samy Pessé 5f207f5e14 Add options pdf.toc and pdf.fontSize 2014-06-02 17:49:05 +02:00
Samy Pessé 718692de3b Langs index page now use direct link to html file 2014-06-02 14:49:40 +02:00
Samy Pessé 9856a51255 Fix #282: stop generation of langs index for page format 2014-06-02 14:13:52 +02:00
Samy Pessé 58eaaf5514 Fix error when plugin don't extend book 2014-06-02 12:51:22 +02:00
Samy Pessé c0ae0138f8 Improve documentation about book.json 2014-06-02 11:41:16 +02:00
Samy Pessé b1b9dc5b0a Fix error during generation 2014-06-02 11:37:25 +02:00
Samy Pessé f85c5a6498 Add error message when generator is invalid
Fixes #276, #277
2014-06-02 11:32:09 +02:00
Aaron O'Mullan 32eafefc45 Bump version to 0.4.8 2014-05-29 16:36:56 -07:00
Aaron O'Mullan 336999a34e Set default pdf font size to 12 2014-05-29 15:57:56 -07:00
Samy Pessé 1bf5f3b984 Force paper format to A4 fro pdf 2014-05-30 00:33:35 +02:00
Samy Pessé c76d070237 Merge pull request #274 from emmanuel-keller/patch-1
Adding a new plugin (Piwik Open Analytics plugin)
2014-05-30 00:11:38 +02:00
Emmanuel Keller 8c7da1351f Adding a new plugin.
I Just published a first version of the Piwik Open Analytics plugin, on both GIT and NPM.
2014-05-29 23:31:38 +02:00
Samy Pessé 34588c6092 Add margin on pdf output 2014-05-29 18:53:08 +02:00
Samy Pessé 9f00bf8060 Add toc at the end of pdf 2014-05-29 18:33:05 +02:00
Samy Pessé f4a17b182f Improve ebook generation (toc, ...) 2014-05-29 18:28:09 +02:00
Samy Pessé 5c3b345fa6 Generate valid first level table of content for ebook 2014-05-29 17:28:18 +02:00
Samy Pessé 46321fffd9 Use ebook-convert for pdf generation 2014-05-29 17:07:03 +02:00
Aaron O'Mullan 89ba17de84 Bump version to 0.4.7 2014-05-26 12:14:32 -07:00
Aaron O'Mullan 115fba837c Merge pull request #265 from GitbookIO/feature/levels
More than two section level in summary
2014-05-26 11:27:27 -07:00
Samy Pessé 4df51546b2 Remove command log in ebook convert 2014-05-26 18:02:32 +02:00
Samy Pessé f68aaf7ff9 Fix cover option for ebook 2014-05-26 18:02:09 +02:00
Samy Pessé e3d1746ba3 Fix use of cover.jpg in ebook 2014-05-26 17:58:08 +02:00
Samy Pessé a0d5646034 Adapt navigation parsing to third and more levels in summary 2014-05-26 17:42:05 +02:00
Samy Pessé 2f8af5f136 Improve design of summary for deep articles 2014-05-26 17:33:10 +02:00
Samy Pessé 8b7abbd6f4 Render articles of level > 2 in summary 2014-05-26 17:31:35 +02:00
Samy Pessé f228f646eb Remove useless method parseArticle 2014-05-26 17:23:19 +02:00
Samy Pessé 45891d80c7 Parse more than two level in summary 2014-05-26 17:21:23 +02:00
Samy Pessé ac900aa37f Merge pull request #263 from jpoesen/patch-1
Minor typo, grammar and style corrections.
2014-05-26 10:44:25 +02:00
Joeri Poesen 422cbed796 Minor typo, grammar and style corrections. 2014-05-26 08:13:35 +02:00
Samy Pessé 38cbbe46aa Fix cache manifest generation 2014-05-25 20:47:57 +02:00
Aaron O'Mullan c1b57a387b Bump version to 0.4.6 2014-05-23 16:35:44 -07:00
Samy Pessé aaa9ae8bb7 Fix loading button display on navigation 2014-05-24 01:32:39 +02:00
Samy Pessé 6340a64cff Fix #235: search after navigation to another page 2014-05-23 22:28:36 +02:00
Samy Pessé 96bcfa4e20 Fix #218: keep state of font settings 2014-05-23 22:20:36 +02:00
Samy Pessé af0f56b26b Change position of menu action in header 2014-05-23 21:49:11 +02:00
Samy Pessé aed5befa48 Fix display of divider in lateral menu 2014-05-23 21:48:04 +02:00
Samy Pessé 8e37da672d Minor css improvements 2014-05-23 21:43:47 +02:00
Samy Pessé c0f68c45e2 Fix #249: fix links.toAbsolute on windows 2014-05-23 21:10:58 +02:00
Samy Pessé 47fdd73791 Fix cache manifest in the html tag
Serve command disable cache
2014-05-23 17:45:46 +02:00
Samy Pessé 7c0b88cf2e Plugins can extend html 2014-05-23 17:00:37 +02:00
Samy Pessé bf21633b6c Support multi-languages book in json format 2014-05-23 16:35:34 +02:00
Samy Pessé ad9b1fd6f7 Fix order of progress for level superior to x.9
Related to GitbookIO/gitbook.io#23
2014-05-21 23:31:57 +02:00
Samy Pessé d245895085 Add link to documentation 2014-05-21 20:22:28 +02:00
Samy Pessé c7439d12f7 Add livereload as default plugins only for serve 2014-05-21 17:34:36 +02:00
Samy Pessé 8d6923ce2c Merge pull request #234 from davidagraf/livereload
Livereload
2014-05-21 17:17:08 +02:00
David Graf b21fb24a82 gitbook-plugin-livereload dependency to npm 2014-05-21 16:35:55 +02:00
David Graf e7be132194 Merge branch 'master' into livereload 2014-05-21 16:24:07 +02:00
Samy Pessé 0e1717f4bb Improve grammar (thanks to @theoutlander ) 2014-05-21 10:46:26 +02:00
Aaron O'Mullan 276dbe29c7 Bump version to 0.4.5 2014-05-20 23:29:39 -07:00
Aaron O'Mullan 21fd5924b8 Fix generate failing on README parsing
Failed if no title or description could be extracted
2014-05-20 23:29:06 -07:00
Aaron O'Mullan 443def6cfd Bump version to 0.4.4 2014-05-20 22:59:26 -07:00
Samy Pessé 75464574d7 For multi-langs, run build one by one 2014-05-21 00:09:40 +02:00
Samy Pessé 86f05c68c5 Improve parsing of readme (extraction of title and description as plain text and not markdown) 2014-05-20 23:45:35 +02:00
Samy Pessé 8dd407eaa8 Fix merge of output for multi-languages books 2014-05-20 23:31:22 +02:00
Samy Pessé ea7ad10e8a Add support for multi-languages books in JSON format 2014-05-20 23:21:12 +02:00
Samy Pessé 62ec695dc8 Extract title and description from readme before switching to multi-languages handling 2014-05-20 23:07:41 +02:00
Samy Pessé ca37ac73a9 Add more infos about gitbook.io 2014-05-20 14:40:22 +02:00
Samy Pessé 96edd50c8f Add norm for small cover 2014-05-20 01:23:49 +02:00
Samy Pessé 3993c1e35a Change advised ratio for cover 2014-05-19 21:47:41 +02:00
Aaron O'Mullan 5298558925 Bump version to 0.4.3 2014-05-18 02:25:53 -07:00
Samy Pessé c6ba147917 Print stack only when env DEBUG is set 2014-05-17 20:35:41 +02:00
Samy Pessé 7efecea3d6 Exit process with -1 when error occurs 2014-05-17 20:35:13 +02:00
Aaron O'Mullan a73fed3ebd Merge pull request #240 from ajschumacher/patch-1
typo: "blockin" to "block in"
2014-05-16 09:06:15 -07:00
Aaron Schumacher 50ef7bcd23 typo: "blockin" to "block in" 2014-05-16 12:02:06 -04:00
Aaron O'Mullan 82e422cb89 Fix issue of disabling issues and contribute
Improve robustness of template display conditions
2014-05-15 21:35:09 -07:00
Aaron O'Mullan dab46c779d Allow github support to be disabled in book.json
Fixes #238
2014-05-15 21:24:28 -07:00
David Graf 7c65adc9b2 disable cache manifest import if cache is disabled 2014-05-15 12:07:51 +02:00
David Graf 9b96ef9bae added livereload dependency 2014-05-15 11:41:59 +02:00
David Graf e97d1941dc Introducing livereload server 2014-05-15 08:09:02 +02:00
David Graf 0b0ac46e34 Introducing option to disable cache manifest generation 2014-05-15 08:07:24 +02:00
David Graf 622bbcb8ea ignore vim swap files 2014-05-15 07:43:59 +02:00
Samy Pessé 58a5245d21 Merge pull request #233 from embreinhardt/master
Add LESS variable for sidebar full-width breakpoint
2014-05-14 20:10:02 +02:00
Erika Reinhardt 612f9a6dc7 Add LESS variable for sidebar full-width breakpoint 2014-05-14 11:01:57 -07:00
Samy Pessé 90d4dff263 Merge pull request #232 from embreinhardt/master
Make font-settings.html indentation consistent
2014-05-14 10:35:32 +02:00
Erika Reinhardt 0f38385c7d Make font-settings.html indentation consistent
[Minor] Was mix of tabs and spaces, which messed with text edit display
2014-05-14 01:25:18 -07:00
Samy Pessé 2b50616251 Merge pull request #231 from embreinhardt/master
Add HTML classes to make plugins easier to write
2014-05-14 10:20:42 +02:00
Erika Reinhardt 5b7f37f0d4 Add HTML classes to make plugins easier to write 2014-05-14 01:14:21 -07:00
Aaron O'Mullan 6ca8e26741 Merge pull request #229 from weitzman/patch-1
Fix recent typo in README.MD
2014-05-12 14:04:52 -07:00
Moshe Weitzman a3ada05598 Fix recent typo in README.MD 2014-05-12 16:18:50 -04:00
Samy Pessé 42eb11aaff Add infos about cover 2014-05-11 16:47:30 +02:00
Samy Pessé b581859afd Merge pull request #223 from cef62/fix-windows-paths
Force forward slashes on SUMMARY.md path for articles
2014-05-08 22:54:40 +02:00
Matteo Ronchi 2e22fae52a Force forward slashes on SUMMARY.md paths
this prevent windows user to generate invalid path on unix OS
Reference to issue #39 on gitbook-editor
2014-05-08 22:46:53 +02:00
Aaron O'Mullan 603ac8b91d Merge pull request #221 from frodsan/patch-1
Don't show divider even if githubId is present.
2014-05-08 09:58:06 -07:00
Francesco Rodríguez 5faa4841ca Don't show divider even if githubId is present. 2014-05-08 13:40:15 -03:00
Samy Pessé 7416b826e0 Fix #218: Add settings for client theme (font family, size, theme) 2014-05-08 13:16:30 +02:00
Aaron O'Mullan 82afc34755 Merge pull request #219 from BjoernSchotte/feature/doc-theme
added documentation for theme option
2014-05-07 23:08:53 -07:00
Björn Schotte da39631a5a added documentation for theme option 2014-05-08 07:57:33 +02:00
Aaron O'Mullan fc8685a7c1 Merge pull request #216 from ianmcburnie/accessibility
Accessibility: added aria-labels to header
2014-05-06 12:34:43 -07:00
Ian McBurnie 5e0204a980 Accessibility: added aria-labels to header
Feel free to change any of the label text that I have chosen :-)
2014-05-06 11:27:54 -07:00
Samy Pessé 6bb0f33f39 Fix #211: read config file before cleaning output folder 2014-05-06 12:08:27 +02:00
Aaron O'Mullan ed4fd565b2 Merge pull request #213 from ianmcburnie/accessibility
Accessibility: aria-label added to navigation links
2014-05-05 22:37:21 -07:00
Ian McBurnie 79cff28abd Accessibility: aria-label added to navigation links
Current behavior: Screenreader just reads ‘link’ when landing on the
previous and next navigation links, giving the user no indication of
what the link does or where it goes. New Behaviour: Screenreader reads
“Previous page: {page title}” and “Next page: {page title}”
respectively.
2014-05-05 22:29:22 -07:00
Samy Pessé 27329239f7 Fix #208: correct name for edit/contribute link in doc 2014-05-05 17:55:19 +02:00
Samy Pessé 9de86fc0e4 Add tests for book generation 2014-05-05 15:25:15 +02:00
Samy Pessé 7fa7826372 Merge pull request #203 from GitbookIO/improve/generate
Improve generate's reliability, fixes #202
2014-05-04 22:37:42 +02:00
Aaron O'Mullan 96ecfd098b Improve generate's reliability, fixes #202
This also improves speed on multi language books
2014-05-04 12:53:24 -07:00
Samy Pessé f7fe976c7e Fix #197: use URI.js for calculation of relative url
Use bower for client dependencies
2014-05-04 15:15:18 +02:00
Samy Pessé 756629b91d Add bower dependencies 2014-05-04 15:03:47 +02:00
Samy Pessé e640d124d4 Add bower vendors to gitignore 2014-05-04 14:58:45 +02:00
Samy Pessé 7f41466297 Remove vendors directory 2014-05-04 14:58:01 +02:00
Samy Pessé 57dfc062d2 Create CHANGES.md 2014-05-04 13:09:27 +02:00
Aaron O'Mullan 799815d85a Bump version to 0.4.2 2014-05-01 16:17:05 -07:00
Samy Pessé 2946246834 Fix #191: remove log for changes in files 2014-05-01 18:55:45 +02:00
Samy Pessé 6d276d1e79 Add option to remove home link 2014-05-01 18:53:49 +02:00
Samy Pessé 6a3a369b3e Add options to remove sharing links 2014-05-01 18:37:30 +02:00
Samy Pessé 5d8afa2135 Merge pull request #196 from mrpotes/new-plugins
New plugins
2014-05-01 18:33:30 +02:00
Samy Pessé ab3e6aa8ce Fix font-size for titles in markdown content 2014-05-01 18:29:42 +02:00
James Phillpotts 822092736e New plugins 2014-05-01 17:00:38 +01:00
Samy Pessé 7497acd2e3 Center navigation button on mobile on start page 2014-05-01 17:37:38 +02:00
Samy Pessé ae137885b8 Fix book content padding on mobile 2014-05-01 17:27:03 +02:00
Samy Pessé b964cc7009 Fix #162: hide watch/star buttons on mobile 2014-05-01 17:16:09 +02:00
Samy Pessé c2ea126c5f Fix #182: Replace all book body when page changes 2014-05-01 14:05:14 +02:00
Samy Pessé b668dba479 Update gaze version to 0.5.1 2014-05-01 13:26:50 +02:00
Samy Pessé 2cae5a0310 Secure .book position 2014-05-01 13:26:19 +02:00
Samy Pessé b6a0f3ff67 Fix #164: use position absolute instead of fixed 2014-04-30 17:01:25 +02:00
Samy Pessé fa00b52fb5 Fix #127: add configuration for changing links in sidebar 2014-04-30 16:46:24 +02:00
Samy Pessé 2999d90907 Merge pull request #188 from snipe/feature/json-doc
Added quotes for JSON example, per #127
2014-04-30 12:48:28 +02:00
snipe b6696bcad2 Added quotes for JSON example, per #127 2014-04-30 06:46:28 -04:00
Samy Pessé 728c87c525 Merge pull request #187 from snipe/feature/typo-fixes
Fixed typos
2014-04-30 12:05:12 +02:00
snipe ace394ee2d Fixed typos 2014-04-30 05:28:18 -04:00
Samy Pessé 076ee98f64 Fix #186: Plugins that don't have assets should be skipped 2014-04-30 11:15:01 +02:00
Samy Pessé 2e472654ca Fix error when calling hooks in site generation 2014-04-30 10:54:45 +02:00
Aaron O'Mullan d46bd14678 0.4.1 2014-04-29 10:23:54 -07:00
Samy Pessé b3e5b3d106 Fix #183: normalize hooks and transformation of page (content, progress, ..° 2014-04-29 17:45:54 +02:00
Samy Pessé b830ef5dca Merge pull request #179 from erixtekila/master
Update README.md
2014-04-29 08:23:33 +02:00
Eric Priou 45326c4ad2 Update README.md 2014-04-29 03:45:03 +02:00
Aaron O'Mullan 603b61e8c8 Bump version to 0.4.0 2014-04-28 13:26:40 -07:00
Samy Pessé efb066c8e3 Update doc about how to configure gitbook 2014-04-28 20:45:10 +02:00
Samy Pessé c30db427b1 Remove some options from command line (moved to book.json) 2014-04-28 20:33:25 +02:00
Samy Pessé 23056fc85d Use a global configuration file for a book (default to book.json) 2014-04-28 20:29:36 +02:00
Samy Pessé 5f1bfc0280 Fix #163: Move logic of git infos extraction to lib 2014-04-28 20:16:30 +02:00
Samy Pessé b715901f92 Add hooks "page:before" and "page:after" 2014-04-28 19:56:40 +02:00
Samy Pessé 38fea06cf9 Use hook to allow plugins to edit page content during generation (Fixes #176) 2014-04-28 17:21:06 +02:00
Aaron O'Mullan 4481b5f556 Bump version to 0.3.2 2014-04-27 13:46:20 -07:00
Samy Pessé 0e0d051f7c Merge pull request #173 from erixtekila/master
Update README.md
2014-04-27 22:26:56 +02:00
Eric Priou a1b649a279 Update README.md 2014-04-27 22:24:37 +02:00
Eric Priou e6bc0a0fb7 Update README.md
3rd party plugins done in the weekend
2014-04-27 22:18:09 +02:00
Samy Pessé 93e4016c09 Add link to disqus plugin 2014-04-27 21:55:50 +02:00
Samy Pessé 20c741a248 Fix stupid typo thanks to @erixtekila 2014-04-27 21:21:48 +02:00
Samy Pessé 54f530c7c3 Merge pull request #171 from GitbookIO/feature/offlineCache
Improve pages loading
2014-04-27 18:05:13 +02:00
Samy Pessé 9fdc5da22d Update page when new version of application cache 2014-04-27 17:57:34 +02:00
Samy Pessé 99ce48b146 Add base loading indicator for navigation
#161
2014-04-27 17:36:12 +02:00
Samy Pessé 6a33ad9ef7 Add base cache manifest 2014-04-27 17:14:57 +02:00
Samy Pessé ca94055adf Adapt tests for plugins 2014-04-27 15:45:16 +02:00
Samy Pessé 36a67e4c91 Fix #169: fix resolve of external plugins 2014-04-27 15:41:02 +02:00
Samy Pessé 69431e1bbb Fix #165: replace separator from ":" to "," 2014-04-27 09:14:39 +02:00
Samy Pessé a3fc4ee2b0 Fix display of code element in sepia/night 2014-04-25 18:14:41 +02:00
Samy Pessé e505fd6d54 Improve summary divider for theme sepia and night 2014-04-25 18:12:39 +02:00
Samy Pessé 93a3785e4c Improve highlight theme for sepia and night themes 2014-04-25 18:05:09 +02:00
Samy Pessé a02de84812 Change highlight theme to tomorrow 2014-04-25 17:56:48 +02:00
Samy Pessé c0521f0ec7 Improve display of pre in markdown content 2014-04-25 17:48:31 +02:00
Samy Pessé 83d8c16853 Add file watcher in "gitbook serve" command (Fix #47, #89, #140) 2014-04-25 16:23:13 +02:00
Samy Pessé 47e9506397 Fix #153 and Improve pushState navigation 2014-04-25 15:12:19 +02:00
Samy Pessé b8a3d0e254 Merge pull request #144 from attomos/master
Make quiz easier to click by surround it with label.
2014-04-25 14:29:04 +02:00
Samy Pessé 001d7ec44e Merge pull request #155 from pborreli/patch-1
Fixed typo
2014-04-24 22:44:36 +02:00
Pascal Borreli 5cb0ec447a Fixed typo 2014-04-24 21:43:32 +01:00
Nattaphoom Ch 8f4b799d3c Make quiz easier to click by surround it with label. 2014-04-24 01:19:13 +07:00
89 changed files with 1918 additions and 19936 deletions
+5
View File
@@ -23,3 +23,8 @@ build/Release
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules
theme/javascript/vendors
# vim swapfile
*.swp
+2
View File
@@ -0,0 +1,2 @@
# Release notes
+20 -7
View File
@@ -4,10 +4,18 @@ module.exports = function (grunt) {
// Load NPM tasks
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks("grunt-bower-install-simple");
// Init GRUNT configuraton
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
'bower-install-simple': {
options: {
color: true,
production: false,
directory: "theme/javascript/vendors"
}
},
less: {
development: {
options: {
@@ -16,8 +24,8 @@ module.exports = function (grunt) {
optimization: 2
},
files: {
"theme/assets/style.css": "theme/stylesheets/main.less",
"theme/assets/print.css": "theme/stylesheets/print.less"
"theme/assets/style.css": "theme/stylesheets/website.less",
"theme/assets/print.css": "theme/stylesheets/ebook.less"
}
}
},
@@ -31,11 +39,13 @@ module.exports = function (grunt) {
optimize: "uglify", //"uglify",
include: ["requireLib"],
paths: {
"jQuery": 'vendors/jquery',
"lodash": 'vendors/lodash',
"requireLib": 'vendors/require',
"Mousetrap": 'vendors/mousetrap',
"lunr": path.join(__dirname, "node_modules/lunr/lunr")
"jQuery": 'vendors/jquery/dist/jquery',
"lodash": 'vendors/lodash/dist/lodash',
"requireLib": 'vendors/requirejs/require',
"Mousetrap": 'vendors/mousetrap/mousetrap',
"lunr": 'vendors/lunr.js/lunr',
"URI": 'vendors/URIjs/src/URI',
"ace": 'vendors/ace-builds/src-noconflict/'
},
shim: {
'jQuery': {
@@ -56,8 +66,11 @@ module.exports = function (grunt) {
}
});
grunt.registerTask("bower-install", [ "bower-install-simple" ]);
// Build
grunt.registerTask('build', [
'bower-install',
'less',
'requirejs'
]);
+126 -20
View File
@@ -3,7 +3,7 @@ GitBook
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
GitBook is a command line tool (and Node.js library) for building beautiful programming books and exercises using GitHub/Git and Markdown. You can see an example: [Learn Javascript](http://gitbookio.github.io/javascript/). An [editor](https://github.com/GitbookIO/editor) is available for Windows, Mac and Linux. You can follow [@GitBookIO](https://twitter.com/GitBookIO) on Twitter.
GitBook is a command line tool (and Node.js library) for building beautiful books and exercises using GitHub/Git and Markdown. You can see an example: [Learn Javascript](https://www.gitbook.io/book/GitBookIO/javascript). An [editor](https://github.com/GitbookIO/editor) is available for Windows, Mac and Linux. You can follow [@GitBookIO](https://twitter.com/GitBookIO) on Twitter. Complete documentation is available at [help.gitbook.io](http://help.gitbook.io/).
![Image](https://raw.github.com/GitbookIO/gitbook/master/preview.png)
@@ -30,14 +30,102 @@ $ gitbook build ./repository --output=./outputFolder
Options for commands `build` and `serve` are:
```
-t, --title <name> Name of the book to generate, defaults to repo name
-i, --intro <intro> Description of the book to generate
-g, --github <repo_path> ID of github repo like : username/repo
-o, --output <directory> Path to output directory, defaults to ./_book
-f, --format <name> Change generation format, defaults to site, availables are: site, page, pdf, json
--githubHost <url> The url of the github host (defaults to https://github.com/)
--plugins <plugins> List of plugins to use separated by ","
--pluginsConfig <json file> JSON File containing plugins configuration
-f, --format <name> Change generation format, defaults to site, availables are: site, page, ebook, json
--config <config file> Configuration file to use, defaults to book.js or book.json
```
GitBook loads the default configuration from a `book.json` file in the repository if it exists.
Here are the options that can be stored in this file:
```js
{
// Folders to use for output
// Caution: it overrides the value from the command line
// It's not advised this option in the book.json
"output": null,
// Generator to use for building
// Caution: it overrides the value from the command line
// It's not advised this option in the book.json
"generator": "site",
// Book title and description (defaults are extracted from the README)
"title": null,
"description": null,
// For ebook format, the extension to use for generation (default is detected from output extension)
// "epub", "pdf", "mobi"
// Caution: it overrides the value from the command line
// It's not advised this option in the book.json
"extension": null,
// GitHub information (defaults are extracted using git)
"github": null,
"githubHost": "https://github.com/",
// Plugins list, can contain "-name" for removing default plugins
"plugins": [],
// Global configuration for plugins
"pluginsConfig": {
"fontSettings": {
"theme": "sepia", "night" or "white",
"family": "serif" or "sans",
"size": 1 to 4
}
},
// Set another theme with your own layout
// It's recommended to use plugins or add more options for default theme, though
// See https://github.com/GitbookIO/gitbook/issues/209
"theme": "./localtheme",
// Links in template (null: default, false: remove, string: new value)
"links": {
// Link to home in the top-left corner
"home": null,
// Links in top of sidebar
"about": null,
"issues": null,
"contribute": null,
// Sharing links
"sharing": {
"google": null,
"facebook": null,
"twitter": null
}
},
// Options for PDF generation
"pdf": {
// Add toc at the end of the file
"toc": true,
// Add page numbers to the bottom of every page
"pageNumbers": false,
// Font size for the fiel content
"fontSize": 12,
// Paper size for the pdf
// Choices are [ua0, ua1, ua2, ua3, ua4, ua5, ua6, ub0, ub1, ub2, ub3, ub4, ub5, ub6, ulegal, uletter]
"paperSize": "a4",
// Margin (in pts)
// Note: 72 pts equals 1 inch
"margin": {
"right": 62,
"left": 62,
"top": 36,
"bottom": 36
}
}
}
```
You can publish your books to our index by visiting [GitBook.io](http://www.gitbook.io)
@@ -46,19 +134,18 @@ You can publish your books to our index by visiting [GitBook.io](http://www.gitb
GitBook can generate your book in the following formats:
* **Static Website**: This is the default format, it generates a complete interactive static website that can be for example hosted on GitHub Pages.
* **PDF**: A complete PDF book with exercise solutions at the end of the book. Generate to this format using: ```gitbook pdf ./myrepo```, you need to have [gitbook-pdf](https://github.com/GitbookIO/gitbook-pdf) installed.
* **eBook**: A complete eBook with exercise solutions at the end of the book. Generate to this format using: ```gitbook ebook ./myrepo```, you need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed.
* **Single Page**: The book will be stored in a single printable HTML page, this format is used for conversion to PDF or eBook. Generate to this format using: ```gitbook build ./myrepo -f page```.
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate to this format using: ```gitbook build ./myrepo -f json```.
* **Static Website**: This is the default format. It generates a complete interactive static website that can be, for example, hosted on GitHub Pages.
* **eBook**: A complete eBook with exercise solutions at the end of the book. Generate this format using: ```gitbook ebook ./myrepo```. You need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed. The output format could be **PDF**, **ePub** or **MOBI**.
* **Single Page**: The book will be stored in a single printable HTML page. This format is used for conversion to PDF or eBook. Generate this format using: ```gitbook build ./myrepo -f page```.
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate this format using: ```gitbook build ./myrepo -f json```.
## Book Format
A book is a GitHub repository containing at least 2 files: `README.md` and `SUMMARY.md`.
A book is a Git repository containing at least 2 files: `README.md` and `SUMMARY.md`.
#### README.md
As usual, it should contains an introduction for your book. It will be automatically added to the final summary.
Typically, this should be the introduction for your book. It will be automatically added to the final summary.
#### SUMMARY.md
@@ -78,11 +165,11 @@ This is the summary of my book.
* [example 1](section2/example1.md)
```
Files that are not included in the `SUMMARY.md` will not be processed by `gitbook`.
Files that are not included in `SUMMARY.md` will not be processed by `gitbook`.
#### Exercises
A book can contain interactive exercises (currently only in Javascript but Python and Ruby are coming soon ;) ). An exercise is a code challenge provided to the reader, which is given a code editor to write a solution which is checked against the book author's validation code.
A book can contain interactive exercises (currently only in Javascript but Python and Ruby are coming soon ;) ). An exercise is a code challenge provided to the reader, who is given a code editor to write a solution which is checked against the book author's validation code.
An exercise is defined by 4 simple parts:
@@ -91,7 +178,7 @@ An exercise is defined by 4 simple parts:
* **Solution** code, being a correct solution to the exercise
* **Validation** code that tests the correctness of the user's input
Exercises need to start and finish with a separation bar (```---``` or ```***```). It should contain 3 code elements (**base**, **solution** and **validation**). It can contain a 4th element that provides **context** code (functions, imports of libraries etc ... that shouldn't be displayed to the user).
Exercises need to start and finish with a separation bar (```---``` or ```***```). It should contain 3 code elements (**base**, **solution** and **validation**). It can contain a 4th element that provides **context** code (functions, imports of libraries, etc which shouldn't be displayed to the user).
---
@@ -133,11 +220,22 @@ You can see a complete example with the [Learn Git](https://github.com/GitbookIO
#### Ignoring files & folders
GitBook will read the `.gitignore`, `.bookignore` and `.ignore` files to get a list of files and folders to skip. (The format inside those files, follows the same convention as `.gitignore`)
GitBook will read the `.gitignore`, `.bookignore` and `.ignore` files to get a list of files and folders to skip. (The format inside those files follows the same convention as `.gitignore`)
#### Cover
A cover image can be set by creating a file: **/cover.jpg**.
The best resolution is **1800x2360**. The generation of the cover can be done automatically using the plugin [autocover](https://github.com/GitbookIO/plugin-autocover).
A small version of the cover can also be set by creating a file: **/cover_small.jpg**.
#### Publish your book
The platform [GitBook.io](https://www.gitbook.io/) is like an "Heroku for books": you can create a book on it (public, paid, or private) and update it using **git push**.
#### Plugins
Plugins can used to extend your book functionnalities. Read [GitbookIO/plugin](https://github.com/GitbookIO/plugin) for more informations about how to build a plugin for gitbook.
Plugins can used to extend your book's functionality. Read [GitbookIO/plugin](https://github.com/GitbookIO/plugin) for more information about how to build a plugin for GitBook.
##### Default plugins:
@@ -147,3 +245,11 @@ Plugins can used to extend your book functionnalities. Read [GitbookIO/plugin](h
##### Other plugins:
* [Google Analytics](https://github.com/GitbookIO/plugin-ga): Google Analytics tracking for your book
* [Disqus](https://github.com/GitbookIO/plugin-disqus): Disqus comments integration in your book
* [Autocover](https://github.com/GitbookIO/plugin-autocover): Generate a cover for your book
* [Transform annoted quotes to notes](https://github.com/erixtekila/gitbook-plugin-richquotes): Allow extra markdown markup to render blockquotes as nice notes
* [Send code to console](https://github.com/erixtekila/gitbook-plugin-toconsole): Evaluate javascript block in the browser inspector's console
* [Revealable sections](https://github.com/mrpotes/gitbook-plugin-reveal): Reveal sections of the page using buttons made from the first title in each section
* [Markdown within HTML](https://github.com/mrpotes/gitbook-plugin-nestedmd): Process markdown within HTML blocks - allows custom layout options for individual pages
* [Bootstrap JavaScript plugins](https://github.com/mrpotes/gitbook-plugin-bootstrapjs): Use the [Bootstrap JavaScript plugins](http://getbootstrap.com/javascript) in your online GitBook
* [Piwik Open Analytics](https://github.com/emmanuel-keller/gitbook-plugin-piwik): Piwik Open Analytics tracking for your book
+22 -40
View File
@@ -12,60 +12,42 @@ var buildCommand = function(command) {
return command
.option('-o, --output <directory>', 'Path to output directory, defaults to ./_book')
.option('-f, --format <name>', 'Change generation format, defaults to site, availables are: '+_.keys(generators).join(", "))
.option('-t, --title <name>', 'Name of the book to generate, default is extracted from readme')
.option('-i, --intro <intro>', 'Description of the book to generate, default is extracted from readme')
.option('--plugins <plugins>', 'List of plugins to use separated by ","')
.option('--pluginsConfig <json file>', 'JSON File containing plugins configuration')
.option('-g, --github <repo_path>', 'ID of github repo like : username/repo')
.option('--githubHost <url>', 'The url of the github host (defaults to https://github.com/');
.option('--config <config file>', 'Configuration file to use, defaults to book.js or book.json')
};
var buildEbookCommand = function(command) {
return buildCommand(command)
.option('-c, --cover <path>', 'Cover image, default is cover.jpg if exists');
};
var makeBuildFunc = function(converter) {
return function(dir, options) {
dir = dir || process.cwd();
outputDir = options.output;
// Read plugins config
var pluginsConfig = {};
if (options.pluginsConfig) {
pluginsConfig = JSON.parse(fs.readFileSync(options.pluginsConfig))
}
console.log('Starting build ...');
// Get repo's URL
return utils.gitURL(dir)
.then(function(url) {
// Get ID of repo
return utils.githubID(url);
}, function(err) {
return null;
})
.then(function(repoID) {
return converter(
_.extend(options || {}, {
input: dir,
output: outputDir,
title: options.title,
description: options.intro,
github: options.github || repoID,
githubHost: options.githubHost,
generator: options.format,
plugins: options.plugins,
pluginsConfig: pluginsConfig
})
);
})
return converter(
_.extend({}, options || {}, {
input: dir,
output: outputDir,
generator: options.format,
configFile: options.config
})
)
.then(function(output) {
console.log("Successfuly built !");
console.log("Successfully built!");
return output;
}, utils.logError);
};
}, utils.logError)
.fail(function() {
process.exit(-1);
});
};
};
module.exports = {
folder: makeBuildFunc(generate.folder),
file: makeBuildFunc(generate.file),
command: buildCommand
command: buildCommand,
commandEbook: buildEbookCommand
};
+66 -20
View File
@@ -4,6 +4,7 @@ var Q = require('q');
var _ = require('lodash');
var path = require('path');
var prog = require('commander');
var tinylr = require('tiny-lr-fork');
var pkg = require('../package.json');
var generators = require("../lib/generate").generators;
@@ -12,6 +13,7 @@ var fs = require('../lib/generate/fs');
var utils = require('./utils');
var build = require('./build');
var Server = require('./server');
// General options
prog
@@ -24,39 +26,83 @@ build.command(prog.command('build [source_dir]'))
build.command(prog.command('serve [source_dir]'))
.description('Build then serve a gitbook from a directory')
.option('-p, --port <port>', 'Port for server to listen on', 4000)
.option('--no-watch', 'Disable restart with file watching')
.action(function(dir, options) {
build.folder(dir, options || {})
.then(function(_options) {
console.log();
console.log('Starting server ...');
return utils.serveDir(_options.output, options.port)
.fail(utils.logError);
})
.then(function() {
console.log('Serving book on http://localhost:'+options.port);
console.log();
console.log('Press CTRL+C to quit ...');
var server = new Server();
// init livereload server
var lrOptions = {port: 35729};
var lrServer = tinylr(lrOptions);
var lrPath = undefined;
lrServer.listen(lrOptions.port, function(err) {
if (err) { return console.log(err); }
console.log('Live reload server started on port: ' + lrOptions.port);
});
var generate = function() {
if (server.isRunning()) console.log("Stopping server");
server.stop()
.then(function() {
return build.folder(dir, _.extend(options || {}, {
cache: false,
defaultsPlugins: ["livereload"]
}));
})
.then(function(_options) {
console.log();
console.log('Starting server ...');
return server.start(_options.output, options.port)
.then(function() {
console.log('Serving book on http://localhost:'+options.port);
if (lrPath) {
// trigger livereload
lrServer.changed({body:{files:[lrPath]}})
}
if (!options.watch) return;
return utils.watch(_options.input)
.then(function(filepath) {
// set livereload path
lrPath = filepath;
console.log("Restart after change in files");
console.log('');
return generate();
})
})
})
.fail(utils.logError);
};
console.log('Press CTRL+C to quit ...');
console.log('')
generate();
});
build.command(prog.command('pdf [source_dir]'))
build.commandEbook(prog.command('pdf [source_dir]'))
.description('Build a gitbook as a PDF')
.option('-pf, --paperformat <format>', 'PDF paper format (default is A4): "5in*7.5in", "10cm*20cm", "A4", "Letter"')
.action(function(dir, options) {
build.file(dir, _.extend(options, {
extension: "pdf",
format: "pdf"
format: "ebook"
}));
});
build.command(prog.command('ebook [source_dir]'))
.description('Build a gitbook as a eBook')
.option('-c, --cover <path>', 'Cover image, default is cover.png if exists')
build.commandEbook(prog.command('epub [source_dir]'))
.description('Build a gitbook as a ePub book')
.action(function(dir, options) {
var ext = options.output ? path.extname(options.output) : "epub";
build.file(dir, _.extend(options, {
extension: ext,
extension: "epub",
format: "ebook"
}));
});
build.commandEbook(prog.command('mobi [source_dir]'))
.description('Build a gitbook as a Mobi book')
.action(function(dir, options) {
build.file(dir, _.extend(options, {
extension: "mobi",
format: "ebook"
}));
});
+96
View File
@@ -0,0 +1,96 @@
var Q = require('q');
var _ = require('lodash');
var events = require('events');
var http = require('http');
var send = require('send');
var util = require('util');
var url = require('url');
var Server = function() {
this.running = null;
this.dir = null;
this.port = 0;
this.sockets = [];
};
util.inherits(Server, events.EventEmitter);
// Return true if the server is running
Server.prototype.isRunning = function() {
return this.running != null;
};
// Stop the server
Server.prototype.stop = function() {
var that = this;
if (!this.isRunning()) return Q();
var d = Q.defer();
this.running.close(function(err) {
that.running = null;
that.emit("state", false);
if (err) d.reject(err);
else d.resolve();
});
for (var i = 0; i < this.sockets.length; i++) {
this.sockets[i].destroy();
}
return d.promise;
};
Server.prototype.start = function(dir, port) {
var that = this, pre = Q();
port = port || 8004;
if (that.isRunning()) pre = this.stop();
return pre
.then(function() {
var d = Q.defer();
that.running = http.createServer(function(req, res){
// Render error
function error(err) {
res.statusCode = err.status || 500;
res.end(err.message);
}
// Redirect to directory's index.html
function redirect() {
res.statusCode = 301;
res.setHeader('Location', req.url + '/');
res.end('Redirecting to ' + req.url + '/');
}
// Send file
send(req, url.parse(req.url).pathname)
.root(dir)
.on('error', error)
.on('directory', redirect)
.pipe(res);
});
that.running.on('connection', function (socket) {
that.sockets.push(socket);
socket.setTimeout(4000);
socket.on('close', function () {
that.sockets.splice(that.sockets.indexOf(socket), 1);
});
});
that.running.listen(port, function(err) {
if (err) return d.reject(err);
that.port = port;
that.dir = dir;
that.emit("state", true);
d.resolve();
});
return d.promise;
});
}
module.exports = Server;
+18 -75
View File
@@ -4,98 +4,41 @@ var _ = require('lodash');
var http = require('http');
var send = require('send');
var url = require('url');
var cp = require('child_process');
var path = require('path');
var Gaze = require('gaze').Gaze;
// Get the remote of a given repo
function gitURL(path) {
function watch(dir) {
var d = Q.defer();
dir = path.resolve(dir);
cp.exec("git config --get remote.origin.url", {
cwd: path,
env: process.env,
}, function(err, stdout, stderr) {
if(err) {
return d.reject(err);
}
return d.resolve(stdout);
var gaze = new Gaze("**/*.md", {
cwd: dir
});
return d.promise
.then(function(output) {
return output.replace(/(\r\n|\n|\r)/gm, "");
gaze.once("all", function(e, filepath) {
gaze.close();
d.resolve(filepath);
});
}
gaze.once("error", function(err) {
gaze.close();
// Poorman's parsing
// Parse a git URL to a github ID : username/reponame
function githubID(_url) {
// Remove .git if it's in _url
var sliceEnd = _url.slice(-4) === '.git' ? -4 : _url.length;
// Detect HTTPS repos
var parsed = url.parse(_url);
if(parsed.protocol === 'https:' && parsed.host === 'github.com') {
return parsed.path.slice(1, sliceEnd);
}
// Detect SSH repos
if(_url.indexOf('git@') === 0) {
return _url.split(':', 2)[1].slice(0, sliceEnd);
}
// None found
return null;
}
function titleCase(str)
{
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
function serveDir(dir, port) {
var d = Q.defer();
var server = http.createServer(function(req, res){
// Render error
function error(err) {
res.statusCode = err.status || 500;
res.end(err.message);
}
// Redirect to directory's index.html
function redirect() {
res.statusCode = 301;
res.setHeader('Location', req.url + '/');
res.end('Redirecting to ' + req.url + '/');
}
// Send file
send(req, url.parse(req.url).pathname)
.root(dir)
.on('error', error)
.on('directory', redirect)
.pipe(res);
}).listen(port);
d.resolve(server);
d.reject(err);
});
return d.promise;
}
function logError(err) {
console.log(err.stack || err.message || err);
var message = err.message || err;
if (process.env.DEBUG != null) message = err.stack || message;
console.log(message);
return Q.reject(err);
};
// Exports
module.exports = {
gitURL: gitURL,
githubID: githubID,
titleCase: titleCase,
serveDir: serveDir,
watch: watch,
logError: logError
};
+13
View File
@@ -0,0 +1,13 @@
{
"name": "GitBook",
"version": "1.0.0",
"dependencies": {
"jquery": "2.1.1",
"lodash": "2.4.1",
"requirejs": "2.1.11",
"URIjs": "1.13.1",
"mousetrap": "1.4.6",
"lunr.js": "0.5.2",
"ace-builds": "1.1.3"
}
}
+41 -19
View File
@@ -7,6 +7,7 @@ var exec = require('child_process').exec;
var fs = require("fs");
var parse = require("../../parse");
var BaseGenerator = require("../page");
var stringUtils = require("../../utils/string");
/*
* This generator inherits from the single page generator
@@ -14,15 +15,6 @@ var BaseGenerator = require("../page");
*/
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Options for eBook generation
this.options = _.defaults(this.options, {
extension: "epub"
});
if (!this.options.cover && fs.existsSync(path.join(this.options.input, "cover.png"))) {
this.options.cover = path.join(this.options.input, "cover.png")
}
};
util.inherits(Generator, BaseGenerator);
@@ -32,23 +24,53 @@ Generator.prototype.finish = function() {
return BaseGenerator.prototype.finish.apply(this)
.then(function() {
var d = Q.defer();
var format = that.options.extension || path.extname(that.options.output).replace("\.", "") || "pdf";
if (!that.options.cover && fs.existsSync(path.join(that.options.output, "cover.jpg"))) {
that.options.cover = path.join(that.options.output, "cover.jpg");
}
var _options = {
"--cover": that.options.cover
"--cover": that.options.cover,
"--title": that.options.title,
"--comments": that.options.description,
"--authors": that.options.author,
"--level1-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-1 ')]",
"--level2-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-2 ')]",
"--level3-toc": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter-3 ')]"
};
if (format == "pdf") {
var pdfOptions = _.defaults(that.options.pdf || {}, {
"fontSize": 12,
"toc": true,
"pageNumbers": false,
"paperSize": "a4",
"margin": {
"right": 62,
"left": 62,
"top": 36,
"bottom": 36
}
});
_.extend(_options, {
"--margin-left": String(pdfOptions.margin.left),
"--margin-right": String(pdfOptions.margin.right),
"--margin-top": String(pdfOptions.margin.top),
"--margin-bottom": String(pdfOptions.margin.bottom),
"--pdf-add-toc": Boolean(pdfOptions.toc),
"--pdf-default-font-size": String(pdfOptions.fontSize),
"--paper-size": String(pdfOptions.paperSize),
"--pdf-page-numbers": Boolean(pdfOptions.pageNumbers)
});
}
var command = [
"ebook-convert",
path.join(that.options.output, "index.html"),
path.join(that.options.output, "index."+that.options.extension),
_.chain(_options)
.map(function(value, key) {
if (value == null) return null;
return key+"="+value;
})
.compact()
.value()
.join(" ")
path.join(that.options.output, "index."+format),
stringUtils.optionsToShellArgs(_options)
].join(" ");
exec(command, function (error, stdout, stderr) {
+1
View File
@@ -18,6 +18,7 @@ var getFiles = function(path) {
ig.addIgnoreRules([
'.git/',
'.gitignore',
'.DS_Store'
], '__custom_stuff');
// Push each file to our list
+4 -3
View File
@@ -9,17 +9,18 @@ var BaseGenerator = function(options) {
this.options = options;
this.options.plugins = Plugin.normalizeNames(this.options.plugins);
this.options.plugins = _.union(this.options.plugins, this.options.defaultsPlugins);
this.plugins = [];
};
BaseGenerator.prototype.callHook = function(name) {
return this.plugins.hook(name, this);
BaseGenerator.prototype.callHook = function(name, data) {
return this.plugins.hook(name, this, data);
};
BaseGenerator.prototype.loadPlugins = function() {
var that = this;
return Plugin.fromList(this.options.plugins)
return Plugin.fromList(this.options.plugins, this.options.input)
.then(function(_plugins) {
that.plugins = _plugins;
+255 -119
View File
@@ -1,9 +1,9 @@
var Q = require("q");
var _ = require("lodash");
var path = require("path");
var swig = require('swig');
var tmp = require('tmp');
var swig = require('./template');
var fs = require("./fs");
var parse = require("../parse");
var Plugin = require("./plugin");
@@ -11,57 +11,233 @@ var Plugin = require("./plugin");
var generators = {
"site": require("./site"),
"page": require("./page"),
"pdf": require("./pdf"),
"ebook": require("./ebook"),
"json": require("./json")
};
var defaultDescription = "Book generated using GitBook";
/*
* Use a specific generator to convert a gitbook to a site/pdf/ebook/
* output is always a folder
*/
var generate = function(options) {
var generator = null;
var files;
var containsFiles = function(dir, files) {
return Q.all(_.map(files, function(file) {
return fs.exists(path.join(dir, file));
}))
.then(_.all);
};
// TEst if generator exists
var checkGenerator = function(options) {
if (!generators[options.generator]) {
return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", ")+")"));
}
return Q();
};
// Create the generator and load plugins
var loadGenerator = function(options) {
return checkGenerator(options)
.then(function() {
var generator = new generators[options.generator](options);
return generator.loadPlugins()
.then(_.constant(generator));
});
};
var generate = function(options) {
// Set defaults to options
options = _.defaults(options || {}, {
// Folders to use
input: null,
output: null,
"input": null,
"output": null,
// Config file (relative to input)
"configFile": "book",
// Output generator
generator: "site",
"generator": "site",
// Book title, keyword, description
title: null,
description: null,
"title": null,
"description": null,
// Origin github repository id
github: null,
githubHost: 'https://github.com/',
"github": null,
"githubHost": 'https://github.com/',
// Theming
theme: path.resolve(__dirname, '../../theme'),
"theme": path.resolve(__dirname, '../../theme'),
// Plugins
plugins: [],
pluginsConfig: {}
"plugins": [],
"pluginsConfig": {},
"defaultsPlugins": [], // Default plugins that can't be set in book.json
// Links
"links": {
"about": null,
"issues": null,
"contribue": null,
}
});
// Validate options
if (!options.input) {
return Q.reject(new Error("Need option input (book input directory)"));
}
if (!generators[options.generator]) {
return Q.reject(new Error("Invalid generator (availables are: "+_.keys(generators).join(", ")));
}
// Check files to get folder type (book, multilanguage book or neither)
return checkGenerator(options)
// Read config file
.then(function() {
try {
var _config = require(path.resolve(options.input, options.configFile));
_.extend(options, _.omit(_config, 'input', 'configFile', 'defaultsPlugins'));
}
catch(err) {
// No config file: not a big deal
return Q();
}
})
// Read readme
.then(function() {
return fs.readFile(path.join(options.input, "README.md"), "utf-8")
.then(function(_readme) {
_readme = parse.readme(_readme);
options.title = options.title || _readme.title;
options.description = options.description || _readme.description || defaultDescription;
});
})
// Detect multi-languages book
.then(function() {
return containsFiles(options.input, ['LANGS.md'])
})
.then(function(isMultiLang) {
// Multi language book
if(isMultiLang) {
return generateMultiLang(options);
}
// Book
return generateBook(options);
});
};
var generateMultiLang = function(options) {
var langsSummary;
options.output = options.output || path.join(options.input, "_book");
return checkGenerator(options)
// Multi-languages book
.then(function() {
return fs.readFile(path.join(options.input, "LANGS.md"), "utf-8")
})
// Clean output folder
.then(function(_langsSummary) {
langsSummary = _langsSummary;
return fs.remove(options.output);
})
.then(function() {
return fs.mkdirp(options.output);
})
// Generate sub-books
.then(function() {
options.langsSummary = parse.langs(langsSummary);
// Generated a book for each valid entry
return _.reduce(options.langsSummary.list, function(prev, entry) {
return prev.then(function() {
return generate(_.extend({}, options, {
input: path.join(options.input, entry.path),
output: path.join(options.output, entry.path)
}));
})
}, Q());
})
.then(function() {
return loadGenerator(options);
})
// Generate languages index
.then(function(generator) {
return generator.langsIndex(options.langsSummary);
})
// Copy cover file
.then(function() {
return Q.all([
fs.copy(path.join(options.input, "cover.jpg"), path.join(options.output, "cover.jpg")),
fs.copy(path.join(options.input, "cover_small.jpg"), path.join(options.output, "cover_small.jpg"))
])
.fail(function() {
return Q();
})
})
// Return options to caller
.then(_.constant(options));
};
/*
* Use a specific generator to convert a gitbook to a site/pdf/ebook/
* output is always a folder
*/
var generateBook = function(options) {
var files;
options.output = options.output || path.join(options.input, "_book");
// Check if it's a book
return containsFiles(options.input, ['SUMMARY.md', 'README.md'])
// Fail if not a book
.then(function(isBook) {
if(!isBook) {
return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
}
})
// Clean output folder
return fs.remove(options.output)
.then(function() {
return fs.remove(options.output);
})
// Get repo's URL
.then(function() {
// Git deactivated
if(options.github === false) {
options.github = null;
return null;
} else if(options.github) {
// Git already specified in options
return options.github;
}
// Try auto detecting
return parse.git.url(options.input)
.then(function(_url) {
// Get ID of repo
return parse.git.githubID(_url);
}, function(err) {
return null;
})
.then(function(repoId) {
options.github = repoId;
});
})
.then(function() {
return fs.mkdirp(options.output);
@@ -72,108 +248,67 @@ var generate = function(options) {
return fs.list(options.input)
.then(function(_files) {
files = _files;
});
})
.then(function() {
return loadGenerator(options);
})
// Convert files
.then(function(generator) {
// Generate the book
return Q()
// Get summary
.then(function() {
return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
.then(function(_summary) {
options.summary = parse.summary(_summary);
// Parse navigation
options.navigation = parse.navigation(options.summary);
});
})
})
// Create the generator and load plugins
.then(function() {
generator = new generators[options.generator](options);
return generator.loadPlugins();
})
// Detect multi-languages book
.then(function() {
if (_.contains(files, "LANGS.md")) {
// Multi-languages book
return fs.readFile(path.join(options.input, "LANGS.md"), "utf-8")
// Generate sub-books
.then(function(_langsSummary) {
options.langsSummary = parse.langs(_langsSummary);
// Generated a book for each valid entry
return Q.all(
_.map(options.langsSummary.list, function(entry) {
return generate(_.extend({}, options, {
input: path.join(options.input, entry.path),
output: path.join(options.output, entry.path)
}));
})
// Skip processing some files
.then(function() {
files = _.filter(files, function (file) {
return !(
file === 'SUMMARY.md'
);
})
// Generate languages index
.then(function() {
return generator.langsIndex(options.langsSummary);
});
} else if (!_.contains(files, "SUMMARY.md") || !_.contains(files, "README.md")) {
// Invalid book
return Q.reject(new Error("Invalid gitbook repository, need SUMMARY.md and README.md"));
} else {
// Generate the book
return Q()
})
// Read readme
.then(function() {
return fs.readFile(path.join(options.input, "README.md"), "utf-8")
.then(function(_readme) {
_readme = parse.readme(_readme);
// Copy file and replace markdown file
.then(function() {
return Q.all(
_.chain(files)
.map(function(file) {
if (!file) return;
options.title = options.title || _readme.title;
options.description = options.description || _readme.description || defaultDescription;
});
})
if (file[file.length -1] == "/") {
return Q(generator.transferFolder(file));
} else if (path.extname(file) == ".md" && options.navigation[file] != null) {
return fs.readFile(path.join(options.input, file), "utf-8")
.then(function(content) {
return Q(generator.convertFile(content, file));
});
} else {
return Q(generator.transferFile(file));
}
})
.value()
);
})
// Get summary
.then(function() {
return fs.readFile(path.join(options.input, "SUMMARY.md"), "utf-8")
.then(function(_summary) {
options.summary = parse.summary(_summary);
// Parse navigation
options.navigation = parse.navigation(options.summary);
});
})
// Skip processing some files
.then(function() {
files = _.filter(files, function (file) {
return !(
file === 'SUMMARY.md'
);
});
})
// Copy file and replace markdown file
.then(function() {
return Q.all(
_.chain(files)
.map(function(file) {
if (!file) return;
if (file[file.length -1] == "/") {
return Q(generator.transferFolder(file));
} else if (path.extname(file) == ".md" && options.navigation[file] != null) {
return fs.readFile(path.join(options.input, file), "utf-8")
.then(function(content) {
return Q(generator.convertFile(content, file));
});
} else {
return Q(generator.transferFile(file));
}
})
.value()
);
})
// Finish gneration
.then(function() {
return generator.finish();
})
.then(function() {
return generator.callHook("finish");
});
}
// Finish generation
.then(function() {
return generator.finish();
})
.then(function() {
return generator.callHook("finish");
});
})
// Return all options
@@ -244,5 +379,6 @@ module.exports = {
generators: generators,
folder: generate,
file: generateFile,
Plugin: Plugin
book: generateBook,
Plugin: Plugin,
};
+30
View File
@@ -1,6 +1,7 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var fs = require("../fs");
var parse = require("../../parse");
@@ -40,6 +41,35 @@ Generator.prototype.convertFile = function(content, input) {
});
};
// Generate languages index
// Contains the first languages readme and langs infos
Generator.prototype.langsIndex = function(langs) {
var that = this;
if (langs.list.length == 0) return Q.reject("Need at least one language");
var mainLang = _.first(langs.list).lang;
console.log("Main language is", mainLang);
return Q()
.then(function() {
return fs.readFile(
path.join(that.options.output, mainLang, "README.json")
);
})
.then(function(content) {
var json = JSON.parse(content);
_.extend(json, {
langs: langs.list
});
return fs.writeFile(
path.join(that.options.output, "README.json"),
JSON.stringify(json, null, 4)
);
});
};
Generator.prototype.finish = function() {
// ignore
};
+72
View File
@@ -0,0 +1,72 @@
var _ = require('lodash');
var path = require('path');
var Q = require('q');
var fs = require("./fs");
var extsToIgnore = [".gz"]
var Manifest = function() {
this.revision = 0;
this.clear(Date.now());
};
// Regenerate manifest
Manifest.prototype.clear = function(revision) {
if (revision) this.revision = revision;
this.sections = {
'CACHE': {},
'NETWORK': {},
'FALLBACK': {}
};
return Q(this);
};
// Add a resource
Manifest.prototype.add = function(category, resource, value) {
if (_.isArray(resource)) {
_.each(resource, function(subres) {
this.add(category, subres, value);
}, this);
return;
}
this.sections[category][resource] = value;
};
// Add a directory in cache
Manifest.prototype.addFolder = function(folder, root, except) {
var that = this;
root = root || "/";
return fs.list(folder)
.then(function(files) {
_.each(
// Ignore diretcories
_.filter(files, function(file) {
return file.substr(-1) != "/" && !_.contains(except, path.join(root, file)) && !_.contains(extsToIgnore, path.extname(file));
}),
function(file) {
that.add("CACHE", path.join(root, file));
}
);
})
};
// Get manifest content
Manifest.prototype.dump = function() {
var lines = [
"CACHE MANIFEST",
"# Revision "+this.revision
];
_.each(this.sections, function(content, section) {
if (_.size(content) == 0) return;
lines.push("");
lines.push(section+":");
lines = lines.concat(_.keys(content));
}, this);
return Q(lines.join("\n"));
};
module.exports = Manifest;
+9 -11
View File
@@ -2,22 +2,12 @@ var _ = require("lodash");
var util = require("util");
var path = require("path");
var Q = require("q");
var swig = require('swig');
var hljs = require('highlight.js');
var swig = require("../template");
var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../site");
// Swig filter: highlight coloration
swig.setFilter('code', function(code, lang) {
try {
return hljs.highlight(lang, code).value;
} catch(e) {
return hljs.highlightAuto(code).value;
}
});
/*
* This generator will generate a simple index.html which can be converted as a PDF
@@ -61,16 +51,24 @@ Generator.prototype.convertFile = function(content, input) {
});
};
// Generate languages index
Generator.prototype.langsIndex = function(langs) {
return Q();
};
Generator.prototype.finish = function() {
var that = this;
var basePath = ".";
var output = path.join(this.options.output, "index.html");
var progress = parse.progress(this.options.navigation, "README.md");
return Q()
// Generate html
.then(function(pages) {
return that._writeTemplate(that.template, {
pages: that.pages,
progress: progress,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
-56
View File
@@ -1,56 +0,0 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var exec = require('child_process').exec;
var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../page");
/*
* This generator inherits from the single page generator
* and convert the page output to pdf using gitbook-pdf
*/
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Options for PDF generation
this.options = _.defaults(this.options, {
paperformat: "A4"
});
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.finish = function() {
var that = this;
return BaseGenerator.prototype.finish.apply(this)
.then(function() {
var d = Q.defer();
var command = [
"gitbook-pdf",
"generate",
path.join(that.options.output, "index.html"),
path.join(that.options.output, "index.pdf"),
"--format="+that.options.paperformat
].join(" ");
exec(command, function (error, stdout, stderr) {
if (error) {
if (error.code == 127) {
error.message = "Need to install gitbook-pdf using: npm install gitbook-pdf -g";
} else {
error.message = error.message + " "+stdout;
}
return d.reject(error);
}
d.resolve();
});
return d.promise;
});
};
module.exports = Generator;
+48 -21
View File
@@ -4,13 +4,15 @@ var semver = require("semver");
var path = require("path");
var url = require("url");
var fs = require("./fs");
var resolve = require('resolve');
var pkg = require("../../package.json");
var RESOURCES = ["js", "css"];
var Plugin = function(name) {
var Plugin = function(name, root) {
this.name = name;
this.root = root;
this.packageInfos = {};
this.infos = {};
@@ -20,16 +22,21 @@ var Plugin = function(name) {
"gitbook-"+name,
name,
], function(_name) {
if (this.load(_name)) return false;
}.bind(this));
if (this.load(_name, __dirname)) return false;
if (this.load(_name, path.resolve(root))) return false;
}, this);
};
// Load from a name
Plugin.prototype.load = function(name) {
Plugin.prototype.load = function(name, baseDir) {
try {
this.packageInfos = require(name+"/package.json");
this.infos = require(name);
var res = resolve.sync(name+"/package.json", { basedir: baseDir });
this.baseDir = path.dirname(res);
this.packageInfos = require(res);
this.infos = require(resolve.sync(name, { basedir: baseDir }));
this.name = name;
return true;
} catch (e) {
return false;
@@ -63,25 +70,30 @@ Plugin.prototype.isValid = function() {
// Resolve file path
Plugin.prototype.resolveFile = function(filename) {
return path.resolve(path.dirname(require.resolve(this.name)), filename);
return path.resolve(this.baseDir, filename);
};
// Resolve file path
Plugin.prototype.callHook = function(name, args) {
Plugin.prototype.callHook = function(name, context, data) {
var hookFunc = this.infos.hooks? this.infos.hooks[name] : null;
args = _.isArray(args) ? args : [args];
data = data || {};
if (!hookFunc) return Q();
if (!hookFunc) return Q(data);
return Q()
.then(function() {
return hookFunc.apply(null, args);
return hookFunc.apply(context, [data]);
});
};
// Has assets
Plugin.prototype.hasAssets = function(out) {
return (this.infos.book && this.infos.book.assets);
};
// Copy plugin assets fodler
Plugin.prototype.copyAssets = function(out) {
if (!this.infos.book || !this.infos.book.assets) return Q();
if (!this.hasAssets()) return Q();
return fs.copy(
this.resolveFile(this.infos.book.assets),
out
@@ -94,7 +106,7 @@ Plugin.prototype.copyAssets = function(out) {
// Normalize a list of plugin name to use
Plugin.normalizeNames = function(names) {
// Normalize list to an array
names = _.isString(names) ? names.split(":") : (names || []);
names = _.isString(names) ? names.split(",") : (names || []);
// List plugins to remove
var toremove = _.chain(names)
@@ -121,12 +133,12 @@ Plugin.normalizeNames = function(names) {
};
// Extract data from a list of plugin
Plugin.fromList = function(names) {
Plugin.fromList = function(names, root) {
var failed = [];
// Load plugins
var plugins = _.map(names, function(name) {
var plugin = new Plugin(name);
var plugin = new Plugin(name, root);
if (!plugin.isValid()) failed.push(name);
return plugin;
});
@@ -148,16 +160,27 @@ Plugin.fromList = function(names) {
})
.object()
.value();
resources.html = {}
_.each(plugins, function(plugin) {
if (!plugin.infos.book || !plugin.infos.book.html) return;
var html = plugin.infos.book.html || {};
_.each(html, function(code, key) {
if (!_.isFunction(code)) code = _.constant(code);
if (!resources.html[key]) resources.html[key] = [];
resources.html[key].push(code);
})
});
return Q({
'list': plugins,
'resources': resources,
'hook': function(name, args) {
'hook': function(name, context, data) {
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function() {
return plugin.callHook(name, args);
return prev.then(function(ret) {
return plugin.callHook(name, context, ret);
})
}, Q());
}, Q(data));
},
'template': function(name) {
var withTpl = _.find(plugins, function(plugin) {
@@ -167,14 +190,18 @@ Plugin.fromList = function(names) {
if (!withTpl) return null;
return withTpl.resolveFile(withTpl.infos.templates[name]);
},
'html': function(tag, context, options) {
return _.map(resources.html[tag] || [], function(code) {
return code.call(context, options);
}).join("\n");
}
});
};
// Default plugins
Plugin.defaults = [
"mixpanel",
"mathjax"
];
module.exports = Plugin;
module.exports = Plugin;
+79 -23
View File
@@ -2,25 +2,16 @@ var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var swig = require('swig');
var swig = require("../template");
var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../generator");
var links = require("../../utils/links");
var indexer = require('./search_indexer');
var Manifest = require('../manifest');
// Swig filter for returning the count of lines in a code section
swig.setFilter('lines', function(content) {
return content.split('\n').length;
});
// Swig filter for returning a link to the associated html file of a markdown file
swig.setFilter('mdLink', function(link) {
var link = link.replace(".md", ".html");
if (link == "README.html") link = "index.html";
return link;
});
var Generator = function() {
BaseGenerator.apply(this, arguments);
@@ -30,6 +21,10 @@ var Generator = function() {
this.revision = Date.now();
this.indexer = indexer();
this.manifest = new Manifest(Date.now());
this.manifest.add("NETWORK", [
'*'
]);
};
util.inherits(Generator, BaseGenerator);
@@ -54,8 +49,10 @@ Generator.prototype.loadPlugins = function() {
};
// Generate a template
Generator.prototype._writeTemplate = function(tpl, options, output) {
Generator.prototype._writeTemplate = function(tpl, options, output, interpolate) {
var that = this;
interpolate = interpolate || _.identity;
return Q()
.then(function(sections) {
return tpl(_.extend({
@@ -72,9 +69,13 @@ Generator.prototype._writeTemplate = function(tpl, options, output) {
allNavigation: that.options.navigation,
plugins: that.plugins,
pluginsConfig: JSON.stringify(that.options.pluginsConfig)
pluginsConfig: JSON.stringify(that.options.pluginsConfig),
htmlSnippet: _.partialRight(that.plugins.html, that, options),
options: that.options
}, options));
})
.then(interpolate)
.then(function(html) {
return fs.writeFile(
output,
@@ -91,7 +92,6 @@ Generator.prototype.indexPage = function(lexed, pagePath) {
// Convert a markdown file to html
Generator.prototype.convertFile = function(content, _input) {
var that = this;
var progress = parse.progress(this.options.navigation, _input);
_output = _input.replace(".md", ".html");
if (_output == "README.html") _output = "index.html";
@@ -100,10 +100,28 @@ Generator.prototype.convertFile = function(content, _input) {
var output = path.join(this.options.output, _output);
var basePath = path.relative(path.dirname(output), this.options.output) || ".";
var page = {
path: _input,
content: content,
progress: parse.progress(this.options.navigation, _input)
};
var _callHook = function(name) {
return that.callHook(name, page)
.then(function(_page) {
page = _page;
return page;
});
};
return Q()
.then(function() {
// Send content to plugins
return _callHook("page:before");
})
.then(function() {
// Lex page
return parse.lex(content);
return parse.lex(page.content);
})
.then(function(lexed) {
// Index page in search
@@ -119,15 +137,27 @@ Generator.prototype.convertFile = function(content, _input) {
});
})
.then(function(sections) {
page.sections = sections;
// Use plugin hook
return _callHook("page");
})
.then(function() {
that.manifest.add("CACHE", _output);
return that._writeTemplate(that.template, {
progress: progress,
progress: page.progress,
_input: _input,
content: sections,
content: page.sections,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
}, output);
staticBase: links.join(basePath, "gitbook"),
}, output, function(html) {
page.content = html;
return _callHook("page:after").get("content")
});
});
};
@@ -157,11 +187,23 @@ Generator.prototype.copyAssets = function() {
path.join(that.options.theme, "assets"),
path.join(that.options.output, "gitbook")
)
// Add to cach manifest
.then(function() {
return that.manifest.addFolder(path.join(that.options.output, "gitbook"), "gitbook");
})
// Copy plugins assets
.then(function() {
return Q.all(
_.map(that.plugins.list, function(plugin) {
return plugin.copyAssets(path.join(that.options.output, "gitbook/plugins/", plugin.name))
if (!plugin.hasAssets()) return Q();
var pluginAssets = path.join(that.options.output, "gitbook/plugins/", plugin.name);
return plugin.copyAssets(pluginAssets)
.then(function() {
return that.manifest.addFolder(pluginAssets, "gitbook/plugins/"+plugin.name);
});
})
);
})
@@ -175,9 +217,23 @@ Generator.prototype.writeSearchIndex = function() {
);
};
// Add cache manifest
Generator.prototype.writeCacheManifest = function() {
return fs.writeFile(
path.join(this.options.output, 'manifest.appcache'),
this.manifest.dump()
);
};
Generator.prototype.finish = function() {
return this.copyAssets()
.then(this.writeSearchIndex);
var deferred = this.copyAssets().then(this.writeSearchIndex);
if (this.options.cache !== false) {
deferred = deferred.then(this.writeCacheManifest);
}
return deferred;
};
module.exports = Generator;
+47
View File
@@ -0,0 +1,47 @@
var path = require("path");
var swig = require('swig');
var hljs = require('highlight.js');
var pkg = require('../../package.json');
swig.setDefaults({
locals: {
gitbook: {
version: pkg.version
}
}
});
// Swig filter for returning the count of lines in a code section
swig.setFilter('lines', function(content) {
return content.split('\n').length;
});
// Swig filter for returning a link to the associated html file of a markdown file
swig.setFilter('mdLink', function(link) {
var link = link.replace(".md", ".html");
if (link == "README.html") link = "index.html";
return link;
});
// Swig filter: highlight coloration
swig.setFilter('code', function(code, lang) {
try {
return hljs.highlight(lang, code).value;
} catch(e) {
return hljs.highlightAuto(code).value;
}
});
// Convert a level into a deep level
swig.setFilter('lvl', function(lvl) {
return lvl.split(".").length;
});
// Join path
swig.setFilter('pathJoin', function(base, _path) {
return path.join(base, _path);
});
module.exports = swig;
+56
View File
@@ -0,0 +1,56 @@
var Q = require('q');
var _ = require('lodash');
var cp = require('child_process');
var url = require('url');
// Get the remote of a given repo
function gitURL(path) {
var d = Q.defer();
cp.exec("git config --get remote.origin.url", {
cwd: path,
env: process.env,
}, function(err, stdout, stderr) {
if(err) {
return d.reject(err);
}
return d.resolve(stdout);
});
return d.promise
.then(function(output) {
return output.replace(/(\r\n|\n|\r)/gm, "");
});
}
// Poorman's parsing
// Parse a git URL to a github ID : username/reponame
function githubID(_url) {
// Remove .git if it's in _url
var sliceEnd = _url.slice(-4) === '.git' ? -4 : _url.length;
// Detect HTTPS repos
var parsed = url.parse(_url);
if(parsed.protocol === 'https:' && parsed.host === 'github.com') {
return parsed.path.slice(1, sliceEnd);
}
// Detect SSH repos
if(_url.indexOf('git@') === 0) {
return _url.split(':', 2)[1].slice(0, sliceEnd);
}
// None found
return null;
}
function titleCase(str) {
return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}
module.exports = {
url: gitURL,
githubID: githubID,
titleCase: titleCase
};
+2 -1
View File
@@ -5,5 +5,6 @@ module.exports = {
lex: require('./lex'),
progress: require('./progress'),
navigation: require('./navigation'),
readme: require('./readme')
readme: require('./readme'),
git: require('./git')
};
+1 -1
View File
@@ -8,7 +8,7 @@ function clean(obj) {
function flattenChapters(chapters) {
return _.reduce(chapters, function(accu, chapter) {
return accu.concat([clean(chapter)].concat(chapter.articles));
return accu.concat([clean(chapter)].concat(flattenChapters(chapter.articles)));
}, []);
}
+2 -2
View File
@@ -12,7 +12,7 @@ var calculProgress = function(navigation, current) {
return nav;
})
.sortBy(function(nav) {
return nav.level;
return parseFloat(nav.level);
})
.map(function(nav, i) {
// Calcul percent
@@ -27,7 +27,7 @@ var calculProgress = function(navigation, current) {
} else if (done) {
prevPercent = nav.percent;
}
return nav;
})
.value();
+18 -5
View File
@@ -1,5 +1,6 @@
var _ = require('lodash');
var marked = require('marked');
var textRenderer = require('marked-text-renderer');
function extractFirstNode(nodes, nType) {
return _.chain(nodes)
@@ -14,16 +15,28 @@ function extractFirstNode(nodes, nType) {
function parseReadme(src) {
var nodes, title, description;
var renderer = textRenderer();
// Parse content
nodes = marked.lexer(src);
var title = extractFirstNode(nodes, "heading");
var description = extractFirstNode(nodes, "paragraph");
title = extractFirstNode(nodes, "heading") || '';
description = extractFirstNode(nodes, "paragraph") || '';
var convert = _.compose(
function(text) {
return _.unescape(text.replace(/(\r\n|\n|\r)/gm, ""));
},
function(text) {
return marked.parse(text, _.extend({}, marked.defaults, {
renderer: renderer
}));
}
);
return {
title: title,
description: description
title: convert(title),
description: convert(description)
};
}
+8 -3
View File
@@ -18,6 +18,7 @@ function GitBookRenderer(options, extra_options) {
this._extra_options = extra_options;
this.quizRowId = 0;
this.id = rendererId++;
this.quizIndex = 0;
}
inherits(GitBookRenderer, marked.Renderer);
@@ -66,7 +67,7 @@ GitBookRenderer.prototype.link = function(href, title, text) {
} else if (o && o.repo && o.dir) {
href = url.resolve('https://github.com/' + o.repo + '/blob/', [o.dir, _href].join("/"));
parsed = url.parse(href);
_href = parsed.href;
_href = parsed.href;
}
}
@@ -116,10 +117,14 @@ GitBookRenderer.prototype._createCheckboxAndRadios = function(text) {
if (!match) {
return text;
}
var field = "<input name='quiz-row-" + this.id + "-" + this.quizRowId + "' type='";
var quizIdentifier = 'quiz-row-' + this.id + '-' + this.quizRowId + '-' + this.quizIndex++;
var field = "<input name='" + quizIdentifier + "' id='" + quizIdentifier + "' type='";
field += match[1] === '(' ? "radio" : "checkbox";
field += match[2] === 'x' ? "' checked/>" : "'/>";
return text.replace(fieldRegex, field);
var splittedText = text.split(fieldRegex);
var length = splittedText.length;
var label = '<label class="quiz-label" for="' + quizIdentifier + '">' + splittedText[length - 1] + '</label>';
return text.replace(fieldRegex, field).replace(splittedText[length - 1], label);
}
GitBookRenderer.prototype.tablecell = function(content, flags) {
+9 -9
View File
@@ -82,17 +82,15 @@ function parseTitle(src, nums) {
level: level,
// Replace .md references with .html
path: matches[2],
path: matches[2].replace(/\\/g, '/'),
};
}
function parseArticle(chapterNum, nodes, idx) {
return parseTitle(_.first(nodes).text, [chapterNum, idx+1]);
}
function parseChapter(nodes, idx) {
return _.extend(parseTitle(_.first(nodes).text, [idx+1]), {
articles: _.map(listSplit(filterList(nodes), 'list_item_start', 'list_item_end'), parseArticle.bind(null, idx+1))
function parseChapter(nodes, nums) {
return _.extend(parseTitle(_.first(nodes).text, nums), {
articles: _.map(listSplit(filterList(nodes), 'list_item_start', 'list_item_end'), function(nodes, i) {
return parseChapter(nodes, nums.concat(i + 1));
})
});
}
@@ -104,7 +102,9 @@ function parseSummary(src) {
// Split out chapter sections
var chapters = _.chain(listSplit(chapterList, 'list_item_start', 'list_item_end'))
.map(parseChapter)
.map(function(nodes, i) {
return parseChapter(nodes, [i + 1]);
})
.value();
return {
+18 -1
View File
@@ -19,11 +19,28 @@ var toAbsolute = function(_href, dir, outdir) {
// make it relative to output
_href = path.relative(outdir, _href);
if (process.platform === 'win32') {
_href = _href.replace(/\\/g, '/');
}
return _href;
};
// Join links
var join = function() {
var _href = path.join.apply(path, arguments);
if (process.platform === 'win32') {
_href = _href.replace(/\\/g, '/');
}
return _href;
};
module.exports = {
isRelative: isRelative,
toAbsolute: toAbsolute
toAbsolute: toAbsolute,
join: join
};
+26
View File
@@ -0,0 +1,26 @@
var _ = require("lodash");
function escapeShellArg(arg) {
var ret = '';
ret = arg.replace(/"/g, '\\"');
return "\"" + ret + "\"";
}
function optionsToShellArgs(options) {
return _.chain(options)
.map(function(value, key) {
if (value == null || value == false) return null;
if (value == true) return key;
return key+"="+escapeShellArg(value);
})
.compact()
.value()
.join(" ");
}
module.exports = {
escapeShellArg: escapeShellArg,
optionsToShellArgs: optionsToShellArgs
};
+8 -5
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.3.1",
"version": "0.5.1",
"homepage": "http://www.gitbook.io/",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
@@ -18,17 +18,20 @@
"highlight.js": "8.0.0",
"tmp": "0.0.23",
"semver": "2.2.1",
"gaze": "~0.5.1",
"resolve": "0.6.3",
"tiny-lr-fork": "0.0.5",
"gitbook-plugin": "0.0.2",
"gitbook-plugin-mixpanel": "0.0.2",
"gitbook-plugin-mathjax": "0.0.3"
"gitbook-plugin-mathjax": "0.0.3",
"gitbook-plugin-livereload": "0.0.1"
},
"devDependencies": {
"mocha": "1.18.2",
"grunt": "~0.4.2",
"grunt-cli": "0.1.11",
"grunt-contrib-less": "~0.5.0",
"grunt-contrib-requirejs": "0.4.1"
"grunt-contrib-requirejs": "0.4.1",
"grunt-bower-install-simple": "0.9.2"
},
"scripts": {
"test": "export TESTING=true; mocha --reporter list"
+2
View File
@@ -3,6 +3,8 @@
* [Chapter 1](chapter-1/README.md)
* [Article 1](chapter-1/ARTICLE1.md)
* [Article 2](chapter-1/ARTICLE2.md)
* [article 1.2.1](chapter-1/ARTICLE-1-2-1.md)
* [article 1.2.2](chapter-1/ARTICLE-1-2-2.md)
* [Chapter 2](chapter-2/README.md)
* [Chapter 3](chapter-3/README.md)
* [Chapter 4](chapter-4/README.md)
+2
View File
@@ -0,0 +1,2 @@
# This is a test
+3
View File
@@ -0,0 +1,3 @@
# Summary
* [test](test.md)
+1
View File
@@ -0,0 +1 @@
# Test
+1
View File
@@ -0,0 +1 @@
# This should fail
+46
View File
@@ -0,0 +1,46 @@
var _ = require('lodash');
var Q = require('q');
var path = require('path');
var tmp = require('tmp');
var assert = require('assert');
var generate = require("../lib/generate");
var generateTmpBook = function(path) {
return ;
};
var BOOKS = {
"book1": true,
"book2": false
};
describe('Site Generation', function () {
var ret = {};
beforeEach(function(done){
Q.all(_.map(BOOKS, function(state, bookName) {
return Q.nfcall(tmp.dir)
.then(function(_dir) {
return generate.folder({
input: path.join(__dirname, "fixtures", bookName),
output: _dir
});
})
.then(function(_book) {
ret[bookName] = _book;
}, function(err) {
// ignore errors here
});
}))
.fin(done);
});
it('should generate the valid sites', function() {
_.each(BOOKS, function(state, bookName) {
assert((ret[bookName] != null) == state);
});
});
});
+12 -2
View File
@@ -18,6 +18,8 @@ describe('Summary navigation', function() {
'chapter-1/ARTICLE1.md',
'chapter-1/ARTICLE2.md',
'chapter-2/README.md',
'chapter-1/ARTICLE-1-2-1.md',
'chapter-1/ARTICLE-1-2-2.md'
]);
// Make sure it found the files we gave it
@@ -26,6 +28,8 @@ describe('Summary navigation', function() {
assert(nav['chapter-1/ARTICLE1.md']);
assert(nav['chapter-1/ARTICLE2.md']);
assert(nav['chapter-2/README.md']);
assert(nav['chapter-1/ARTICLE-1-2-1.md']);
assert(nav['chapter-1/ARTICLE-1-2-2.md']);
assert.equal(nav['README.md'].prev, null);
@@ -38,9 +42,15 @@ describe('Summary navigation', function() {
assert.equal(nav['chapter-1/ARTICLE1.md'].next.path, 'chapter-1/ARTICLE2.md');
assert.equal(nav['chapter-1/ARTICLE2.md'].prev.path, 'chapter-1/ARTICLE1.md');
assert.equal(nav['chapter-1/ARTICLE2.md'].next.path, 'chapter-2/README.md');
assert.equal(nav['chapter-1/ARTICLE2.md'].next.path, 'chapter-1/ARTICLE-1-2-1.md');
assert.equal(nav['chapter-2/README.md'].prev.path, 'chapter-1/ARTICLE2.md');
assert.equal(nav['chapter-1/ARTICLE-1-2-1.md'].prev.path, 'chapter-1/ARTICLE2.md');
assert.equal(nav['chapter-1/ARTICLE-1-2-1.md'].next.path, 'chapter-1/ARTICLE-1-2-2.md');
assert.equal(nav['chapter-1/ARTICLE-1-2-2.md'].prev.path, 'chapter-1/ARTICLE-1-2-1.md');
assert.equal(nav['chapter-1/ARTICLE-1-2-2.md'].next.path, 'chapter-2/README.md');
assert.equal(nav['chapter-2/README.md'].prev.path, 'chapter-1/ARTICLE-1-2-2.md');
assert.equal(nav['chapter-2/README.md'].next.path, 'chapter-3/README.md');
});
+2 -2
View File
@@ -5,7 +5,7 @@ var assert = require('assert');
var Plugin = require('../').generate.Plugin;
describe('Plugin validation', function () {
var plugin = new Plugin("plugin");
var plugin = new Plugin("plugin", __dirname);
it('should be valid', function() {
assert(plugin.isValid());
@@ -33,7 +33,7 @@ describe('Plugin defaults loading', function () {
var ret = true;
beforeEach(function(done){
Plugin.fromList(Plugin.defaults)
Plugin.fromList(Plugin.defaults, __dirname)
.then(function(_r) {
ret = _r;
}, function(err) {
+1
View File
@@ -50,5 +50,6 @@ describe('Summary parsing', function () {
assert.equal(c[0].articles[0].level, '1.1');
assert.equal(c[0].articles[1].level, '1.2');
assert.equal(c[0].articles[1].articles[0].level, '1.2.1');
});
});
+1 -1
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -1,9 +1,13 @@
define([
"jQuery",
"ace/ace",
"ace/theme-tomorrow",
"ace/mode-javascript",
"utils/execute",
"core/events",
"core/state"
], function($, execute, events, state){
], function($, _ace, _aceTheme, _aceMode, execute, events, state){
// Bind an exercise
var prepareExercise = function($exercise) {
var codeSolution = $exercise.find(".code-solution").text();
+41 -35
View File
@@ -4,6 +4,17 @@ define([
], function($, storage) {
var fontState;
var THEMES = {
"white": 0,
"sepia": 1,
"night": 2
};
var FAMILY = {
"serif": 0,
"sans": 1
};
var togglePopover = function(e) {
var $dropdown = $("#font-settings-wrapper .dropdown-menu");
@@ -11,7 +22,7 @@ define([
e.stopPropagation();
e.preventDefault();
};
var closePopover = function(e) {
var $dropdown = $("#font-settings-wrapper .dropdown-menu");
@@ -19,40 +30,23 @@ define([
};
var enlargeFontSize = function(e){
var $bookBody = $(".book-body");
if (fontState.size < 4){
$bookBody.toggleClass("font-size-"+fontState.size, false);
fontState.size++;
$bookBody.toggleClass("font-size-"+fontState.size, true);
fontState.save();
}
};
var reduceFontSize = function(e){
var $bookBody = $(".book-body");
if (fontState.size > 0){
$bookBody.toggleClass("font-size-"+fontState.size);
fontState.size--;
$bookBody.toggleClass("font-size-"+fontState.size);
fontState.save();
}
};
var changeFontFamily = function(){
var $bookBody = $(".book-body");
var index = $(this).data("font");
$bookBody.toggleClass("font-family-"+fontState.family);
$(".font-settings .font-family-list li:nth-child("+(fontState.family+1)+")")
.removeClass("active");
fontState.family = index;
$bookBody.toggleClass("font-family-"+fontState.family);
$(this).addClass("active");
fontState.save();
};
@@ -70,7 +64,23 @@ define([
fontState.save();
};
var init = function() {
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.
@@ -79,37 +89,33 @@ define([
$dropdown = $("#font-settings-wrapper .dropdown-menu");
$bookBody = $(".book-body");
//Instantiate font state object
// Instantiate font state object
fontState = storage.get("fontState", {
size:1,
family:0,
theme:0
size: config.size || 2,
family: FAMILY[config.family || "sans"],
theme: THEMES[config.theme || "white"]
});
fontState.save = function(){
storage.set("fontState",fontState);
update();
};
$bookBody.addClass("font-size-"+fontState.size);
$bookBody.addClass("font-family-"+fontState.family);
$(".font-settings .font-family-list li:nth-child("+(fontState.family+1)+")").addClass("active");
if(fontState.theme !== 0)
$book.addClass("color-theme-"+fontState.theme);
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 li", changeFontFamily);
$(document).on('click', "#font-settings-wrapper .color-theme-list button", changeColorTheme);
$(document).on('click', ".book-header .toggle-font-settings", togglePopover);
$(document).on('click', ".book-header .toggle-font-settings", togglePopover);
$(document).on('click', "#font-settings-wrapper .dropdown-menu", function(e){ e.stopPropagation(); });
$(document).on("click", closePopover);
};
return {
init: init
init: init,
update: update
}
});
+16
View File
@@ -0,0 +1,16 @@
define([
"jQuery"
], function($) {
var showLoading = function(p) {
$(".book").addClass("is-loading");
p.always(function() {
$(".book").removeClass("is-loading");
});
return p;
};
return {
show: showLoading
};
});
+26 -27
View File
@@ -1,38 +1,41 @@
define([
"jQuery",
"utils/url",
"core/events",
"core/state",
"core/search",
"core/progress",
"core/exercise",
"core/quiz"
], function($, events, state, search, progress, exercises, quiz) {
"core/quiz",
"core/loading"
], function($, URL, events, state, progress, exercises, quiz, loading) {
var prev, next;
var githubCountStars, githubCountWatch;
var usePushState = !navigator.userAgent.match('CriOS') && (typeof history.pushState !== "undefined");
var usePushState = (typeof history.pushState !== "undefined");
var updateHistory = function(url, title) {
history.pushState({ path: url }, title, url);
};
var handleNavigation = function(relativeUrl, push) {
var url = URL.join(window.location.pathname, relativeUrl);
console.log("navigate to ", url, "baseurl="+relativeUrl, "current="+window.location.pathname);
var handleNavigation = function(url, push) {
if (!usePushState) {
// Refresh the page to the new URL if pushState not supported
location.href = url;
location.href = relativeUrl;
return
}
return $.get(url)
return loading.show($.get(url)
.done(function (html) {
if (push) updateHistory(url, null);
// 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
var headContent = $pageHead.html()
@@ -42,22 +45,22 @@ define([
});
$("head").html(headContent);
// Update header, body
$('.book-header').html($page.find('.book-header').html());
$('.book-body').html($page.find('.book-body').html());
// Update summary
// Merge body
var bodyClass = $(".book").attr("class");
var scrollPosition = $('.book-summary .summary').scrollTop();
$('.book-summary').html($page.find('.book-summary').html());
$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 () {
location.href = url;
});
.fail(function (e) {
location.href = relativeUrl;
}));
};
var updateGitHubCounts = function() {
@@ -92,9 +95,6 @@ define([
// Focus on content
$pageWrapper.focus();
// Prepare search bar
search.prepare();
// Update GitHub count
if (state.githubId) {
if (githubCountStars) {
@@ -131,7 +131,7 @@ define([
if (url) handleNavigation(url, true);
};
var init = function() {
// Prevent cache so that using the back button works
@@ -148,14 +148,13 @@ define([
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
+5 -8
View File
@@ -67,12 +67,10 @@ define([
e.preventDefault();
toggleSearch();
});
};
var prepare = function() {
var $searchInput = $(".book-search input");
$searchInput.keyup(function(e) {
// Type in search bar
$(document).on("keyup", ".book-search input", function(e) {
var key = (e.keyCode ? e.keyCode : e.which);
var q = $(this).val();
@@ -89,13 +87,12 @@ define([
_.pluck(results, "path")
);
}
});
}
})
};
return {
init: init,
search: search,
toggle: toggleSearch,
prepare: prepare
toggle: toggleSearch
};
});
+4 -4
View File
@@ -1,11 +1,10 @@
define([
"jQuery",
"lodash",
"utils/storage",
"utils/platform",
"core/state"
], function(_, storage, platform, state) {
var $summary = state.$book.find(".book-summary");
], function($, _, storage, platform, state) {
// Toggle sidebar with or withour animation
var toggleSidebar = function(_state, animation) {
if (state != null && isOpen() == _state) return;
@@ -38,6 +37,8 @@ define([
// 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);
@@ -48,7 +49,6 @@ define([
};
return {
$el: $summary,
init: init,
toggle: toggleSidebar,
filter: filterSummary
+8 -4
View File
@@ -2,6 +2,7 @@ define([
"jQuery",
"utils/storage",
"utils/sharing",
"utils/appcache",
"core/events",
"core/font-settings",
@@ -11,7 +12,7 @@ define([
"core/progress",
"core/sidebar",
"core/search"
], function($, storage, sharing, events, fontSettings, state, keyboard, navigation, progress, sidebar, search){
], function($, storage, appCache, sharing, events, fontSettings, state, keyboard, navigation, progress, sidebar, search){
var start = function(config) {
var $book;
$book = state.$book;
@@ -30,14 +31,17 @@ define([
// Init keyboard
keyboard.init();
// Init appcache
appCache.init();
// Bind sharing button
sharing.init();
// Init navigation
navigation.init();
//Init font settings
fontSettings.init();
//Init font settings
fontSettings.init(config.fontSettings || {});
events.trigger("start", config);
}
+15
View File
@@ -0,0 +1,15 @@
define([], function() {
var isAvailable = (typeof applicationCache !== "undefined");
var init = function() {
if (!isAvailable) return;
window.applicationCache.addEventListener('updateready', function() {
window.location.reload();
}, false);
};
return {
init: init
};
});
+33
View File
@@ -0,0 +1,33 @@
define([
"vendors/URIjs/src/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
};
})
-9111
View File
File diff suppressed because it is too large Load Diff
-6785
View File
File diff suppressed because it is too large Load Diff
-953
View File
@@ -1,953 +0,0 @@
/*global define:false */
/**
* Copyright 2013 Craig Campbell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Mousetrap is a simple keyboard shortcut library for Javascript with
* no external dependencies
*
* @version 1.4.6
* @url craig.is/killing/mice
*/
(function(window, document, undefined) {
/**
* mapping of special keycodes to their corresponding keys
*
* everything in this dictionary cannot use keypress events
* so it has to be here to map to the correct keycodes for
* keyup/keydown events
*
* @type {Object}
*/
var _MAP = {
8: 'backspace',
9: 'tab',
13: 'enter',
16: 'shift',
17: 'ctrl',
18: 'alt',
20: 'capslock',
27: 'esc',
32: 'space',
33: 'pageup',
34: 'pagedown',
35: 'end',
36: 'home',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
45: 'ins',
46: 'del',
91: 'meta',
93: 'meta',
224: 'meta'
},
/**
* mapping for special characters so they can support
*
* this dictionary is only used incase you want to bind a
* keyup or keydown event to one of these keys
*
* @type {Object}
*/
_KEYCODE_MAP = {
106: '*',
107: '+',
109: '-',
110: '.',
111 : '/',
186: ';',
187: '=',
188: ',',
189: '-',
190: '.',
191: '/',
192: '`',
219: '[',
220: '\\',
221: ']',
222: '\''
},
/**
* this is a mapping of keys that require shift on a US keypad
* back to the non shift equivelents
*
* this is so you can use keyup events with these keys
*
* note that this will only work reliably on US keyboards
*
* @type {Object}
*/
_SHIFT_MAP = {
'~': '`',
'!': '1',
'@': '2',
'#': '3',
'$': '4',
'%': '5',
'^': '6',
'&': '7',
'*': '8',
'(': '9',
')': '0',
'_': '-',
'+': '=',
':': ';',
'\"': '\'',
'<': ',',
'>': '.',
'?': '/',
'|': '\\'
},
/**
* this is a list of special strings you can use to map
* to modifier keys when you specify your keyboard shortcuts
*
* @type {Object}
*/
_SPECIAL_ALIASES = {
'option': 'alt',
'command': 'meta',
'return': 'enter',
'escape': 'esc',
'mod': /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl'
},
/**
* variable to store the flipped version of _MAP from above
* needed to check if we should use keypress or not when no action
* is specified
*
* @type {Object|undefined}
*/
_REVERSE_MAP,
/**
* a list of all the callbacks setup via Mousetrap.bind()
*
* @type {Object}
*/
_callbacks = {},
/**
* direct map of string combinations to callbacks used for trigger()
*
* @type {Object}
*/
_directMap = {},
/**
* keeps track of what level each sequence is at since multiple
* sequences can start out with the same sequence
*
* @type {Object}
*/
_sequenceLevels = {},
/**
* variable to store the setTimeout call
*
* @type {null|number}
*/
_resetTimer,
/**
* temporary state where we will ignore the next keyup
*
* @type {boolean|string}
*/
_ignoreNextKeyup = false,
/**
* temporary state where we will ignore the next keypress
*
* @type {boolean}
*/
_ignoreNextKeypress = false,
/**
* are we currently inside of a sequence?
* type of action ("keyup" or "keydown" or "keypress") or false
*
* @type {boolean|string}
*/
_nextExpectedAction = false;
/**
* loop through the f keys, f1 to f19 and add them to the map
* programatically
*/
for (var i = 1; i < 20; ++i) {
_MAP[111 + i] = 'f' + i;
}
/**
* loop through to map numbers on the numeric keypad
*/
for (i = 0; i <= 9; ++i) {
_MAP[i + 96] = i;
}
/**
* cross browser add event method
*
* @param {Element|HTMLDocument} object
* @param {string} type
* @param {Function} callback
* @returns void
*/
function _addEvent(object, type, callback) {
if (object.addEventListener) {
object.addEventListener(type, callback, false);
return;
}
object.attachEvent('on' + type, callback);
}
/**
* takes the event and returns the key character
*
* @param {Event} e
* @return {string}
*/
function _characterFromEvent(e) {
// for keypress events we should return the character as is
if (e.type == 'keypress') {
var character = String.fromCharCode(e.which);
// if the shift key is not pressed then it is safe to assume
// that we want the character to be lowercase. this means if
// you accidentally have caps lock on then your key bindings
// will continue to work
//
// the only side effect that might not be desired is if you
// bind something like 'A' cause you want to trigger an
// event when capital A is pressed caps lock will no longer
// trigger the event. shift+a will though.
if (!e.shiftKey) {
character = character.toLowerCase();
}
return character;
}
// for non keypress events the special maps are needed
if (_MAP[e.which]) {
return _MAP[e.which];
}
if (_KEYCODE_MAP[e.which]) {
return _KEYCODE_MAP[e.which];
}
// if it is not in the special map
// with keydown and keyup events the character seems to always
// come in as an uppercase character whether you are pressing shift
// or not. we should make sure it is always lowercase for comparisons
return String.fromCharCode(e.which).toLowerCase();
}
/**
* checks if two arrays are equal
*
* @param {Array} modifiers1
* @param {Array} modifiers2
* @returns {boolean}
*/
function _modifiersMatch(modifiers1, modifiers2) {
return modifiers1.sort().join(',') === modifiers2.sort().join(',');
}
/**
* resets all sequence counters except for the ones passed in
*
* @param {Object} doNotReset
* @returns void
*/
function _resetSequences(doNotReset) {
doNotReset = doNotReset || {};
var activeSequences = false,
key;
for (key in _sequenceLevels) {
if (doNotReset[key]) {
activeSequences = true;
continue;
}
_sequenceLevels[key] = 0;
}
if (!activeSequences) {
_nextExpectedAction = false;
}
}
/**
* finds all callbacks that match based on the keycode, modifiers,
* and action
*
* @param {string} character
* @param {Array} modifiers
* @param {Event|Object} e
* @param {string=} sequenceName - name of the sequence we are looking for
* @param {string=} combination
* @param {number=} level
* @returns {Array}
*/
function _getMatches(character, modifiers, e, sequenceName, combination, level) {
var i,
callback,
matches = [],
action = e.type;
// if there are no events related to this keycode
if (!_callbacks[character]) {
return [];
}
// if a modifier key is coming up on its own we should allow it
if (action == 'keyup' && _isModifier(character)) {
modifiers = [character];
}
// loop through all callbacks for the key that was pressed
// and see if any of them match
for (i = 0; i < _callbacks[character].length; ++i) {
callback = _callbacks[character][i];
// if a sequence name is not specified, but this is a sequence at
// the wrong level then move onto the next match
if (!sequenceName && callback.seq && _sequenceLevels[callback.seq] != callback.level) {
continue;
}
// if the action we are looking for doesn't match the action we got
// then we should keep going
if (action != callback.action) {
continue;
}
// if this is a keypress event and the meta key and control key
// are not pressed that means that we need to only look at the
// character, otherwise check the modifiers as well
//
// chrome will not fire a keypress if meta or control is down
// safari will fire a keypress if meta or meta+shift is down
// firefox will fire a keypress if meta or control is down
if ((action == 'keypress' && !e.metaKey && !e.ctrlKey) || _modifiersMatch(modifiers, callback.modifiers)) {
// when you bind a combination or sequence a second time it
// should overwrite the first one. if a sequenceName or
// combination is specified in this call it does just that
//
// @todo make deleting its own method?
var deleteCombo = !sequenceName && callback.combo == combination;
var deleteSequence = sequenceName && callback.seq == sequenceName && callback.level == level;
if (deleteCombo || deleteSequence) {
_callbacks[character].splice(i, 1);
}
matches.push(callback);
}
}
return matches;
}
/**
* takes a key event and figures out what the modifiers are
*
* @param {Event} e
* @returns {Array}
*/
function _eventModifiers(e) {
var modifiers = [];
if (e.shiftKey) {
modifiers.push('shift');
}
if (e.altKey) {
modifiers.push('alt');
}
if (e.ctrlKey) {
modifiers.push('ctrl');
}
if (e.metaKey) {
modifiers.push('meta');
}
return modifiers;
}
/**
* prevents default for this event
*
* @param {Event} e
* @returns void
*/
function _preventDefault(e) {
if (e.preventDefault) {
e.preventDefault();
return;
}
e.returnValue = false;
}
/**
* stops propogation for this event
*
* @param {Event} e
* @returns void
*/
function _stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
return;
}
e.cancelBubble = true;
}
/**
* actually calls the callback function
*
* if your callback function returns false this will use the jquery
* convention - prevent default and stop propogation on the event
*
* @param {Function} callback
* @param {Event} e
* @returns void
*/
function _fireCallback(callback, e, combo, sequence) {
// if this event should not happen stop here
if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
return;
}
if (callback(e, combo) === false) {
_preventDefault(e);
_stopPropagation(e);
}
}
/**
* handles a character key event
*
* @param {string} character
* @param {Array} modifiers
* @param {Event} e
* @returns void
*/
function _handleKey(character, modifiers, e) {
var callbacks = _getMatches(character, modifiers, e),
i,
doNotReset = {},
maxLevel = 0,
processedSequenceCallback = false;
// Calculate the maxLevel for sequences so we can only execute the longest callback sequence
for (i = 0; i < callbacks.length; ++i) {
if (callbacks[i].seq) {
maxLevel = Math.max(maxLevel, callbacks[i].level);
}
}
// loop through matching callbacks for this key event
for (i = 0; i < callbacks.length; ++i) {
// fire for all sequence callbacks
// this is because if for example you have multiple sequences
// bound such as "g i" and "g t" they both need to fire the
// callback for matching g cause otherwise you can only ever
// match the first one
if (callbacks[i].seq) {
// only fire callbacks for the maxLevel to prevent
// subsequences from also firing
//
// for example 'a option b' should not cause 'option b' to fire
// even though 'option b' is part of the other sequence
//
// any sequences that do not match here will be discarded
// below by the _resetSequences call
if (callbacks[i].level != maxLevel) {
continue;
}
processedSequenceCallback = true;
// keep a list of which sequences were matches for later
doNotReset[callbacks[i].seq] = 1;
_fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
continue;
}
// if there were no sequence matches but we are still here
// that means this is a regular match so we should fire that
if (!processedSequenceCallback) {
_fireCallback(callbacks[i].callback, e, callbacks[i].combo);
}
}
// if the key you pressed matches the type of sequence without
// being a modifier (ie "keyup" or "keypress") then we should
// reset all sequences that were not matched by this event
//
// this is so, for example, if you have the sequence "h a t" and you
// type "h e a r t" it does not match. in this case the "e" will
// cause the sequence to reset
//
// modifier keys are ignored because you can have a sequence
// that contains modifiers such as "enter ctrl+space" and in most
// cases the modifier key will be pressed before the next key
//
// also if you have a sequence such as "ctrl+b a" then pressing the
// "b" key will trigger a "keypress" and a "keydown"
//
// the "keydown" is expected when there is a modifier, but the
// "keypress" ends up matching the _nextExpectedAction since it occurs
// after and that causes the sequence to reset
//
// we ignore keypresses in a sequence that directly follow a keydown
// for the same character
var ignoreThisKeypress = e.type == 'keypress' && _ignoreNextKeypress;
if (e.type == _nextExpectedAction && !_isModifier(character) && !ignoreThisKeypress) {
_resetSequences(doNotReset);
}
_ignoreNextKeypress = processedSequenceCallback && e.type == 'keydown';
}
/**
* handles a keydown event
*
* @param {Event} e
* @returns void
*/
function _handleKeyEvent(e) {
// normalize e.which for key events
// @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
if (typeof e.which !== 'number') {
e.which = e.keyCode;
}
var character = _characterFromEvent(e);
// no character found then stop
if (!character) {
return;
}
// need to use === for the character check because the character can be 0
if (e.type == 'keyup' && _ignoreNextKeyup === character) {
_ignoreNextKeyup = false;
return;
}
Mousetrap.handleKey(character, _eventModifiers(e), e);
}
/**
* determines if the keycode specified is a modifier key or not
*
* @param {string} key
* @returns {boolean}
*/
function _isModifier(key) {
return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
}
/**
* called to set a 1 second timeout on the specified sequence
*
* this is so after each key press in the sequence you have 1 second
* to press the next key before you have to start over
*
* @returns void
*/
function _resetSequenceTimer() {
clearTimeout(_resetTimer);
_resetTimer = setTimeout(_resetSequences, 1000);
}
/**
* reverses the map lookup so that we can look for specific keys
* to see what can and can't use keypress
*
* @return {Object}
*/
function _getReverseMap() {
if (!_REVERSE_MAP) {
_REVERSE_MAP = {};
for (var key in _MAP) {
// pull out the numeric keypad from here cause keypress should
// be able to detect the keys from the character
if (key > 95 && key < 112) {
continue;
}
if (_MAP.hasOwnProperty(key)) {
_REVERSE_MAP[_MAP[key]] = key;
}
}
}
return _REVERSE_MAP;
}
/**
* picks the best action based on the key combination
*
* @param {string} key - character for key
* @param {Array} modifiers
* @param {string=} action passed in
*/
function _pickBestAction(key, modifiers, action) {
// if no action was picked in we should try to pick the one
// that we think would work best for this key
if (!action) {
action = _getReverseMap()[key] ? 'keydown' : 'keypress';
}
// modifier keys don't work as expected with keypress,
// switch to keydown
if (action == 'keypress' && modifiers.length) {
action = 'keydown';
}
return action;
}
/**
* binds a key sequence to an event
*
* @param {string} combo - combo specified in bind call
* @param {Array} keys
* @param {Function} callback
* @param {string=} action
* @returns void
*/
function _bindSequence(combo, keys, callback, action) {
// start off by adding a sequence level record for this combination
// and setting the level to 0
_sequenceLevels[combo] = 0;
/**
* callback to increase the sequence level for this sequence and reset
* all other sequences that were active
*
* @param {string} nextAction
* @returns {Function}
*/
function _increaseSequence(nextAction) {
return function() {
_nextExpectedAction = nextAction;
++_sequenceLevels[combo];
_resetSequenceTimer();
};
}
/**
* wraps the specified callback inside of another function in order
* to reset all sequence counters as soon as this sequence is done
*
* @param {Event} e
* @returns void
*/
function _callbackAndReset(e) {
_fireCallback(callback, e, combo);
// we should ignore the next key up if the action is key down
// or keypress. this is so if you finish a sequence and
// release the key the final key will not trigger a keyup
if (action !== 'keyup') {
_ignoreNextKeyup = _characterFromEvent(e);
}
// weird race condition if a sequence ends with the key
// another sequence begins with
setTimeout(_resetSequences, 10);
}
// loop through keys one at a time and bind the appropriate callback
// function. for any key leading up to the final one it should
// increase the sequence. after the final, it should reset all sequences
//
// if an action is specified in the original bind call then that will
// be used throughout. otherwise we will pass the action that the
// next key in the sequence should match. this allows a sequence
// to mix and match keypress and keydown events depending on which
// ones are better suited to the key provided
for (var i = 0; i < keys.length; ++i) {
var isFinal = i + 1 === keys.length;
var wrappedCallback = isFinal ? _callbackAndReset : _increaseSequence(action || _getKeyInfo(keys[i + 1]).action);
_bindSingle(keys[i], wrappedCallback, action, combo, i);
}
}
/**
* Converts from a string key combination to an array
*
* @param {string} combination like "command+shift+l"
* @return {Array}
*/
function _keysFromString(combination) {
if (combination === '+') {
return ['+'];
}
return combination.split('+');
}
/**
* Gets info for a specific key combination
*
* @param {string} combination key combination ("command+s" or "a" or "*")
* @param {string=} action
* @returns {Object}
*/
function _getKeyInfo(combination, action) {
var keys,
key,
i,
modifiers = [];
// take the keys from this pattern and figure out what the actual
// pattern is all about
keys = _keysFromString(combination);
for (i = 0; i < keys.length; ++i) {
key = keys[i];
// normalize key names
if (_SPECIAL_ALIASES[key]) {
key = _SPECIAL_ALIASES[key];
}
// if this is not a keypress event then we should
// be smart about using shift keys
// this will only work for US keyboards however
if (action && action != 'keypress' && _SHIFT_MAP[key]) {
key = _SHIFT_MAP[key];
modifiers.push('shift');
}
// if this key is a modifier then add it to the list of modifiers
if (_isModifier(key)) {
modifiers.push(key);
}
}
// depending on what the key combination is
// we will try to pick the best event for it
action = _pickBestAction(key, modifiers, action);
return {
key: key,
modifiers: modifiers,
action: action
};
}
/**
* binds a single keyboard combination
*
* @param {string} combination
* @param {Function} callback
* @param {string=} action
* @param {string=} sequenceName - name of sequence if part of sequence
* @param {number=} level - what part of the sequence the command is
* @returns void
*/
function _bindSingle(combination, callback, action, sequenceName, level) {
// store a direct mapped reference for use with Mousetrap.trigger
_directMap[combination + ':' + action] = callback;
// make sure multiple spaces in a row become a single space
combination = combination.replace(/\s+/g, ' ');
var sequence = combination.split(' '),
info;
// if this pattern is a sequence of keys then run through this method
// to reprocess each pattern one key at a time
if (sequence.length > 1) {
_bindSequence(combination, sequence, callback, action);
return;
}
info = _getKeyInfo(combination, action);
// make sure to initialize array if this is the first time
// a callback is added for this key
_callbacks[info.key] = _callbacks[info.key] || [];
// remove an existing match if there is one
_getMatches(info.key, info.modifiers, {type: info.action}, sequenceName, combination, level);
// add this call back to the array
// if it is a sequence put it at the beginning
// if not put it at the end
//
// this is important because the way these are processed expects
// the sequence ones to come first
_callbacks[info.key][sequenceName ? 'unshift' : 'push']({
callback: callback,
modifiers: info.modifiers,
action: info.action,
seq: sequenceName,
level: level,
combo: combination
});
}
/**
* binds multiple combinations to the same callback
*
* @param {Array} combinations
* @param {Function} callback
* @param {string|undefined} action
* @returns void
*/
function _bindMultiple(combinations, callback, action) {
for (var i = 0; i < combinations.length; ++i) {
_bindSingle(combinations[i], callback, action);
}
}
// start!
_addEvent(document, 'keypress', _handleKeyEvent);
_addEvent(document, 'keydown', _handleKeyEvent);
_addEvent(document, 'keyup', _handleKeyEvent);
var Mousetrap = {
/**
* binds an event to mousetrap
*
* can be a single key, a combination of keys separated with +,
* an array of keys, or a sequence of keys separated by spaces
*
* be sure to list the modifier keys first to make sure that the
* correct key ends up getting bound (the last key in the pattern)
*
* @param {string|Array} keys
* @param {Function} callback
* @param {string=} action - 'keypress', 'keydown', or 'keyup'
* @returns void
*/
bind: function(keys, callback, action) {
keys = keys instanceof Array ? keys : [keys];
_bindMultiple(keys, callback, action);
return this;
},
/**
* unbinds an event to mousetrap
*
* the unbinding sets the callback function of the specified key combo
* to an empty function and deletes the corresponding key in the
* _directMap dict.
*
* TODO: actually remove this from the _callbacks dictionary instead
* of binding an empty function
*
* the keycombo+action has to be exactly the same as
* it was defined in the bind method
*
* @param {string|Array} keys
* @param {string} action
* @returns void
*/
unbind: function(keys, action) {
return Mousetrap.bind(keys, function() {}, action);
},
/**
* triggers an event that has already been bound
*
* @param {string} keys
* @param {string=} action
* @returns void
*/
trigger: function(keys, action) {
if (_directMap[keys + ':' + action]) {
_directMap[keys + ':' + action]({}, keys);
}
return this;
},
/**
* resets the library back to its initial state. this is useful
* if you want to clear out the current keyboard shortcuts and bind
* new ones - for example if you switch to another page
*
* @returns void
*/
reset: function() {
_callbacks = {};
_directMap = {};
return this;
},
/**
* should we stop this event before firing off callbacks
*
* @param {Event} e
* @param {Element} element
* @return {boolean}
*/
stopCallback: function(e, element) {
// if the element has the class "mousetrap" then no need to stop
if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
return false;
}
// stop for input, select, and textarea
return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
},
/**
* exposes _handleKey publicly so it can be overwritten by extensions
*/
handleKey: _handleKey
};
// expose mousetrap to the global object
window.Mousetrap = Mousetrap;
// expose mousetrap as an AMD module
if (typeof define === 'function' && define.amd) {
define(Mousetrap);
}
}) (window, document);
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
@import "mixins.less";
@import "ebook/variables.less";
@import "ebook/highlight.less";
.book-chapter {
display: none;
}
article {
page-break-after: always;
}
pre, blockquote {
border: 1px solid #999;
page-break-inside: avoid;
background: #f1f1f1;
padding: 8px;
}
img {
max-width: 100% !important;
page-break-inside: avoid;
margin: 0px auto;
}
.exercise, .quiz {
margin: 1cm 0cm;
padding: 0.4cm;
page-break-inside: avoid;
border: 3px solid #ddd;
.exercise-header {
margin-bottom: 0.4cm;
padding-bottom: 0.2cm;
border-bottom: 1px solid #ddd;
}
.question {
margin-top: 0.4cm;
}
}
+1
View File
@@ -0,0 +1 @@
@font-size-base: 13px;
+1 -1
View File
@@ -4,4 +4,4 @@
&:hover, &:focus {
color: inherit;
}
}
}
-115
View File
@@ -1,115 +0,0 @@
@import "vendors/bootstrap/bootstrap.less";
@import "vendors/fontawesome/font-awesome.less";
@import "mixins.less";
@import "variables.less";
@import "fonts.less";
@import "page/highlight.less";
@font-size-base: 13px;
* {
-webkit-overflow-scrolling: touch;
-webkit-tap-highlight-color: transparent;
-webkit-text-size-adjust: none;
-webkit-touch-callout: none;
-webkit-font-smoothing: antialiased;
}
html, body {
height: 100%;
}
body {
text-rendering: optimizeLegibility;
font-smoothing: antialiased;
font-family: @font-family-base;
}
h1, h2, h3 {
page-break-after: avoid;
page-break-before: auto;
}
h1 { font-size: floor(@font-size-base * 2.15); }
h2 { font-size: floor(@font-size-base * 1.70); }
h3 { font-size: ceil(@font-size-base * 1.25); }
h4 { font-size: ceil(@font-size-base * 1); }
h5 { font-size: ceil(@font-size-base * 0.85); }
h6 { font-size: ceil(@font-size-base * 0.65); }
pre, blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
img {
max-width: 100% !important;
page-break-inside: avoid;
}
section {
page-break-after: always;
&#cover {
padding: 3cm 0cm;
text-align: center;
h1 {
font-size: 1.5cm;
}
}
&#summary {
margin: 1.5cm;
h1 {
text-align: center;
}
ol {
list-style: none;
padding: 0px;
margin: 0px;
margin-left: 1cm;
}
> ol {
> li {
}
}
}
article {
margin-bottom: 1.5cm;
&.new-chapter {
page-break-after: always;
font-size: 0.6cm;
text-align: center;
padding: 3cm 0cm;
h1 {
font-size: floor(@font-size-base * 2.7);
}
}
.exercise {
margin: 1cm 0cm;
padding: 0.4cm;
page-break-inside: avoid;
border: 3px solid #ddd;
.exercise-header {
margin-bottom: 0.4cm;
padding-bottom: 0.2cm;
border-bottom: 1px solid #ddd;
}
}
}
}
@@ -2,20 +2,19 @@
@import "vendors/fontawesome/font-awesome.less";
@import "mixins.less";
@import "variables.less";
@import "fonts.less";
@import "book/highlight.less";
@import "book/languages.less";
@import "book/header.less";
@import "book/summary.less";
@import "book/font-settings.less";
@import "book/body.less";
@import "book/exercise.less";
@import "book/quiz.less";
@import "book/markdown.less";
@import "book/progress.less";
@import "book/navigation.less";
@import "website/fonts.less";
@import "website/variables.less";
@import "website/languages.less";
@import "website/header.less";
@import "website/summary.less";
@import "website/font-settings.less";
@import "website/body.less";
@import "website/exercise.less";
@import "website/quiz.less";
@import "website/markdown.less";
@import "website/progress.less";
@import "website/navigation.less";
* {
-webkit-overflow-scrolling: touch;
@@ -1,6 +1,10 @@
.book {
position: relative;
width: 100%;
height: 100%;
.book-body {
position: fixed;
position: absolute;
top: @header-height;
right: 0px;
left: 0px;
@@ -33,7 +37,8 @@
background: @page-background;
border-radius: 2px;
line-height: 1.5em;
line-height: @content-line-height;
font-size: @default-font-size;
}
.btn-group {
@@ -55,32 +60,10 @@
min-height: calc(~"100% - 57px")
}
}
&.font-size-0{
font-size:@s-font-size;
}
&.font-size-1{
font-size:@m-font-size;
}
&.font-size-2{
font-size:@l-font-size;
}
&.font-size-3{
font-size:@xl-font-size;
}
&.font-size-4{
font-size:@xxl-font-size;
}
&.font-family-0{
font-family: @font-family-serif;
}
&.font-family-1{
font-family: @font-family-sans;
}
}
&.with-summary {
@media (min-width: 800px) {
@media (min-width: @sidebar-breakpoint) {
.book-body {
left: 250px;
}
@@ -118,4 +101,37 @@
}
}
}
&.font-size-0 {
.book-body {
font-size:@s-font-size;
}
}
&.font-size-1 {
.book-body {
font-size:@m-font-size;
}
}
&.font-size-2 {
.book-body {
font-size:@l-font-size;
}
}
&.font-size-3 {
.book-body {
font-size:@xl-font-size;
}
}
&.font-size-4 {
.book-body {
font-size:@xxl-font-size;
}
}
&.font-family-0{
font-family: @font-family-serif;
}
&.font-family-1{
font-family: @font-family-sans;
}
}
@@ -1,7 +1,7 @@
.book .book-body .page-wrapper .page-inner {
section.exercise {
padding: 0px;
margin: 20px 0px;
margin: 20px 15px;
border: 3px solid #2f8cde;
.header {
+8 -56
View File
@@ -1,15 +1,3 @@
@font-face {
font-family: 'EB Garamond';
font-style: normal;
font-weight: 400;
src: local('EB Garamond 12 Regular'),url('@{FontPath}/ebgaramond/400.woff') format('woff');
}
@font-face {
font-family: 'EB Garamond';
font-style: italic;
font-weight: 400i;
src: local('EB Garamond 12 Italic'),url('@{FontPath}/ebgaramond/400i.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
@@ -19,7 +7,7 @@
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 250i;
font-weight: 250;
src: local('Merriweather Light Italic'),url('@{FontPath}/merriweather/250i.woff') format('woff');
}
@font-face {
@@ -31,21 +19,9 @@
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 400i;
font-weight: 400;
src: local('Merriweather Italic'),url('@{FontPath}/merriweather/400i.woff') format('woff');
}
@font-face {
font-family: 'Merriweather';
font-style: normal;
font-weight: 600;
src: local(''),url('@{FontPath}/merriweather/600.woff') format('woff');
}
/*@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 600i;
src: local(''),url('@{FontPath}/merriweather/600i.woff') format('woff');
}*/
@font-face {
font-family: 'Merriweather';
font-style: normal;
@@ -55,7 +31,7 @@
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 700i;
font-weight: 700;
src: local('Merriweather Bold Italic'),url('@{FontPath}/merriweather/700i.woff') format('woff');
}
@font-face {
@@ -67,33 +43,9 @@
@font-face {
font-family: 'Merriweather';
font-style: italic;
font-weight: 900i;
font-weight: 900;
src: local('Merriweather Heavy Italic'),url('@{FontPath}/merriweather/900i.woff') format('woff');
}
@font-face {
font-family: 'Anonymous Pro';
font-style: normal;
font-weight: 400;
src: local('Anonymous Pro'),url('@{FontPath}/anonymouspro/400.woff') format('woff');
}
@font-face {
font-family: 'Anonymous Pro';
font-style: italic;
font-weight: 400i;
src: local('Anonymous Pro Italic'),url('@{FontPath}/anonymouspro/400i.woff') format('woff');
}
@font-face {
font-family: 'Anonymous Pro';
font-style: normal;
font-weight: 700;
src: local('Anonymous Pro Bold'),url('@{FontPath}/anonymouspro/700.woff') format('woff');
}
@font-face {
font-family: 'Anonymous Pro';
font-style: italic;
font-weight: 700i;
src: local('Anonymous Pro Bold Italic'),url('@{FontPath}/anonymouspro/700i.woff') format('woff');
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
@@ -103,7 +55,7 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300i;
font-weight: 300;
src: local('Open Sans Light Italic'),url('@{FontPath}/opensans/300i.woff') format('woff');
}
@font-face {
@@ -115,7 +67,7 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400i;
font-weight: 400;
src: local('Open Sans Italic'),url('@{FontPath}/opensans/400i.woff') format('woff');
}
@font-face {
@@ -127,7 +79,7 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600i;
font-weight: 600;
src: local('Open Sans Semibold Italic'),url('@{FontPath}/opensans/600i.woff') format('woff');
}
@font-face {
@@ -139,6 +91,6 @@
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700i;
font-weight: 700;
src: local('Open Sans Bold Italic'),url('@{FontPath}/opensans/700i.woff') format('woff');
}
@@ -2,7 +2,7 @@
.book-header {
font-family: @font-family-sans;
position: fixed;
position: absolute;
overflow: visible;
top: 0px;
right: 0px;
@@ -13,7 +13,7 @@
font-size: 0.85em;
color: @header-color;
background: @header-background;
box-shadow: 0 1px 2px hsla(200,10%,80%,0.6);
border-bottom: 1px solid @header-border;
.btn {
display: block;
@@ -52,9 +52,23 @@
@media (max-width: 800px) {
display: none;
}
i {
display: none;
}
}
}
&.is-loading {
.book-header h1 {
i {
display: inline-block;
}
a {
display: none;
}
}
}
&.with-summary {
.book-header h1 {
margin-left: 250px;
@@ -0,0 +1,92 @@
/* Tomorrow Night Bright Theme */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Tomorrow Comment */
.hljs-comment,
.hljs-title {
color: #969896;
}
/* Tomorrow Red */
.hljs-variable,
.hljs-attribute,
.hljs-tag,
.hljs-regexp,
.ruby .hljs-constant,
.xml .hljs-tag .hljs-title,
.xml .hljs-pi,
.xml .hljs-doctype,
.html .hljs-doctype,
.css .hljs-id,
.css .hljs-class,
.css .hljs-pseudo {
color: #d54e53;
}
/* Tomorrow Orange */
.hljs-number,
.hljs-preprocessor,
.hljs-pragma,
.hljs-built_in,
.hljs-literal,
.hljs-params,
.hljs-constant {
color: #e78c45;
}
/* Tomorrow Yellow */
.ruby .hljs-class .hljs-title,
.css .hljs-rules .hljs-attribute {
color: #e7c547;
}
/* Tomorrow Green */
.hljs-string,
.hljs-value,
.hljs-inheritance,
.hljs-header,
.ruby .hljs-symbol,
.xml .hljs-cdata {
color: #b9ca4a;
}
/* Tomorrow Aqua */
.css .hljs-hexcolor {
color: #70c0b1;
}
/* Tomorrow Blue */
.hljs-function,
.python .hljs-decorator,
.python .hljs-title,
.ruby .hljs-function .hljs-title,
.ruby .hljs-title .hljs-keyword,
.perl .hljs-sub,
.javascript .hljs-title,
.coffeescript .hljs-title {
color: #7aa6da;
}
/* Tomorrow Purple */
.hljs-keyword,
.javascript .hljs-function {
color: #c397d8;
}
.hljs {
display: block;
background: black;
color: #eaeaea;
padding: 0.5em;
}
.coffeescript .javascript,
.javascript .xml,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}
@@ -0,0 +1,107 @@
/*
Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>
*/
.hljs {
display: block;
padding: 0.5em;
background: #fdf6e3;
color: #657b83;
}
.hljs-comment,
.hljs-template_comment,
.diff .hljs-header,
.hljs-doctype,
.hljs-pi,
.lisp .hljs-string,
.hljs-javadoc {
color: #93a1a1;
}
/* Solarized Green */
.hljs-keyword,
.hljs-winutils,
.method,
.hljs-addition,
.css .hljs-tag,
.hljs-request,
.hljs-status,
.nginx .hljs-title {
color: #859900;
}
/* Solarized Cyan */
.hljs-number,
.hljs-command,
.hljs-string,
.hljs-tag .hljs-value,
.hljs-rules .hljs-value,
.hljs-phpdoc,
.tex .hljs-formula,
.hljs-regexp,
.hljs-hexcolor,
.hljs-link_url {
color: #2aa198;
}
/* Solarized Blue */
.hljs-title,
.hljs-localvars,
.hljs-chunk,
.hljs-decorator,
.hljs-built_in,
.hljs-identifier,
.vhdl .hljs-literal,
.hljs-id,
.css .hljs-function {
color: #268bd2;
}
/* Solarized Yellow */
.hljs-attribute,
.hljs-variable,
.lisp .hljs-body,
.smalltalk .hljs-number,
.hljs-constant,
.hljs-class .hljs-title,
.hljs-parent,
.haskell .hljs-type,
.hljs-link_reference {
color: #b58900;
}
/* Solarized Orange */
.hljs-preprocessor,
.hljs-preprocessor .hljs-keyword,
.hljs-pragma,
.hljs-shebang,
.hljs-symbol,
.hljs-symbol .hljs-string,
.diff .hljs-change,
.hljs-special,
.hljs-attr_selector,
.hljs-subst,
.hljs-cdata,
.clojure .hljs-title,
.css .hljs-pseudo,
.hljs-header {
color: #cb4b16;
}
/* Solarized Red */
.hljs-deletion,
.hljs-important {
color: #dc322f;
}
/* Solarized Violet */
.hljs-link_label {
color: #6c71c4;
}
.tex .hljs-formula {
background: #eee8d5;
}
@@ -1,8 +1,9 @@
/* http://jmblog.github.io/color-themes-for-highlightjs */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Tomorrow Comment */
.hljs-comment {
color: hsl(207, 35%, 35%);
.hljs-comment,
.hljs-title {
color: #8e908c;
}
/* Tomorrow Red */
@@ -27,9 +28,9 @@
.hljs-pragma,
.hljs-built_in,
.hljs-literal,
.hljs-constant,
.hljs-function .hljs-title {
color: hsl(50, 100%, 60%);
.hljs-params,
.hljs-constant {
color: #f5871f;
}
/* Tomorrow Yellow */
@@ -45,7 +46,7 @@
.hljs-header,
.ruby .hljs-symbol,
.xml .hljs-cdata {
color: hsl(0, 100%, 70%);
color: #718c00;
}
/* Tomorrow Aqua */
@@ -54,7 +55,7 @@
}
/* Tomorrow Blue */
.hljs-function .keyword,
.hljs-function,
.python .hljs-decorator,
.python .hljs-title,
.ruby .hljs-function .hljs-title,
@@ -62,13 +63,13 @@
.perl .hljs-sub,
.javascript .hljs-title,
.coffeescript .hljs-title {
color: hsl(207, 70%, 60%);
color: #4271ae;
}
/* Tomorrow Purple */
.hljs-keyword,
.javascript .hljs-function {
color: hsl(207, 95%, 70%);
color: #8959a8;
}
.hljs {
@@ -76,7 +77,6 @@
background: white;
color: #4d4d4c;
padding: 0.5em;
font-family: "Anonymous Pro", "Inconsolata", "Monaco", monospace;
}
.coffeescript .javascript,
@@ -87,4 +87,4 @@
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}
}
@@ -1,13 +1,30 @@
.book.color-theme-1 .book-body .page-wrapper .page-inner section.normal {
color:@page-color-1;
pre, code {
background: #fdf6e3;
color: #657b83;
border-color: darken(#fdf6e3, 15%);
@import "./highlight/sepia.less";
}
}
.book.color-theme-2 .book-body .page-wrapper .page-inner section.normal {
color:@page-color-2;
pre, code {
background: black;
color: #eaeaea;
border-color: #000;
@import "./highlight/night.less";
}
}
.book .book-body .page-wrapper .page-inner section.normal {
padding: 25px;
padding-bottom: 25px;
padding-top: 15px;
color:@page-color;
letter-spacing: 0.01rem;
& > *:first-child {
margin-top: 0 !important; }
@@ -56,25 +73,25 @@
font-size: inherit; }
h1 {
font-size: 28px;
font-size: 2.5em;
}
h2 {
font-size: 24px;
font-size: 2em;
border-bottom: 1px solid #eee;
}
h3 {
font-size: 18px; }
font-size: 1.5em; }
h4 {
font-size: 16px; }
font-size: 1.2em; }
h5 {
font-size: 14px; }
font-size: 1em; }
h6 {
font-size: 14px; }
font-size: 1em; }
p, blockquote, ul, ol, dl, table, pre {
margin: 15px 0; }
@@ -276,29 +293,15 @@
white-space: pre;
border: none;
background: transparent;
}
.highlight pre {
color: hsl(204, 40%, 80%);
background-color: hsl(204, 30%, 10%);
border: 1px solid hsl(204, 30%, 10%);
font-size: 16px;
line-height: 1.5em;
overflow: auto;
padding: 20px;
margin: 0 -20px;
border-radius: 3px;
@import "./highlight/white.less";
}
pre {
color: hsl(204, 40%, 80%);
background-color: hsl(204, 30%, 10%);
border: 1px solid hsl(204, 30%, 10%);
font-size: 16px;
font-size: inherit;
line-height: 1.5em;
overflow: auto;
padding: 20px;
margin: 0 -20px;
border-radius: 3px;
}
@@ -13,15 +13,15 @@
flex-direction: column;
font-size: 40px;
color: rgba(0,0,0,0.5);
color: rgba(0, 0, 0, 0.2);
text-align: center;
.transition(all 350ms ease);
&:hover {
background-color: @body-pagination-background;
text-decoration: none;
color: rgba(0, 0, 0, 0.6);
}
&.navigation-next {
@@ -40,16 +40,19 @@
width: 50%;
display: inline-block;
float: left;
&.navigation-unique {
max-width: 100%;
width: 100%;
}
}
}
}
.book.color-theme-1 .book-body {
.navigation:hover{
background-color: @body-pagination-background-1;
}
}
.book.color-theme-2 .book-body {
.navigation:hover{
background-color: @body-pagination-background-2;
}
}
@@ -16,7 +16,7 @@
width: 100%;
position: relative;
background: #fff;
margin-bottom: 20px;
margin-bottom: 10px;
.bar {
height: @bar-height;
@@ -1,7 +1,7 @@
.book .book-body .page-wrapper .page-inner {
section.quiz {
padding: 0px;
margin: 20px 0px;
margin: 20px 15px;
border: 3px solid #2f8cde;
.header {
@@ -29,8 +29,12 @@
.question-inner {
padding: 15px;
.quiz-label {
font-weight: normal;
cursor: pointer;
}
}
table {
margin-bottom: 10px;
width: 100%;
@@ -46,4 +50,4 @@
}
}
}
}
}
@@ -2,7 +2,7 @@
.book-summary {
font-family: @font-family-sans;
position: fixed;
position: absolute;
top: @header-height;
left: -@sidebar-width;
bottom: 0px;
@@ -11,7 +11,7 @@
width: @sidebar-width;
color: @sidebar-color;
background: @sidebar-background;
box-shadow: inset -1px 0 5px hsla(0, 0%, 0%, 0.1);
border-right: 1px solid @sidebar-border;
.transition(all 0.5s ease);
@@ -57,7 +57,6 @@
margin: 7px 0;
overflow: hidden;
background: @sidebar-divider-color;
box-shadow: 0 1px rgba(255,255,255,0.7);
}
i.fa-check {
@@ -90,7 +89,6 @@
overflow: hidden;
white-space: nowrap;
position: relative;
padding-left: 25px;
}
span {
@@ -110,7 +108,7 @@
}
}
@media (max-width: 600px) {
@media (max-width: @sidebar-breakpoint) {
width: 100%;
bottom: 0px;
left: -100%;
@@ -162,6 +160,7 @@
li {
&.divider {
background: @sidebar-divider-color-1;
box-shadow: none;
}
i.fa-check {
@@ -206,6 +205,7 @@
li {
&.divider {
background: @sidebar-divider-color-2;
box-shadow: none;
}
i.fa-check {
+37 -33
View File
@@ -7,39 +7,44 @@
@header-height: 50px;
@header-color: hsl(194, 5%, 52%);
@header-background: white;
@header-border: rgba(0, 0, 0, 0.07);
@header-button-color: hsl(194, 5%, 52%);
@header-button-hover-color: #151515;
@header-button-hover-background: hsl(207, 15%, 95%);
// Body
@body-background: white;
@body-pagination-background: hsl(207, 15%, 95%);
// Sidebar
@sidebar-width: 250px;
@sidebar-breakpoint: 600px;
@sidebar-color: hsl(207, 15%, 80%);
@sidebar-background: hsl(207, 15%, 95%);
@sidebar-background: @body-background;
@sidebar-border: @header-border;
@sidebar-nested-padding: 10px;
@sidebar-nested-padding: 20px;
@sidebar-search-padding: 6px;
@sidebar-search-background: transparent;
@sidebar-search-input-border-color: hsl(207, 15%, 80%);
@sidebar-search-input-border-color: @sidebar-border;
@sidebar-divider-color: hsl(207, 15%, 85%);
@sidebar-divider-color: @sidebar-border;
@sidebar-link-color: hsl(207, 15%, 50%);
@sidebar-link-color: hsl(207, 15%, 25%);
@sidebar-link-background: transparent;
@sidebar-link-hover-color: hsl(207, 100%, 50%);
@sidebar-link-hover-background: transparent;
@sidebar-icon-color: @bar-progress-background;
@sidebar-link-completed: hsl(207, 15%, 25%);
@sidebar-link-completed: @sidebar-link-color;
@sidebar-link-completed-weight: normal;
// Page
@page-color: black;
@page-background: white;
@page-color: black;
@page-background: @body-background;
// Content
@content-line-height: 1.6em;
// Progress Bar
@chapter-display: none;
@@ -76,26 +81,27 @@
@border-radius-small: 1px;
// Fonts
@font-family-serif: Merriweather, "EB Garamond", Georgia, serif;
@font-family-serif: "Merriweather", Georgia, serif;
@font-family-sans: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-base: @font-family-serif;
@font-family-base: @font-family-sans;
@headings-font-family: inherit;
@FontPath: '@{staticPath}/fonts';
@fa-font-path: "@{FontPath}/fontawesome";
@s-font-size: 1.2rem;
@m-font-size: 1.4rem;
@l-font-size: 1.6rem;
@xl-font-size: 2.2rem;
@xxl-font-size: 4.0rem;
@s-font-size: 1.2rem;
@m-font-size: 1.4rem;
@l-font-size: 1.6rem;
@xl-font-size: 2.2rem;
@xxl-font-size: 4.0rem;
@default-font-size: @l-font-size;
/*
,--------.,--.
'--. .--'| ,---. ,---. ,--,--,--. ,---. ,---. .--.
| | | .-. || .-. :| || .-. :( .-' '--'
| | | | | |\ --.| | | |\ --..-' `).--.
`--' `--' `--' `----'`--`--`--' `----'`----' '--'
/*
,--------.,--.
'--. .--'| ,---. ,---. ,--,--,--. ,---. ,---. .--.
| | | .-. || .-. :| || .-. :( .-' '--'
| | | | | |\ --.| | | |\ --..-' `).--.
`--' `--' `--' `----'`--`--`--' `----'`----' '--'
*/
// Header
@header-color-1: #AFA790;
@@ -112,21 +118,19 @@
// Body
@body-background-1: #F3EACB;
@body-background-2: #1d1f21;
@body-pagination-background-1: #FFFAEA;
@body-pagination-background-2: #33404D;
// Sidebar
@sidebar-color-1: #AFA790;
@sidebar-color-2: hsl(207, 15%, 80%);
@sidebar-background-1: #F3EACB;
@sidebar-background-2: #111111;
@sidebar-background-1: @body-background-1;
@sidebar-background-2: @body-background-2;
@sidebar-search-background-1: transparent;
@sidebar-search-background-2: transparent;
@sidebar-search-input-border-color-1: #D6CFBA;
@sidebar-search-input-border-color-2: #c4cdd4;
@sidebar-search-input-border-color-2: #000;
@sidebar-divider-color-1: hsl(207, 15%, 85%);
@sidebar-divider-color-1: @sidebar-divider-color;
@sidebar-divider-color-2: #1d1f21;
@sidebar-link-color-1: #877F6A;
@@ -140,14 +144,14 @@
@sidebar-icon-color-1: @bar-progress-background;
@sidebar-icon-color-2: @bar-progress-background;
@sidebar-link-completed-1: hsl(207, 15%, 25%);
@sidebar-link-completed-2: hsl(207, 15%, 25%);
@sidebar-link-completed-1: @sidebar-link-color-1;
@sidebar-link-completed-2: @sidebar-link-color-2;
@sidebar-link-completed-weight-1: normal;
@sidebar-link-completed-weight-2: normal;
// Page
@page-color-1: #704214;
@page-color-2: #a4b1b1;
@page-color-1: #704214;
@page-color-2: #a4b1b1;
@page-background-1: #F3EACB;
@page-background-2: #1d1f21;
@@ -1,22 +1,22 @@
<div class="dropdown-menu font-settings">
<div class="dropdown-caret">
<span class="caret-outer"></span>
<span class="caret-inner"></span>
</div>
<div class="btn-group btn-block">
<button id="reduce-font-size" class="btn btn-default">A</button>
<button id="enlarge-font-size" class="btn btn-default">A</button>
</div>
<ul class="list-group font-family-list">
<li class="list-group-item" data-font="0">Serif</li>
<li class="list-group-item" data-font="1">Sans</li>
</ul>
<div class="btn-group btn-group-xs btn-block color-theme-list">
<button type="button" class="btn btn-default" id="color-theme-preview-0" data-theme="0">White</button>
<button type="button" class="btn btn-default" id="color-theme-preview-1" data-theme="1">Sepia</button>
<button type="button" class="btn btn-default" id="color-theme-preview-2" data-theme="2">Night</button>
</div>
<div class="dropdown-caret">
<span class="caret-outer"></span>
<span class="caret-inner"></span>
</div>
<div class="btn-group btn-block">
<button id="reduce-font-size" class="btn btn-default">A</button>
<button id="enlarge-font-size" class="btn btn-default">A</button>
</div>
<ul class="list-group font-family-list">
<li class="list-group-item" data-font="0">Serif</li>
<li class="list-group-item" data-font="1">Sans</li>
</ul>
<div class="btn-group btn-group-xs btn-block color-theme-list">
<button type="button" class="btn btn-default" id="color-theme-preview-0" data-theme="0">White</button>
<button type="button" class="btn btn-default" id="color-theme-preview-1" data-theme="1">Sepia</button>
<button type="button" class="btn btn-default" id="color-theme-preview-2" data-theme="2">Night</button>
</div>
</div>
+20 -11
View File
@@ -1,26 +1,35 @@
<div class="book-header">
<!-- Actions Left -->
{% if githubId %}
<a href="{{ githubHost }}{{ githubId }}" target="_blank" class="btn pull-left"><i class="fa fa-github-alt"></i></a>
<a href="#" class="btn pull-left toggle-summary" aria-label="Toggle summary"><i class="fa fa-align-justify"></i></a>
{% if options.links.home !== false and (options.links.home != null or githubId) %}
<a href="{{ options.links.home|default(githubHost+githubId) }}" target="_blank" class="btn pull-left home-bookmark" aria-label="GitHub home"><i class="fa fa-bookmark-o"></i></a>
{% endif %}
<a href="#" class="btn pull-left toggle-summary"><i class="fa fa-align-justify"></i></a>
<a href="#" class="btn pull-left toggle-search"><i class="fa fa-search"></i></a>
<a href="#" class="btn pull-left toggle-search" aria-label="Toggle search"><i class="fa fa-search"></i></a>
<span id="font-settings-wrapper">
<a href="#" class="btn pull-left toggle-font-settings"><i class="fa fa-font"></i>
<a href="#" class="btn pull-left toggle-font-settings" aria-label="Toggle font settings"><i class="fa fa-font"></i>
</a>
{% include "./font-settings.html" %}
</span>
<!-- Actions Right -->
<a href="#" target="_blank" class="btn pull-right" data-sharing="google-plus"><i class="fa fa-google-plus"></i></a>
<a href="#" target="_blank" class="btn pull-right" data-sharing="facebook"><i class="fa fa-facebook"></i></a>
<a href="#" target="_blank" class="btn pull-right" data-sharing="twitter"><i class="fa fa-twitter"></i></a>
{% if options.links.sharing.google !== false %}
<a href="#" target="_blank" class="btn pull-right google-plus-sharing-link sharing-link" data-sharing="google-plus" aria-label="Share on Google Plus"><i class="fa fa-google-plus"></i></a>
{% endif %}
{% if options.links.sharing.facebook !== false %}
<a href="#" target="_blank" class="btn pull-right facebook-sharing-link sharing-link" data-sharing="facebook" aria-label="Share on Facebook"><i class="fa fa-facebook"></i></a>
{% endif %}
{% if options.links.sharing.twitter !== false %}
<a href="#" target="_blank" class="btn pull-right twitter-sharing-link sharing-link" data-sharing="twitter" aria-label="Share on Twitter"><i class="fa fa-twitter"></i></a>
{% endif %}
{% if githubId %}
<a href="{{ githubHost }}{{ githubId }}/stargazers" target="_blank" class="btn pull-right count-star"><i class="fa fa-star-o"></i> Star (<span>-</span>)</a>
<a href="{{ githubHost }}{{ githubId }}/watchers" target="_blank" class="btn pull-right count-watch"><i class="fa fa-eye"></i> Watch (<span>-</span>)</a>
<a href="{{ githubHost }}{{ githubId }}/stargazers" target="_blank" class="btn pull-right count-star hidden-xs"><i class="fa fa-star-o"></i> Star (<span>-</span>)</a>
<a href="{{ githubHost }}{{ githubId }}/watchers" target="_blank" class="btn pull-right count-watch hidden-xs"><i class="fa fa-eye"></i> Watch (<span>-</span>)</a>
{% endif %}
<!-- Title -->
<h1><a href="{{ basePath }}/" >{{ title }}</a></h1>
<h1>
<i class="fa fa-spinner fa-spin"></i>
<a href="{{ basePath }}/" >{{ title }}</a>
</h1>
</div>
+45 -30
View File
@@ -1,48 +1,63 @@
{% macro articles(_articles) %}
{% for item in _articles %}
<li class="chapter {% if item._path == _input %}active{% endif %}" data-level="{{ item.level }}" {% if item.path %}data-path="{{ item.path|mdLink }}"{% endif %}>
{% if item.path %}
<a href="{{ basePath }}/{{ item.path|mdLink }}">
<i class="fa fa-check"></i> <b>{{ item.level }}.</b> {{ item.title }}
</a>
{% else %}
<span><b>{{ item.level }}.</b> {{ item.title }}</span>
{% endif %}
{% if item.articles.length > 0 %}
<ul class="articles">
{{ articles(item.articles) }}
</ul>
{% endif %}
</li>
{% endfor %}
{% endmacro %}
<div class="book-summary">
<div class="book-search">
<input type="text" placeholder="Search" class="form-control" />
</div>
<ul class="summary">
{% if githubId %}
{% set _divider = false %}
{% if options.links.about !== false && (options.links.about || githubId) %}
{% set _divider = true %}
<li>
<a href="{{ githubHost }}{{ githubAuthor }}" target="blank">About the author</a>
<a href="{{ options.links.about|default(githubHost+githubAuthor) }}" target="blank" class="author-link">About the author</a>
</li>
{% endif %}
{% if options.links.issues !== false && (options.links.issues || githubId) %}
{% set _divider = true %}
<li>
<a href="{{ githubHost }}{{ githubId }}/issues" target="blank">Questions and Issues</a>
<a href="{{ options.links.issues|default(githubHost+githubId+"/issues") }}" target="blank"class="issues-link">Questions and Issues</a>
</li>
{% endif %}
{% if options.links.contribute !== false && (options.links.contribute || githubId) %}
{% set _divider = true %}
<li>
<a href="{{ githubHost }}{{ githubId }}/edit/master/{{ _input }}" target="blank">Edit and Contribute</a>
<a href="{{ options.links.contribute|default(githubHost+githubId+"/edit/master/"+_input) }}" target="blank" class="contribute-link">Edit and Contribute</a>
</li>
{% endif %}
{% if _divider %}
<li class="divider"></li>
{% endif %}
<li data-level="0" data-path="index.html">
<a href="{{ basePath }}/"><i class="fa fa-check"></i> Introduction</a>
</li>
{% for item in summary.chapters %}
<li {% if item._path == _input %}class="active"{% endif %} data-level="{{ item.level }}" {% if item.path %}data-path="{{ item.path|mdLink }}"{% endif %}>
{% if item.path %}
<a href="{{ basePath }}/{{ item.path|mdLink }}">
<i class="fa fa-check"></i> <b>{{ item.level }}.</b> {{ item.title }}
</a>
{% else %}
<span><b>{{ item.level }}.</b> {{ item.title }}</span>
{% endif %}
{% if item.articles.length > 0 %}
<ul class="articles">
{% for article in item.articles %}
<li {% if article._path == _input %}class="active"{% endif %} data-level="{{ article.level }}" {% if article.path %}data-path="{{ article.path|mdLink }}"{% endif %}>
{% if article.path %}
<a href="{{ basePath }}/{{ article.path|mdLink }}">
<i class="fa fa-check"></i> <b>{{ article.level }}.</b> {{ article.title }}
</a>
{% else %}
<span><b>{{ article.level }}.</b> {{ article.title }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
{{ articles(summary.chapters) }}
{% if options.links.gitbook !== false %}
<li class="divider"></li>
<li>
<a href="http://www.gitbook.io/" target="blank" class="gitbook-link">Generated using GitBook</a>
</li>
{% endif %}
</ul>
</div>
+5 -1
View File
@@ -2,6 +2,10 @@
{% block title %}{{ title }}{% endblock %}
{% block style %}
<link rel="stylesheet" href="{{ staticBase }}/style.css">
{% endblock %}
{% block content %}
<div class="book-langs-index">
<div class="inner">
@@ -10,7 +14,7 @@
<ul class="languages">
{% for lang in langs %}
<li>
<a href="{{ basePath}}/{{ lang.path }}">{{ lang.title }}</a>
<a href="{{ basePath}}/{{ lang.path|pathJoin("/index.html") }}">{{ lang.title }}</a>
</li>
{% endfor %}
</ul>
+15 -37
View File
@@ -1,49 +1,27 @@
<!DOCTYPE HTML>
<html lang="en-US">
<head prefix="og: http://ogp.me/ns# book: http://ogp.me/ns/book#">
{% block head %}
<html lang="en-US" {% block htmlTag %}{% endblock %}>
{{ htmlSnippet("html:start")|default("") }}
<head>
{{ htmlSnippet("head:start")|default("") }}
<meta charset="UTF-8">
<title>{% block title %} | {{ title }}{% endblock %}</title>
<title>{% block title %}{% endblock %}</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="robots" content="index, follow">
<meta name="author" content="{{ githubAuthor }}">
<meta name="description" content="{{ description }}">
<meta name="keywords" content="gitbook,github" >
<meta name="generator" content="www.gitbook.io">
{% if progress.current.next and progress.current.next.path %}
<link rel="next" href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" />
{% endif %}
{% if progress.current.prev and progress.current.prev.path %}
<link rel="prev" href="{{ basePath }}/{{ progress.current.prev.path|mdLink }}" />
{% endif %}
<meta property="og:title" content="{% block title %} | {{ title }}{% endblock %}">
<meta property="og:site_name" content="{{ title }}">
<meta property="og:type" content="book">
<meta property="og:locale" content="en_US">
<meta property="book:author" content="{{ githubHost }}{{ githubAuthor }}">
<meta property="book:tag" content="GitBook">
<meta name="description" content="{% block description %}{% endblock %}">
<meta name="generator" content="GitBook {{ gitbook.version }}">
<meta name="HandheldFriendly" content="true"/>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="shortcut icon" href="{{ staticBase }}/images/favicon.ico" type="image/x-icon">
{% endblock %}
{% block head %}{% endblock %}
{{ htmlSnippet("head:end")|default("") }}
</head>
<body>
{% block style %}
<link rel="stylesheet" href="{{ staticBase }}/style.css">
{% endblock %}
{{ htmlSnippet("body:start")|default("") }}
{% block style %}{% endblock %}
{% block content %}{% endblock %}
{% block javascript %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/mode-javascript.js"></script>
<script src="{{ staticBase }}/jsrepl/jsrepl.js" id="jsrepl-script"></script>
<script src="{{ staticBase }}/app.js"></script>
{% endblock %}
{% block javascript %}{% endblock %}
{{ htmlSnippet("body:end")|default("") }}
</body>
{{ htmlSnippet("html:end")|default("") }}
</html>
+45 -94
View File
@@ -20,10 +20,15 @@
</div>
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% autoescape false %}{{ section.quiz.base }}{% endautoescape %}
{% set exercise = exercise + 1 %}
<div class="exercise-header">Quiz #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% for quiz in section.quiz %}
<div class="question">
<div class="question-header">Question {{ loop.index }} of {{ section.quiz.length }}</div>
{% autoescape false %}{{ quiz.base }}{% endautoescape %}
</div>
{% endfor %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
@@ -31,104 +36,50 @@
{% set exercise = 1 %}
{% block content %}
{# Cover #}
<section id="cover">
<h1>{{ title }}</h1>
{% if githubId %}
<h2>By <a href="{{ githubHost }}{{ githubAuthor }}">@{{ githubAuthor }}</a></h2>
{% endif %}
</section>
{# Summary #}
<section id="summary">
<h1>Table of Contents</h1>
<ol>
<li>
<a href="#README.md">
<h2>Introduction</h2>
</a>
</li>
{% for item in summary.chapters %}
<li>
<h2><span>{{ item.level }}.</span> <a href="#{{ item.path }}">{{ item.title }}</a></h2>
{% if item.articles.length > 0 %}
<ol>
{% for article in item.articles %}
<li>
<span>{{ article.level }}</span> <a href="#{{ article.path }}">{{ article.title }}</a>
</li>
{% endfor %}
</ol>
{% endif %}
</li>
{% endfor %}
</ol>
</section>
{# Pages content #}
<section>
<article id="README.md">
{{ articleContent(pages["README.md"].content) }}
</article>
</section>
{% for item in summary.chapters %}
<section>
<article id="{{ item.path }}" class="new-chapter">
<h1>{{ item.title }}</h1>
</article>
{% if pages[item.path] %}
<article>
{{ articleContent(pages[item.path].content) }}
</article>
{% endif %}
{% if item.articles.length > 0 %}
{% for article in item.articles %}
{% if pages[article.path] %}
<article id="{{ article.path }}">
{{ articleContent(pages[article.path].content) }}
</article>
{% endif %}
{% endfor %}
{% endif %}
</section>
{% for item in progress.chapters %}
<article id="{{ article.path }}">
<h1 class="book-chapter book-chapter-{{ item.level|lvl }}">{{ item.title }}</h1>
{% if pages[item.path] %}
{{ articleContent(pages[item.path].content) }}
{% endif %}
</article>
{% endfor %}
{# Exercise solutions #}
{% if exercise > 1 %}
{% set exercise = 1 %}
<section>
<article class="new-chapter">
<h1>Exercise Solutions</h1>
</article>
<article>
{% for item in summary.chapters %}
{% for article in item.articles %}
{% if pages[article.path] %}
{% for section in pages[article.path].content %}
{% if section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre><code>{% autoescape false %}{{ section.code.solution|code }}{% endautoescape %}</code></pre>
{% set exercise = exercise + 1 %}
</div>
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% autoescape false %}{{ section.quiz.solution }}{% endautoescape %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<article>
<h1 class="book-chapter book-chapter-1">Exercise Solutions</h1>
{% for item in progress.chapters %}
{% if pages[item.path] %}
{% for section in pages[item.path].content %}
{% if section.type == "exercise" %}
<div class="exercise">
<div class="exercise-header">Exercise #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
<pre><code>{% autoescape false %}{{ section.code.solution|code }}{% endautoescape %}</code></pre>
{% set exercise = exercise + 1 %}
</div>
{% elif section.type == "quiz" %}
<div class="quiz">
<div class="exercise-header">Quiz #{{ exercise }}</div>
{% autoescape false %}{{ section.content }}{% endautoescape %}
{% for quiz in section.quiz %}
<div class="question">
<div class="question-header">Question {{ loop.index }} of {{ section.quiz.length }}</div>
{% autoescape false %}{{ quiz.solution }}{% endautoescape %}
</div>
{% endfor %}
{% set exercise = exercise + 1 %}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</article>
</section>
</article>
{% endif %}
{% endblock %}
+23 -5
View File
@@ -1,6 +1,23 @@
{% extends "layout.html" %}
{% block title %}{{ progress.current.title }}{% parent %}{% endblock %}
{% block htmlTag %}{% if options.cache !== false %}manifest="{{ basePath }}/manifest.appcache"{% endif %}{% endblock %}
{% block head %}
{% parent %}
{% if githubAuthor %}
<meta name="author" content="{{ githubAuthor }}">
{% endif %}
{% if progress.current.next and progress.current.next.path %}
<link rel="next" href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" />
{% endif %}
{% if progress.current.prev and progress.current.prev.path %}
<link rel="prev" href="{{ basePath }}/{{ progress.current.prev.path|mdLink }}" />
{% endif %}
{% endblock %}
{% block title %}{{ progress.current.title }} | {{ title }}{% endblock %}
{% block description %}{% if progress.current.level == "0" %}{{ description }}{% endif %}{% endblock %}
{% block content %}
<div class="book" {% if githubId %}data-github="{{ githubId }}"{% endif %} data-level="{{ progress.current.level }}" data-basepath="{{ basePath }}" data-revision="{{ revision }}">
{% include "includes/book/header.html" %}
@@ -27,17 +44,18 @@
</div>
{% if progress.current.prev and progress.current.prev.path %}
<a href="{{ basePath }}/{{ progress.current.prev.path|mdLink }}" class="navigation navigation-prev"><i class="fa fa-angle-left"></i></a>
<a href="{{ basePath }}/{{ progress.current.prev.path|mdLink }}" class="navigation navigation-prev {% if !progress.current.next or !progress.current.next.path %}navigation-unique{% endif %}" aria-label="Previous page: {{ progress.current.prev.title }}"><i class="fa fa-angle-left"></i></a>
{% endif %}
{% if progress.current.next and progress.current.next.path %}
<a href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" class="navigation navigation-next"><i class="fa fa-angle-right"></i></a>
<a href="{{ basePath }}/{{ progress.current.next.path|mdLink }}" class="navigation navigation-next {% if !progress.current.prev or !progress.current.prev.path %}navigation-unique{% endif %}" aria-label="Next page: {{ progress.current.next.title }}"><i class="fa fa-angle-right"></i></a>
{% endif %}
</div>
</div>
{% endblock %}
{% block javascript %}
{% parent %}
<script src="{{ staticBase }}/jsrepl/jsrepl.js" id="jsrepl-script"></script>
<script src="{{ staticBase }}/app.js"></script>
{% for resource in plugins.resources.js %}
{% if resource.url %}
<script src="{{ resource.url }}"></script>
@@ -54,7 +72,7 @@ require(["gitbook"], function(gitbook) {
{% endblock %}
{% block style %}
{% parent %}
<link rel="stylesheet" href="{{ staticBase }}/style.css">
{% for resource in plugins.resources.css %}
{% if resource.url %}
<link rel="stylesheet" href="{{ resource.url }}">