Compare commits

..

741 Commits

Author SHA1 Message Date
Samy Pessé 42fd7b12c0 Bump version to 2.0.0-alpha.1 2015-02-03 10:37:25 +01:00
Samy Pessé fd78a06b11 Add message in gitbook command line 2015-02-03 10:37:21 +01:00
Samy Pessé 80efc8e086 Accept alpha and beta version of plugins 2015-02-02 22:07:40 +01:00
Samy Pessé c0dcb886ef Improve install command to resolve best plugin version 2015-02-02 21:45:14 +01:00
Samy Pessé df39128894 Fix #575: update logo and use retina favicon 2015-02-02 17:17:54 +01:00
Samy Pessé 3ab5b57b4f Merge pull request #573 from gbonanome/version/2.0
Add italian translation
2015-01-31 09:38:24 +01:00
Giulio Bonanome 49174d1731 Add italian translation 2015-01-31 00:51:10 +01:00
Samy Pessé ed3b77b655 Don't use xmlMode to normalize html 2015-01-30 17:47:19 +01:00
Samy Pessé 55a1aee164 Fix validation of semi-loaded plugins 2015-01-30 17:31:47 +01:00
Samy Pessé d7caf6686e Fix namespacing of plugins resources 2015-01-30 16:53:42 +01:00
Samy Pessé 71dad602ec Merge pull request #572 from AndreyAkinshin/version/2.0
Add theme/i18n/ru.json
2015-01-30 15:42:02 +01:00
Andrey Akinshin 30d8719c4b Add theme/i18n/ru.json 2015-01-30 16:39:16 +02:00
Samy Pessé ad81d38d07 Fix support of iframe in html normalization 2015-01-30 14:29:34 +01:00
Samy Pessé 6740b1b99c Use spaces identation 2015-01-30 13:56:21 +01:00
Samy Pessé f5f2cccbf4 Add "generator" to plugins blocks and filters ctx 2015-01-30 13:16:27 +01:00
Samy Pessé f45a1faa63 Fix #515: Update parsers and add reStructuredText support 2015-01-30 12:51:57 +01:00
Samy Pessé 5a0dbe8cb7 Remove useless log 2015-01-30 11:42:18 +01:00
Samy Pessé 2eb45e4699 Fix #567: Fail hard if book.json is invalid 2015-01-30 11:37:35 +01:00
Samy Pessé f7039a174a Add warning message when gitbook version to use is not fixed 2015-01-30 11:33:31 +01:00
Samy Pessé 5690704444 Download external images in ebook format 2015-01-29 22:42:41 +01:00
Samy Pessé 8a8ffb6134 Fix #565: fix relative path to fonts 2015-01-29 21:22:20 +01:00
Samy Pessé 5b23dd8ffd Fix ebook commands 2015-01-29 18:41:51 +01:00
Samy Pessé 5fc0c6b507 Convert inline svg as png if needed 2015-01-29 18:16:23 +01:00
Samy Pessé c8e72092c7 Fix inline svg image in page 2015-01-29 16:21:36 +01:00
Samy Pessé 9172948c18 Remove used blocks from templates 2015-01-29 14:18:13 +01:00
Samy Pessé 2bec7c5e10 Add option to block to not parse output 2015-01-29 14:09:27 +01:00
Samy Pessé 907ce6dbcd Fix copy of plugins assets to output 2015-01-28 22:54:19 +01:00
Samy Pessé 898000a580 Fix resources loading in plugins 2015-01-28 19:39:41 +01:00
Samy Pessé f95beed58b Fix log option to command line commands 2015-01-28 19:18:01 +01:00
Samy Pessé 4baaa41ac9 Fix for good path resolution of svg image 2015-01-28 18:34:09 +01:00
Samy Pessé 1f7d349aed Fix conversion of svg when cache is empty 2015-01-28 18:27:27 +01:00
Samy Pessé c0d922560d Improve svg conversion and links normalization 2015-01-28 18:23:48 +01:00
Samy Pessé f5ded5e5e1 Fix absolute path calcul
Add tests for links module
2015-01-28 17:52:10 +01:00
Samy Pessé 56d1720aae Improve image conversion to png to avoid doublons 2015-01-28 16:36:17 +01:00
Samy Pessé 02d7102c68 Add svgexport to travis tests 2015-01-28 14:55:59 +01:00
Samy Pessé 592b32b036 Fix error message when svgexport is not installed 2015-01-28 14:54:29 +01:00
Samy Pessé 1213997341 Use svgexport instead of imagemagick 2015-01-28 14:53:27 +01:00
Samy Pessé 4e620f34ad Handle correctly path starting with "/" 2015-01-28 13:17:41 +01:00
Samy Pessé 534edaceac Handle absolute url for images conversion (svg -> png) 2015-01-28 12:46:15 +01:00
Samy Pessé ee13b4bf75 Improve error message when imagemagick is not installed 2015-01-28 12:22:26 +01:00
Samy Pessé 924006d508 Add test for svg conversion 2015-01-28 12:02:36 +01:00
Samy Pessé 350f34561a Show log from ebook-convert in realtime for debugging 2015-01-28 11:52:24 +01:00
Samy Pessé 324fbb2f69 Convert svg images to png in ebook 2015-01-28 11:43:44 +01:00
Samy Pessé 5539ab48a5 Fix init of livereload plugin on serve 2015-01-28 11:28:21 +01:00
Samy Pessé c25157fc40 Fix display of plugins count 2015-01-28 11:10:30 +01:00
Samy Pessé d1b2950993 Check parser used before applying template shortcuts 2015-01-27 22:34:48 +01:00
Samy Pessé d8995200e6 Add options "shortcuts" for blocks from plugins 2015-01-27 22:27:59 +01:00
Samy Pessé bc389ab4a1 Improve tests on blocks extensions 2015-01-27 21:47:14 +01:00
Samy Pessé cf12d09092 Complete parsing of blocks from plugin extensions 2015-01-27 21:39:41 +01:00
Samy Pessé 8e18adbe4c Parse correctly list of blocks and args 2015-01-27 21:21:01 +01:00
Samy Pessé f1b5e3de0c Add test for block defined with special end tag 2015-01-27 17:16:59 +01:00
Samy Pessé 0e0a23b794 Add blocks from plugins to template engine 2015-01-27 17:07:25 +01:00
Samy Pessé d9329295d6 Add test for extending template blocks 2015-01-27 16:04:49 +01:00
Samy Pessé 750fb862d4 Improve tests for plugins with filters 2015-01-27 15:58:28 +01:00
Samy Pessé 131c19dee6 Move plugin helper methods to a specific class 2015-01-27 15:22:42 +01:00
Samy Pessé 870ca4da0c Extend templates filters
Fully remove mathjax from plugins
2015-01-27 12:56:04 +01:00
Samy Pessé 54e1be1e77 Parse plugins in book instead of generator 2015-01-27 12:49:08 +01:00
Samy Pessé ce259eb4ec Remove mathjax plugin from default installed 2015-01-27 12:35:37 +01:00
Samy Pessé 7424395171 Fix invalid log call in website generator 2015-01-27 12:17:20 +01:00
Samy Pessé 6125df5834 Add back "page:before" hook 2015-01-27 11:15:16 +01:00
Samy Pessé 99cd3148ba Log output from ebook-convert when debug is enabled 2015-01-27 09:37:38 +01:00
Samy Pessé 1a1ace0ae1 Transform as absolute output path in configuration 2015-01-26 22:21:09 +01:00
Samy Pessé 22bae559db Fix #273: clean the output without removing .git and .svn 2015-01-26 22:19:17 +01:00
Samy Pessé 5cee07ee8c fs.list take option for files to ignores 2015-01-26 21:59:55 +01:00
Samy Pessé 4250f559b0 Extract filters from plugins 2015-01-26 21:42:30 +01:00
Samy Pessé 3293c39450 Add test for plugin resources 2015-01-26 16:08:39 +01:00
Samy Pessé 5b447d030b Add some tests for plugins validation 2015-01-26 15:51:33 +01:00
Samy Pessé f02680dc73 Transform page after hook "page" 2015-01-26 13:19:42 +01:00
Samy Pessé cee7643f00 Call hook "init" when preparing generator 2015-01-26 12:54:29 +01:00
Samy Pessé a53ec62e4a Fix logs for deprecated hooks 2015-01-26 12:42:01 +01:00
Samy Pessé 795da31af8 Add warning message about deprecated hooks 2015-01-26 12:05:50 +01:00
Samy Pessé f70c29fea5 Improve logs for plugins installation 2015-01-26 11:57:28 +01:00
Samy Pessé decd8dbdc5 Add infos in readme about variables and templating 2015-01-26 10:32:19 +01:00
Samy Pessé edc954f2ac Add chapter in readme about conrefs 2015-01-26 10:19:10 +01:00
Samy Pessé 93bc86a0fe Fix git url parsing for content references 2015-01-26 10:12:26 +01:00
Samy Pessé 75b217fc24 Support ssh urls in git parsing 2015-01-26 09:12:00 +01:00
Samy Pessé d6b44cc149 Add tests for parsing git url 2015-01-26 08:05:07 +01:00
Samy Pessé 0f0ec4cf3d Improve logger 2015-01-25 15:27:40 +01:00
Samy Pessé 06ab84abe3 Improve logger module 2015-01-25 14:55:37 +01:00
Samy Pessé bb5761e885 Change comment start and end in templates
(to avoid conflicts with maths)
2015-01-25 13:54:25 +01:00
Samy Pessé d60a9763cd Fix loading of plugins when book root is relative 2015-01-25 12:08:52 +01:00
Samy Pessé e366c05238 Add logs on plugins loading 2015-01-25 12:06:52 +01:00
Samy Pessé 690cdbe505 Improve output of book.log 2015-01-25 12:03:19 +01:00
Samy Pessé fd701fc37c Add color to output 2015-01-25 11:55:33 +01:00
Samy Pessé 4d38baf7a2 Add _book to list of files to ignore 2015-01-25 11:55:25 +01:00
Samy Pessé 7a999e01d5 Fix link in features list 2015-01-25 11:35:32 +01:00
Samy Pessé f3a5464306 Add AsciiDoc to readme 2015-01-25 11:32:54 +01:00
Samy Pessé 2c57a37afb Add command to init a book 2015-01-24 23:14:32 +01:00
Samy Pessé 244b0f8efc Add back readme 2015-01-24 21:34:18 +01:00
Samy Pessé b4dc31e82b Add command to generate all ebooks 2015-01-24 18:56:18 +01:00
Samy Pessé 1583ede3d7 Add Book.generateFile to output a pdf 2015-01-24 18:54:20 +01:00
Samy Pessé 572994a17e Add more tests for inclusion 2015-01-24 18:18:43 +01:00
Samy Pessé 796dd4c13a Add base loader to resolve git dependencies 2015-01-24 18:04:03 +01:00
Samy Pessé e02f29e763 Add test for local inclusion 2015-01-24 15:33:04 +01:00
Samy Pessé 6805b02c63 Improve german translation of "Introduction" 2015-01-24 12:53:06 +01:00
Samy Pessé 21949ccd04 Update gitbook-parsers@0.2.2 2015-01-24 12:38:36 +01:00
Samy Pessé 7b766f692b Return to version 0.5.1 of gaze 2015-01-24 12:34:01 +01:00
Samy Pessé c9aca86dcc Watch all fiels that can be parsed in serve 2015-01-24 12:29:04 +01:00
Samy Pessé 99d9d49610 Improve navigation with non existant entries
Use i18n for introduction title
2015-01-24 12:18:23 +01:00
Samy Pessé fd98746c2b Update gitbook-parsers and improve summary tests 2015-01-24 11:57:21 +01:00
Samy Pessé 2efd234824 Complete serve command 2015-01-24 11:23:53 +01:00
Samy Pessé 8be4304b5c Fix translation of White theme in german 2015-01-24 10:54:31 +01:00
Samy Pessé c984eba2bc Fix german translation 2015-01-23 23:21:56 +01:00
Samy Pessé 2bb2a2a330 Normalize language to support: "fr-FR" 2015-01-23 23:06:10 +01:00
Samy Pessé f12163534d Add german transalation
Based on PR by @winniehell
2015-01-23 22:51:23 +01:00
Samy Pessé e9d7c71911 Fix fr i18n texts 2015-01-23 22:46:07 +01:00
Samy Pessé 59b3b0fadd Translate ebook 2015-01-23 22:33:24 +01:00
Samy Pessé b426aba846 Add french translations for website 2015-01-23 22:29:40 +01:00
Samy Pessé 89885e17a5 Set translation to use in templates 2015-01-23 22:12:58 +01:00
Samy Pessé b66e9d2bed Load locales to translate layout 2015-01-23 22:05:11 +01:00
Samy Pessé d0f910d65f Fix language parsing test 2015-01-23 18:59:01 +01:00
Samy Pessé 8e41aca8a7 Fix #517: fix weibo sharing class in header 2015-01-23 18:53:48 +01:00
Samy Pessé 4fec7760a1 Fix error in book parsing 2015-01-23 18:50:10 +01:00
Samy Pessé e6cbea9c70 Change name config "lang" to "language" 2015-01-23 18:48:49 +01:00
Samy Pessé 7945f19285 Fix display of log type 2015-01-23 18:40:42 +01:00
Samy Pessé 5bd1269268 Accept argument "log" from commands 2015-01-23 18:39:07 +01:00
Samy Pessé 1fdd8a7611 Improve logs in book parser 2015-01-23 18:21:03 +01:00
Samy Pessé 112782d118 Add logs methods to Book object 2015-01-23 18:03:15 +01:00
Samy Pessé b88b4a0578 Add context configuration to Book object 2015-01-23 17:40:53 +01:00
Samy Pessé a964e19715 Fix paths indexed in search index 2015-01-23 16:17:04 +01:00
Samy Pessé c47166d700 Fix version in generator tag 2015-01-23 16:04:10 +01:00
Samy Pessé 77834f8166 Accept format argument to build command 2015-01-23 15:51:19 +01:00
Samy Pessé 0fc34e0a35 Use npm version of gitbook-parsers 2015-01-23 15:25:13 +01:00
Samy Pessé df16f3d90d Test ebook generation instead of pdf output 2015-01-23 14:10:45 +01:00
Samy Pessé ccd2f6ecdc Check that readme file is not ignored when parsing 2015-01-23 12:11:27 +01:00
Samy Pessé 19f1d31758 Add base for ebook generator 2015-01-23 00:16:46 +01:00
Samy Pessé ee2420cbcc Change name of generator "site" to "website" 2015-01-22 22:57:08 +01:00
Samy Pessé 322cf59a34 Use html link in search index 2015-01-22 22:46:35 +01:00
Samy Pessé 66841b1bb7 Remove glossary from theme js 2015-01-22 22:36:59 +01:00
Samy Pessé 74ea419c53 Write search index to output folder 2015-01-22 22:31:47 +01:00
Samy Pessé 0c8513801c Write glossary for website 2015-01-22 21:52:13 +01:00
Samy Pessé 90789aa5b9 Use container-based Travis CI stack
cc @winniehell
2015-01-22 21:21:27 +01:00
Samy Pessé 1d13624d3d Replace glossary terms by links to glossary 2015-01-22 19:45:14 +01:00
Samy Pessé 9e172549d1 Mark glossary terms in html parsed 2015-01-22 19:09:03 +01:00
Samy Pessé 0ba36c6f4e Use external gitbook parsers 2015-01-22 18:21:30 +01:00
Samy Pessé c47093e82a Add test for generation of multilingual book 2015-01-22 17:15:06 +01:00
Samy Pessé 57bdc102bc Fix tests of generations 2015-01-22 17:12:46 +01:00
Samy Pessé be651f0138 Separate test for geenration 2015-01-22 17:06:33 +01:00
Samy Pessé 974819bafb Remove parent extension for nunjucks 2015-01-22 17:03:51 +01:00
Samy Pessé f31a272b1f Add options "input" that equals root of the book 2015-01-21 23:04:55 +01:00
Samy Pessé a492c66321 Copy files from book to output
Add test for personalised styles
2015-01-21 23:03:09 +01:00
Samy Pessé 85930161f7 Normalize tests 2015-01-21 22:51:59 +01:00
Samy Pessé 4f9ece1ec1 Fix display of summary in website 2015-01-21 20:26:29 +01:00
Samy Pessé 748f004609 Add summary utils 2015-01-21 20:21:55 +01:00
Samy Pessé cb709930bb Normalize summary in gitbook 2015-01-21 20:21:47 +01:00
Samy Pessé 98027cb008 Fix path to assets in website 2015-01-21 20:04:03 +01:00
Samy Pessé 3d8db17bc3 Copy theme assets to output 2015-01-21 19:51:35 +01:00
Samy Pessé 3c605ea809 generate an index.html for entry points 2015-01-21 19:40:40 +01:00
Samy Pessé bd931c5cbd Adapt themes website templates 2015-01-21 19:15:57 +01:00
Samy Pessé c6e3a27284 Add normalization of plugins 2015-01-21 13:36:09 +01:00
Samy Pessé b5d7e7966f Fix site generator polluting basegenerator 2015-01-21 11:43:39 +01:00
Samy Pessé be2da0fab1 Fix json template preparation 2015-01-21 11:33:33 +01:00
Samy Pessé 0da50819d6 Fix asciidoc test generation 2015-01-21 11:28:59 +01:00
Samy Pessé 9f9265ad76 Add asciidoc support 2015-01-21 10:17:58 +01:00
Samy Pessé a5ebbc34c7 Add styles configuration in book.json 2015-01-20 13:30:22 +01:00
Samy Pessé 33e32b20d9 Use latest version fo theme 2015-01-20 11:32:11 +01:00
Samy Pessé 238cd9f7f0 Add back theme 2015-01-20 11:29:26 +01:00
Samy Pessé bac25cdf29 Improve tests for multilingual json output 2015-01-19 22:44:17 +01:00
Samy Pessé c15399613c Normalize paths in html content 2015-01-19 22:39:23 +01:00
Samy Pessé d6f7f1f4fb Add test for parsing of navigation 2015-01-19 21:19:41 +01:00
Samy Pessé a67dad8cbe Fix list of files and parsing of navigation files only 2015-01-19 21:16:06 +01:00
Samy Pessé b549158483 Parse navigation and progress in parsed page 2015-01-19 20:54:59 +01:00
Samy Pessé 9a2b03b183 Handle different structure in json multilingual output 2015-01-19 20:46:05 +01:00
Samy Pessé 2b2b4f46c2 Complete json generator for multilingual book
Add tests
2015-01-19 20:41:34 +01:00
Samy Pessé 6344928580 Write file in json generator 2015-01-19 18:49:02 +01:00
Samy Pessé 2dbf9b0d4a Start adapting json generator 2015-01-19 17:32:59 +01:00
Samy Pessé be4d2679a4 Add base test for generation 2015-01-19 17:25:16 +01:00
Samy Pessé 70189a5ee2 Fix parsing of plugins list 2015-01-19 16:59:42 +01:00
Samy Pessé ad233c0d8e Improve plugins installation to prevent installation of default plugins 2015-01-19 16:53:20 +01:00
Samy Pessé 41c3db4c04 Add command to install plugins 2015-01-19 16:30:52 +01:00
Samy Pessé 7f01f4b048 Add plugin object 2015-01-19 16:23:44 +01:00
Samy Pessé 5ed5844490 Parse list of files and add test for it 2015-01-19 16:00:12 +01:00
Samy Pessé 777963c97c Change name of Book.init to Book.parse 2015-01-19 15:52:35 +01:00
Samy Pessé 2f3d6f8dff Improve test for parsing multilanguages books 2015-01-19 15:48:22 +01:00
Samy Pessé 2ae3924365 Fix parsing of langs 2015-01-19 15:46:34 +01:00
Samy Pessé d172aaefee Add test for glossary parsing 2015-01-19 15:31:58 +01:00
Samy Pessé f0ea7bc53b Add test for readme parsing 2015-01-19 15:28:13 +01:00
Samy Pessé 793600ab2f Improve test to test a all book 2015-01-19 15:26:31 +01:00
Samy Pessé 354b4afba1 Parse readme 2015-01-19 15:12:08 +01:00
Samy Pessé 8569a74103 Use template before parsing glossary, summary and langs 2015-01-19 15:05:13 +01:00
Samy Pessé b314972195 Add configuration to send structures files 2015-01-19 14:51:35 +01:00
Samy Pessé 52179163dd Fix output for sub-books in multi languages 2015-01-19 13:27:17 +01:00
Samy Pessé 8ed9f645fc Fix parsing of langs
Add command to build
2015-01-19 13:22:34 +01:00
Samy Pessé 60993d3123 Parse langs and glossary 2015-01-19 12:06:04 +01:00
Samy Pessé 39b2aaf489 Add parsing of summary and associated tests 2015-01-19 10:59:43 +01:00
Samy Pessé 58b04da60f Add test for loading configuration from book.js 2015-01-19 10:15:53 +01:00
Samy Pessé d9c020194d Add base object to load configuration 2015-01-19 10:08:49 +01:00
Samy Pessé ec586dd3cd Clear folder 2015-01-19 09:47:36 +01:00
Samy Pessé 8043216170 Merge pull request #555 from GitbookIO/feature/styles
Add custom styles options
2015-01-15 06:29:14 +01:00
Samy Pessé 3c7372dff5 Add doc about custom styles 2015-01-14 22:53:27 +01:00
Samy Pessé 2cab018791 Merge pull request #553 from GitbookIO/fix/summary-whitespace
Fix/summary whitespace
2015-01-14 22:41:34 +01:00
Samy Pessé 9467cda131 Apply styles to ebook output 2015-01-14 22:31:36 +01:00
Samy Pessé 3382f1b084 Include styles in website format 2015-01-14 22:19:40 +01:00
Aaron O'Mullan ae52ef5b78 Handle whitespace between lists in SUMMARY.md 2015-01-14 22:17:15 +01:00
Aaron O'Mullan f88951546d Add test/bin/summary.js
Another small useful tool
2015-01-14 22:16:37 +01:00
Samy Pessé 7d90cd2a9e Add default values for "styles" 2015-01-14 22:08:42 +01:00
Samy Pessé 94a9db3d54 Move theme folder "book" to "website" 2015-01-14 22:08:31 +01:00
Aaron O'Mullan 3f36d83bfb Add test/bin/lex.js utility
Helpful binary when developing / debugging
2015-01-14 21:56:57 +01:00
Aaron O'Mullan c7e3370ac0 Add summary whitespace tests 2015-01-14 21:45:32 +01:00
Samy Pessé bcb34dcd79 Merge pull request #547 from JozoVilcek/patch-2
Change link to mermaid plugin
2015-01-12 00:13:17 +01:00
Jozef Vilcek 71905aa0c8 Change link to mermaid plugin
Repository name for mermaid plugin has changed to better reflect naming conventions
2015-01-12 00:11:02 +01:00
Samy Pessé dcbf115f25 Adapt platform command to gitbook.com 2015-01-11 15:14:50 +01:00
Samy Pessé 668f2f9e2c Update urls in package.json 2015-01-11 15:09:25 +01:00
Samy Pessé 530975b4fb Improve names of plugins 2015-01-11 14:53:57 +01:00
Samy Pessé d35ce31af2 Merge pull request #545 from JozoVilcek/patch-1
Enlist gitbook-mermaid plugin
2015-01-11 14:52:49 +01:00
Jozef Vilcek e5f17cd694 Enlist gitbook-mermaid plugin 2015-01-11 13:52:11 +01:00
Aaron O'Mullan b50dea9c70 Update kramed to 0.4.4 2015-01-06 15:15:48 +01:00
Aaron O'Mullan 6b76032bb1 Bump version to 1.5.0 2014-12-17 17:08:09 +01:00
Aaron O'Mullan 85b09eda56 Add 1.4.2's changes 2014-12-17 17:07:10 +01:00
Aaron O'Mullan c4be78a81f Merge pull request #506 from Nijikokun/feature/clarity-midnight
Clarity: Midnight Theme (Night)
2014-12-16 18:53:09 +01:00
Aaron O'Mullan e7309f71d2 Merge pull request #530 from GitbookIO/fix/cli-error-handling
Robust CLI error handling
2014-12-16 17:46:01 +01:00
Aaron O'Mullan 6c979c9419 Robust CLI error handling
Fixes #528, fixes #524, fixes #518
2014-12-16 16:05:50 +01:00
Aaron O'Mullan 7fd7732345 Bump version to 1.4.2 2014-12-10 23:27:01 +01:00
Aaron O'Mullan 960ef4d945 Fix descriptor and me leak with bad write queues
Node.js for some reason queues up writes from fs.writeFile causing
memory and descriptor leaks, preventing building large books. This is
now fixed

Fixes #336, fixes #454
2014-12-10 16:32:49 +01:00
Aaron O'Mullan 92523fb4b5 Force process.exit at end of build 2014-12-09 17:31:23 +01:00
Samy Pessé 66bb46df22 Merge pull request #516 from jr0cket/patch-1
Add gitbook init command to "how to use" section
2014-12-08 00:46:04 +01:00
John Stevenson 0454710d3b Add gitbook init command to "how to use" section
Highlight that Gitbook can generate the files and directories for a book from its SUMMARY.md file.
2014-12-07 23:44:41 +00:00
Samy Pessé c9a31cc987 Add option isbn for books 2014-12-03 23:19:47 +01:00
Samy Pessé f03c316274 Bump version to 1.4.1 2014-11-30 17:01:47 +01:00
Samy Pessé 001a4f91be Fix command 'install' without arguments 2014-11-30 17:00:12 +01:00
Samy Pessé 44a3bfe296 Bump version to 1.4.0 2014-11-30 14:59:08 +01:00
Samy Pessé a9ce6f808d Add doc about gitbook install 2014-11-30 14:54:34 +01:00
Samy Pessé e5c667e0fa Add test for parsing of plugins list with version 2014-11-30 14:50:47 +01:00
Samy Pessé e36f274dbe Handle specific version for plugins 2014-11-30 14:46:21 +01:00
Samy Pessé 2a88427e74 Improve error message when plugins not found 2014-11-30 13:13:00 +01:00
Samy Pessé ed48f5f3ee Add command "install" to install plugins from book.json 2014-11-30 13:04:38 +01:00
Samy Pessé 7b4accb117 Bump version to 1.3.4 2014-11-25 18:55:33 +01:00
Samy Pessé d6437d31d4 Omit "generator" when building file with config file 2014-11-25 18:53:02 +01:00
Samy Pessé 3dd33f1f3c Merge pull request #502 from tonka3000/X-UA-Compatible-tag
add X-UA-Compatible meta tag for IE
2014-11-25 15:20:14 +01:00
Samy Pessé 3a0f00411b Fix #505: add glossary to ebook file 2014-11-24 22:08:19 +01:00
Samy Pessé 5b655bb29a Fx typo 2014-11-24 21:56:33 +01:00
Samy Pessé faf10605e8 Add hook finish:before 2014-11-24 21:41:50 +01:00
Nijiko Yonskai c52aecdbd5 Initial release 2014-11-22 20:37:06 -08:00
Samy Pessé 5c98b3350b Merge pull request #503 from jr0cket/ebook-command-fix
updated eBook generation commands - resolves #432
2014-11-22 16:41:19 +01:00
John Stevenson 6f4e1d10c1 made commands clearer by seperating output filename option 2014-11-22 14:47:12 +00:00
John Stevenson 870886599a updated eBook generation commands 2014-11-22 11:26:08 +00:00
Michael Aigner 1b9801b8a4 add X-UA-Compatible meta tag for IE (https://github.com/GitbookIO/gitbook/issues/501) 2014-11-20 10:16:40 +01:00
Samy Pessé 897463b22e Bump version to 1.3.3 2014-11-14 22:10:19 +01:00
Samy Pessé aa4894e94c Fix parsing of already lexed content 2014-11-14 22:09:30 +01:00
Samy Pessé 8a0180ae6b Bump version to 1.3.2 2014-11-14 18:53:57 +01:00
Samy Pessé d6776b98d3 Fix bundle task 2014-11-14 18:53:48 +01:00
Samy Pessé 6a04ba89a5 Update gitbook bundle 2014-11-14 18:53:39 +01:00
Samy Pessé 05eaeeb0d0 Fix #441: Improve validity of epub output 2014-11-13 08:34:35 +01:00
Samy Pessé 77043464b2 Fix #496: improve replacement of glossary terms in html 2014-11-12 22:10:38 +01:00
Samy Pessé b90f74a821 Merge pull request #494 from cnDelbert/master
Bug fix for issue#493: 2-level or more deep relative links go wrong when in win32
2014-11-12 19:06:33 +01:00
Aaron O'Mullan f804eb42ac Merge pull request #495 from romanlytkin/patch-1
Update README.md
2014-11-07 15:58:18 +01:00
romanlytkin 74fd8c159b Update README.md
Hi, i add 2 new plugins for gitbook, I hope it will be useful to someone.
2014-11-07 17:37:27 +03:00
Delbert a741ec1350 Bug fix for issue #493: 2-level or more deep relative links may go wrong when in win32 2014-11-06 15:50:59 +08:00
Samy Pessé 4b26c31b3b Remove unnecessary option "repo" from tests 2014-11-03 17:43:44 +01:00
Samy Pessé dfd487692a Adapt new page.parse api for tests 2014-11-03 17:40:38 +01:00
Samy Pessé 195374ecee Merge pull request #489 from codepiano/fork
bugfix,init page object's lexed attribute.
2014-11-03 17:29:54 +01:00
Samy Pessé 1659778a77 Bump version to 1.3.1 2014-11-03 16:58:37 +01:00
Samy Pessé 40c4035afb Fix #491: handle correctly url without path for links 2014-11-03 16:57:35 +01:00
codepiano 1502d65286 Merge branch 'master' of github.com:GitbookIO/gitbook into fork
Conflicts:
	lib/parse/page.js
2014-11-02 01:34:28 +08:00
Samy Pessé 0feb672d70 Bump version to 1.3.0 2014-11-01 15:36:10 +01:00
Samy Pessé 2a4ef5bbf4 Fix dependency in renderer 2014-11-01 15:21:16 +01:00
Samy Pessé c4540332a7 Bundle gitbook parser as a client side library 2014-11-01 15:09:58 +01:00
Samy Pessé 2104404cb3 Remove dependency for path in parser 2014-11-01 15:09:41 +01:00
Samy Pessé 002d685ebb Adapt include in page parsing to works on clientside 2014-11-01 15:00:13 +01:00
codepiano 9da10e2b25 bugfix,init page object's lexed attribute.
the page object's attribute 'lexed' is not correctly inited.so when
use page.lexed to generate the search index file,nothing will be
indexed.
2014-10-31 18:00:09 +08:00
Samy Pessé 1e4632080b Bump version to 1.2.0 2014-10-30 18:27:56 +01:00
Samy Pessé 5191106396 Update documentation in readme 2014-10-30 18:27:26 +01:00
Samy Pessé 6ab5cafaf3 Improve toc in ebook (Fixes #338, #322) 2014-10-30 18:14:06 +01:00
Samy Pessé d1c0cb9c71 Normalize template paths 2014-10-30 18:01:56 +01:00
Samy Pessé 38c750101c Fix #487: Use generated toc as base for ebook generation 2014-10-30 17:52:39 +01:00
Samy Pessé ba0f379a89 Add plugin infinitescroll 2014-10-30 16:19:19 +01:00
Samy Pessé 1d9509b3b6 Merge pull request #485 from citizenmatt/Nested_list_spacing
Fix bottom border on nested ul
2014-10-30 14:15:54 +01:00
Samy Pessé 3c8de4a8ab Add 'variables' to book.json doc 2014-10-29 17:20:30 +01:00
Samy Pessé 591d8fc9ee Use book.json to store variables 2014-10-29 17:19:19 +01:00
Samy Pessé d480e9cea4 Update highlight.js@8.3.0 2014-10-23 17:05:27 +02:00
Matt Ellis 32a71869da Fix bottom border on nested ul 2014-10-23 09:19:02 +01:00
Samy Pessé fdbfda64a6 Merge pull request #483 from dharmeshkakadia/patch-1
Corrected a small typo in Plugins description
2014-10-21 15:45:45 +02:00
Dharmesh Kakadia a6f48be39e Corrected a small typo in Plugins description 2014-10-21 00:47:37 +05:30
Aaron O'Mullan ce50b893e9 Merge pull request #482 from GitbookIO/feature/includes
Add include support, with variables and the whole shebang
2014-10-15 21:29:23 +02:00
Aaron O'Mullan 225ff27dd1 Fix small incompatibility mistake 2014-10-15 21:28:53 +02:00
Aaron O'Mullan 52e8361846 Add include support, with variables and the whole shebang
Fixes #462
2014-10-15 21:20:10 +02:00
Samy Pessé 49e3bbc3d6 Bump version to 1.1.1 2014-10-13 16:03:47 +02:00
Aaron O'Mullan c9ec911d0e Merge pull request #472 from GitbookIO/fix/quizzes
Fix/quizzes
2014-10-13 15:55:32 +02:00
Aaron O'Mullan 84d7662fd8 Allow quiz titles in validation 2014-10-13 15:55:59 +02:00
Aaron O'Mullan 676b2287ee Rewrite quiz logic to be more robust
Fixes #470
2014-10-13 15:50:12 +02:00
Samy Pessé 32a64be407 Fix #460: search for glossary term on all nodes 2014-10-13 15:23:51 +02:00
Samy Pessé 138cddf3d6 Ste GitBook as publisher 2014-10-13 15:23:00 +02:00
Aaron O'Mullan d730a3aac6 Split section classification to separate files
Maybe have a sections folder in the future and one per type
(quiz/exercise/normal) ?
2014-10-13 15:07:46 +02:00
Aaron O'Mullan ffb0b12012 Add unit test for quiz false positivies 2014-10-13 14:17:08 +02:00
Samy Pessé 78e25f8252 Add link to gitbook.gitignore 2014-10-13 11:10:28 +02:00
Samy Pessé 09f42f57e1 Improve comments 2014-10-13 11:01:00 +02:00
Samy Pessé 950ad1d060 Remove reference to single page output 2014-10-13 10:59:32 +02:00
Samy Pessé ed6b29c1a4 Adapt tests 2014-10-13 10:57:47 +02:00
Samy Pessé 36785120b1 Fix default for pdf templates 2014-10-13 10:56:14 +02:00
Samy Pessé 26fad5b888 Remove ref to github options 2014-10-13 10:53:51 +02:00
Samy Pessé f049576575 Merge branch 'alexandreaquiles-pdf_header_and_footer' 2014-10-13 10:48:33 +02:00
Samy Pessé a7ecbb87e4 Force type of header and footer templates 2014-10-13 10:48:02 +02:00
Samy Pessé 1da341741b Merge pull request #471 from GitbookIO/feature/multipages
Use multiple HTML files for ebook generation
2014-10-13 10:41:23 +02:00
Samy Pessé 2dd6192576 Remove old codes 2014-10-12 22:25:39 +02:00
Samy Pessé 52c9347b9c Improve style for exercises and quizzes in ebook 2014-10-12 22:07:37 +02:00
Samy Pessé e2295591ed Handle correctly quizzes 2014-10-12 11:42:32 +02:00
Samy Pessé 0c1cb724d7 Handle correctly exercises in ebook 2014-10-12 11:37:51 +02:00
Samy Pessé 7cee248426 Generate multiple page for ebook 2014-10-12 11:32:45 +02:00
Aaron O'Mullan 6ea5b2b539 Fix incorrect page breaks after h1 and h2 divs
Fixes #463, fixes #337, fixes #293
2014-10-08 20:08:22 +02:00
Alexandre Aquiles a56b559d43 Adding options to set header and footer in PDF generation 2014-10-06 13:17:34 -03:00
Samy Pessé df1a82e0e4 Add changelog for 1.1.0 2014-09-24 14:57:50 +02:00
Samy Pessé f15a1efe71 Bump version to 1.1.0 2014-09-24 14:56:09 +02:00
Samy Pessé b8379fc6fa Update kramed@0.4.3 2014-09-24 14:53:42 +02:00
Samy Pessé d69cc74647 Merge pull request #440 from GitbookIO/feature/ebookplugins
Apply plugins to ebook generation
2014-09-17 15:02:22 +02:00
Samy Pessé f83874a9ca Fix correct folder for assets 2014-09-17 14:16:04 +02:00
Samy Pessé 1968bb79c2 Use correct resources file for ebook 2014-09-17 13:17:30 +02:00
Samy Pessé 60c721db40 Enable css from plugins in page output 2014-09-17 13:10:36 +02:00
Samy Pessé 11b299653f Remove githubId
Enable hook for ebook
2014-09-17 13:08:30 +02:00
Samy Pessé c8c07fe8ce Add base to use plugins in page format 2014-09-17 13:00:28 +02:00
Samy Pessé be0a4f08e9 Merge pull request #435 from markcxz/master
fix radio input uncheck failed
2014-09-10 09:56:12 +02:00
markcxz 4da4e28d07 fix radio input uncheck failed 2014-09-10 10:27:27 +08:00
Samy Pessé 9dc19026c6 Add changelog for version 1.0.3 2014-09-05 17:15:04 +02:00
Samy Pessé 31ca9f7fcb Bump version to 1.0.3 2014-09-05 17:12:03 +02:00
Samy Pessé 6925d39e8d Fix GitbookIO/editor#136: update highlight.js@8.2.0 2014-09-05 17:10:47 +02:00
Samy Pessé a24cafd975 Update plugin-mathjax@0.0.6 2014-09-02 23:25:33 +02:00
Aaron O'Mullan e3fdf8b404 Bump version to 1.0.2 2014-08-20 17:03:31 -07:00
Aaron O'Mullan c861223378 Use gitbook-plugin-mathjax@0.0.5 2014-08-20 17:01:14 -07:00
Aaron O'Mullan f5c6d8df71 Bump version to 1.0.1 2014-08-20 16:30:04 -07:00
Aaron O'Mullan 452c846e90 Update framed to 0.4.2
This uses the new kramdown convention for inline math
2014-08-20 16:27:50 -07:00
Samy Pessé aedd875ab7 Fix url for sharing 2014-08-20 15:54:11 -07:00
Samy Pessé d2ec2f8f02 Add plugins exercises and quizzes by default 2014-08-20 10:33:34 -07:00
Samy Pessé 5af0df93c3 Normalize titles in readme 2014-08-19 16:39:54 -07:00
Samy Pessé 5b4b5e1af7 Bump version to 1.0.0 2014-08-19 16:18:29 -07:00
Samy Pessé 21704c4ac2 Add documentation for glossary format 2014-08-19 16:16:59 -07:00
Aaron O'Mullan 71aff10092 Merge pull request #413 from GitbookIO/feature/glossary
Glossary terms highlight in page
2014-08-19 16:09:47 -07:00
Samy Pessé 4b390667e5 Replace whole word with glossary term 2014-08-19 16:10:02 -07:00
Samy Pessé 14dda3148d Improve replace of words with glossary links 2014-08-19 16:05:57 -07:00
Samy Pessé 511dc9f805 Add action on glossary term in the page 2014-08-19 15:40:37 -07:00
Samy Pessé d0a14dcbcc Write correct glossary to disk 2014-08-19 10:36:27 -07:00
Samy Pessé ec8dc2cdb0 Improve design for glossary page 2014-08-18 22:00:34 -07:00
Samy Pessé 6f1ef585be Show index on glossary page 2014-08-18 21:53:07 -07:00
Samy Pessé d84acd0b82 Dump glossary index to glossary_index.json 2014-08-18 21:32:10 -07:00
Samy Pessé e1723ad3d5 Remove debug 2014-08-18 21:25:27 -07:00
Samy Pessé f77404afe1 Merge pull request #411 from GitbookIO/glossary-search
Glossary search
2014-08-18 19:14:14 -07:00
Aaron O'Mullan fbd7522748 Replace kramed-text-renderer references
with “kramed-text-renderer”
2014-08-18 18:36:38 -07:00
Aaron O'Mullan 90320fd26c Add glossary indexing 2014-08-18 17:25:26 -07:00
Aaron O'Mullan 4f9b474fdd Use kramed 0.4.1 2014-08-18 12:01:26 -07:00
Aaron O'Mullan 4c921bdc09 Use kramed-text-renderer (0.2.1) 2014-08-18 12:01:12 -07:00
Aaron O'Mullan 93e0864063 Fix dump typo in glossary's entryId func 2014-08-17 21:18:42 -07:00
Aaron O'Mullan 80e2f23714 Add "id" attribute to parsed glossary entries 2014-08-17 21:13:39 -07:00
Samy Pessé 00496dbd09 Add link to glossary if exists 2014-08-17 17:46:55 -07:00
Samy Pessé 54fb9a58d7 Add base less for glossary 2014-08-17 11:34:33 -07:00
Samy Pessé 6b0ad22b19 Base template for glossary 2014-08-17 11:26:20 -07:00
Samy Pessé 17f40540ae Parse glossary during build 2014-08-17 11:16:36 -07:00
Samy Pessé d337a500dc Merge pull request #408 from GitbookIO/feature-glossary
Feature glossary
2014-08-15 12:12:20 -07:00
Aaron O'Mullan cbe4e9edec Simplify output of glossary parsing to more simple objects 2014-08-15 12:09:36 -07:00
Samy Pessé 402e88f866 Add best practices for .gitignore 2014-08-15 11:55:01 -07:00
Aaron O'Mullan e0aed828e0 Add very basic glossary parsing test 2014-08-11 19:41:06 -07:00
Aaron O'Mullan 323facbdc8 Add glossary parser 2014-08-11 18:57:39 -07:00
Aaron O'Mullan 2a61c858e4 Add GLOSSARY.md fixture 2014-08-11 18:53:05 -07:00
Aaron O'Mullan d216661f27 Merge pull request #403 from GitbookIO/version/1.0.0
Version 1.0.0
2014-08-11 09:42:18 -07:00
Samy Pessé 50dfee99b6 Fix code coloration in book markdown 2014-08-11 09:37:25 -07:00
Samy Pessé 9e8ed9ac68 Use new markdown content css 2014-08-11 08:42:47 -07:00
Samy Pessé 3a54554713 Normalize markdown rendering with ebook (pdf, epub, mobi) 2014-08-11 08:38:11 -07:00
Samy Pessé fc113ce0c3 Fix style for table on night and sepia themes 2014-08-11 07:36:54 -07:00
Samy Pessé f605f0b6c8 Fix color or title for sepia and night 2014-08-11 07:36:54 -07:00
Samy Pessé 08788edfd3 Change list of plugins to a table 2014-08-11 07:36:54 -07:00
Samy Pessé 4a0e19935f Improve readme doc 2014-08-11 07:36:54 -07:00
Samy Pessé a3454bb7a8 Update preview 2014-08-11 07:36:53 -07:00
Samy Pessé 7245fa8ad7 Add icon for iOS 2014-08-11 07:36:53 -07:00
Samy Pessé d771ff341d Improve spacing between titles 2014-08-11 07:36:53 -07:00
Samy Pessé 8171241666 Remove quizzes handling from core 2014-08-11 07:36:53 -07:00
Samy Pessé 1125e89b49 Fix error with markdown rendering 2014-08-11 07:36:53 -07:00
Samy Pessé 67fd5a060b Add style for buttons 2014-08-11 07:36:52 -07:00
Samy Pessé 4f60eda3ee Add style for alerts
Remove style for exercises
2014-08-11 07:36:52 -07:00
Samy Pessé 6c62d75eae Remove exercises execution 2014-08-11 07:36:52 -07:00
Samy Pessé bcfcf3d979 Fix sharing to instapaper 2014-08-11 07:36:52 -07:00
Samy Pessé b4497029db Improve normalization of variables for dropdown 2014-08-11 07:36:52 -07:00
Samy Pessé 23ac4b846d Add sharing to instapaper 2014-08-11 07:36:51 -07:00
Samy Pessé 691d04e994 Improve dropdown to make it global
Add dropdown for sharing
2014-08-11 07:36:51 -07:00
Samy Pessé a54959ee4f Show title on hover of header 2014-08-11 07:36:51 -07:00
Samy Pessé 76ebca8c55 Fix url.js inclusion 2014-08-11 07:36:51 -07:00
Samy Pessé 786c4f147d Use fontawesome from bower 2014-08-11 07:36:51 -07:00
Samy Pessé 7028fc7118 Remove useless comments from less 2014-08-11 07:36:50 -07:00
Samy Pessé 1c3210e789 Fix display of input in search 2014-08-11 07:36:50 -07:00
Samy Pessé c28b804352 Remove bootstrap dependency 2014-08-11 07:36:50 -07:00
Samy Pessé 6d841c909b Remove appCache handling from client js 2014-08-11 07:36:50 -07:00
Samy Pessé e917fd28e0 Change term "Generated" to "Published" 2014-08-11 07:36:49 -07:00
Samy Pessé ab88459a56 Change default font 2014-08-11 07:36:49 -07:00
Samy Pessé fe95c361be Fix options parsing 2014-08-11 07:36:49 -07:00
Samy Pessé 7e78546516 Change links system for sidebar 2014-08-11 07:36:49 -07:00
Samy Pessé d07b9450b1 Fix defaults for configuration 2014-08-11 07:36:49 -07:00
Samy Pessé c30b431b7b Remove link home in header 2014-08-11 07:36:48 -07:00
Samy Pessé 0fe54fbd57 Fix navigation color for sepia and night 2014-08-11 07:36:48 -07:00
Samy Pessé 1bec19029f Fix navigation on mobile 2014-08-11 07:36:48 -07:00
Samy Pessé 21ccf7757e Improve padding on page content 2014-08-11 07:36:48 -07:00
Samy Pessé ceb766a124 Adapt to latest github markdown style 2014-08-11 07:36:48 -07:00
Samy Pessé 046b789fa8 Fix navigation links position 2014-08-11 07:36:47 -07:00
Samy Pessé cd50a1402f Fix border on search input (sepia and night) 2014-08-11 07:36:47 -07:00
Samy Pessé 1b0ae5a827 Fix border on dropdown selector 2014-08-11 07:36:47 -07:00
Samy Pessé 01b6ca76e8 Limit transitions to positions 2014-08-11 07:36:47 -07:00
Samy Pessé 06b9c345c9 Improve design of search bar 2014-08-11 07:36:47 -07:00
Samy Pessé 152276c1d7 Improve display of sidebar on mobile 2014-08-11 07:36:46 -07:00
Samy Pessé 275eedf076 Change button color on header 2014-08-11 07:36:46 -07:00
Samy Pessé 5e959d65a2 Start new design for website 2014-08-11 07:36:46 -07:00
Samy Pessé 66c1b89da2 Use only default config 2014-08-11 07:36:46 -07:00
Samy Pessé 78c68c7b9e Remove config for github urls 2014-08-11 07:36:46 -07:00
Samy Pessé d914eae40a Remove html5 cache manifest 2014-08-11 07:36:45 -07:00
Samy Pessé 74f95f4d0d Merge pull request #404 from Viv-Rajkumar/master
Fix path operator in Windows for index.html with multi lang
2014-08-11 07:31:10 -07:00
Viv-Rajkumar d803052461 Fix Langs path operator for Windows using 'links.js' 2014-08-11 12:09:07 +01:00
Aaron O'Mullan 633e52c4be Bump version to 0.7.1 2014-08-08 15:37:03 -07:00
Aaron O'Mullan 1512bcf808 Update fs-extra to 0.10.0
Fixes potential race condition on copies
2014-08-08 15:31:49 -07:00
Samy Pessé 4e46d088ca Add build for search results 2014-08-05 09:47:08 -07:00
Samy Pessé 388c7ffd32 Merge pull request #396 from codepiano/hold-search-keyword
keep the search result list after user click a link
2014-08-05 09:44:54 -07:00
codepiano 7ba8653935 keep the search result list after user click a link
Record search keyword to LocalStorage, when navigate to a new url,fetch
keyword from LocalStorage, init the search input element,then filter the
sidebar menu.
So User can iterator the result list without type the keyword every
time.
2014-08-05 15:14:10 +08:00
Samy Pessé a4403aad1e Add changes for 0.7.0 2014-08-01 14:01:20 -07:00
Samy Pessé b629b78dba Bump version to 0.7.0 2014-08-01 13:58:36 -07:00
Samy Pessé f62c218735 Fix #278: Add page break between chapters/articles 2014-08-01 13:55:19 -07:00
Aaron O'Mullan 725510028f Use marked-text-renderer@0.1.0 2014-08-01 00:09:45 -07:00
Aaron O'Mullan ad7927242d Add comments to default introduction chapter code
Fixes #388
2014-07-31 08:43:57 -07:00
Aaron O'Mullan 5692bdd229 Merge pull request #386 from codepiano/china-social-network-sharing
add sharing to weibo.com.
2014-07-31 01:41:56 -07:00
codepiano 3402746719 add sharing to weibo.com.
google、facebook and twitter are block by GFW in China,Chinese can
not visit these websites.
add 'weibo' config in option links.sharing,the share link will not be
used by default,users should set the value of 'weibo' to true to turn
on sharing to weibo.com.
2014-07-31 16:31:31 +08:00
Aaron O'Mullan 30a8414b06 Switch to graceful-fs, fix #373 2014-07-30 20:45:33 -07:00
Aaron O'Mullan 9d46899ea7 Replace "marked" with "kramed" 2014-07-30 16:17:47 -07:00
Samy Pessé 58f14fd9ca Fix font settings apply 2014-07-30 11:17:28 -07:00
Samy Pessé b6af14b13e Remove github count stars and watchers 2014-07-30 10:43:10 -07:00
Aaron O'Mullan ba2134c480 Bump version to 0.6.3 2014-07-30 10:23:11 -07:00
Aaron O'Mullan 4705548860 Fix resource normalization in when loading plugins
Fixes #384
2014-07-30 10:17:07 -07:00
Samy Pessé 7d51ae3689 Add changelogs for 0.6.2 2014-07-30 00:26:44 -07:00
Aaron O'Mullan b9360de3e3 Bump version to 0.6.2 2014-07-30 00:22:49 -07:00
Aaron O'Mullan 9f241457bb Fix minor issues with new plugin additions 2014-07-29 23:45:54 -07:00
Aaron O'Mullan 60a7c28e66 Support generating a plugin's book info dynamically 2014-07-29 21:50:26 -07:00
Samy Pessé 5dff9b795e Fix #345: Improve navigation on dark theme 2014-07-29 21:33:47 -07:00
Aaron O'Mullan 0134677e0e Improve path normalization when parsing SUMMARY.md 2014-07-29 20:49:11 -07:00
Aaron O'Mullan 39ef4fa199 Add invalid paths in SUMMARY.md fixture
To test for correct path normalization
2014-07-29 20:46:14 -07:00
Samy Pessé b507a30301 Bump version to 0.6.1 2014-07-23 16:49:57 -07:00
Samy Pessé 539c2d0c57 Update plugin-mathjax@0.0.4 2014-07-23 16:28:38 -07:00
Samy Pessé aa6f3530f9 Fix readme spacing 2014-07-22 23:28:13 -07:00
Samy Pessé 98057af219 Merge pull request #372 from justanotheranonymoususer/patch-1
Update summary.html (fixed an HTML typo)
2014-07-20 14:04:12 -07:00
justanotheranonymoususer bb48c8233c Update summary.html 2014-07-20 23:57:56 +03:00
Aaron O'Mullan 8f14db64df Merge pull request #370 from nightwing/patch-1
Add keyboard shortcut for submitting exercise
2014-07-15 21:33:58 -07:00
Harutyun Amirjanyan 37d40dc66c Add keyboard shortcut for submitting exercise 2014-07-16 08:07:22 +04:00
Samy Pessé 6c96680cca Merge pull request #366 from shaform/ignore
Ignore config files by default in fs.list
2014-07-14 10:08:29 -07:00
Shaform 4e475857d5 Ignore config files by default in fs.list 2014-07-13 08:40:23 -07:00
Samy Pessé f8ff0453dd Merge pull request #359 from jcouyang/patch-1
add jsbin embedded plugin
2014-07-12 07:02:18 -07:00
Samy Pessé 3cc5c47535 Bump version to 0.6.0 2014-07-12 23:28:07 +02:00
Samy Pessé a2046814b4 Change command "git:push" to "publish" 2014-07-12 23:25:40 +02:00
Samy Pessé 82f814fdbd Fix #303: generate header id the same as github 2014-07-12 23:23:03 +02:00
Jichao Ouyang cd382dc8fc add jsbin embedded support 2014-07-12 21:42:52 +08:00
Samy Pessé 3842e5bf4a Merge pull request #358 from GitbookIO/featureplatform
Add base command git:push and git:remote
2014-07-11 17:42:16 -07:00
Samy Pessé f05b138fd6 Add base command git:push and git:remote 2014-07-12 09:23:15 +02:00
Samy Pessé 1b87cec893 Merge pull request #266 from shaform/ref_links
Add reference links support
2014-07-11 17:20:43 -07:00
Samy Pessé 4a894b1e18 Merge pull request #268 from rlmv/master
Fix page.path for plugins
2014-07-11 17:19:26 -07:00
Samy Pessé c7c98009e7 Merge pull request #347 from rlmv/custom_links
Add custom links to TOC
2014-07-11 17:15:37 -07:00
Samy Pessé 356322e0ed Merge pull request #353 from citizenmatt/active_page
Highlight active page in summary
2014-07-11 17:14:36 -07:00
Samy Pessé 7a47049367 Merge pull request #354 from citizenmatt/summary_hooks
Plugin hooks for summary data
2014-07-11 17:13:40 -07:00
Samy Pessé a9f473260c Merge pull request #356 from GitbookIO/feature/includes
Support importing code snippets
2014-07-10 17:59:34 -07:00
Aaron O'Mullan d16577e8bf Add tests for includes 2014-07-10 17:49:50 +02:00
Aaron O'Mullan 15b4053d6b Support code includes for exercises 2014-07-10 17:30:12 +02:00
Aaron O'Mullan 925a9b1388 Support importing code snippets
Allowing to keep code in separate files, which can be cleaner in a
quite a few scenarios
2014-07-11 01:22:07 +02:00
Matt Ellis fc942fdeca Hooks for summary data 2014-07-09 17:17:40 +01:00
Matt Ellis 75a6ecbf52 Highlight active page in summary 2014-07-09 16:06:55 +01:00
Aaron O'Mullan 520045441b Merge pull request #352 from citizenmatt/links_with_fragments
Convert links with fragments from md to html
2014-07-08 21:45:01 +02:00
Matt Ellis 00c3e966d3 Convert links with fragments from md to html 2014-07-08 16:25:39 +01:00
Samy Pessé 8b6ebe86b8 Merge pull request #348 from rlmv/anchors_readme
Add anchor plugin to README
2014-07-06 17:12:57 +02:00
Bo Marchman d5bd3453e1 add heading anchors plugin to readme 2014-07-02 23:49:07 -04:00
Bo Marchman ddacbbe73b update readme with custom links 2014-07-02 23:40:57 -04:00
Bo Marchman f7d52f0eff Add custom link capability
Users can add custom links at the top of the table of contents by
adding a links.custom object to book.json. Issue #304
2014-07-02 23:38:14 -04:00
Robert Marchman 39c50640ea pass 'rawPath' page path to plugins 2014-06-24 12:33:19 -04:00
Samy Pessé 16128c7e60 Bump version to 0.5.6 2014-06-24 12:35:13 +02:00
Samy Pessé 8b6a80cffb Fix error when chapters list is empty
Fix GitbookIO/gitbook.io#101 and GitbookIO/gitbook.io#102
2014-06-24 12:34:29 +02:00
Aaron O'Mullan 86f8a9d8fb Remove useless (and bad) sort on progress
Fixes bad ordering of chapters on gitbook.io
2014-06-20 03:40:47 -07:00
Aaron O'Mullan 790dfd601e Add csharp language highlight mapping
Fixes #327
2014-06-18 11:26:50 -07:00
Aaron O'Mullan 3426428142 Bump version to 0.5.5 2014-06-18 00:25:23 -07:00
Aaron O'Mullan 6c5c8e6eb7 Fix LANGS.md parsing
And cleanup Summary parsing
2014-06-17 16:18:08 -07:00
Aaron O'Mullan 3aefcaea73 Bump version to 0.5.4 2014-06-17 10:56:45 -07:00
Aaron O'Mullan 04c025774d Improve robustness of link utility functions
Fixes #321, fixes #323
2014-06-17 10:56:13 -07:00
Aaron O'Mullan 80aac7f581 Support --verbose flag, fixes #325 2014-06-17 10:46:56 -07:00
Samy Pessé 266f7ca73d Add infos about debugging 2014-06-17 18:45:44 +02:00
Shaform 6bbefad24e Merge remote-tracking branch 'upstream/master' into ref_links 2014-06-17 22:08:46 +08:00
Aaron O'Mullan d6eb8e4c60 Support URLs in "github" config
Fixes GitbookIO/gitbook.io#90
2014-06-16 18:52:36 -07:00
Aaron O'Mullan c87edb155a Better string slicing in parse.git.githubID() 2014-06-16 17:48:18 -07:00
Samy Pessé ef0468ad5d Fix #310: improve display of long title in header 2014-06-16 14:03:28 +02:00
Samy Pessé f03153eb0c Remove page break between articles in ebook
Add title for exercise solutions sections
2014-06-16 13:55:07 +02:00
Samy Pessé 3d8b6e550a Merge pull request #319 from GitbookIO/enchancement/custom_intro
Enchancement/custom intro
2014-06-16 13:02:38 +02:00
Aaron O'Mullan 4c3dbca5ca Normalize intro node in Summary parsing 2014-06-16 00:26:11 -07:00
Aaron O'Mullan a258feea37 Remove special case in navigation
For intro node
2014-06-16 00:25:59 -07:00
Aaron O'Mullan 3a3332d0b9 Adapt summary tests to new summary parsing
We now insert default README.md (introduction) node in parsing the
SUMMARY.md logic
2014-06-16 00:25:33 -07:00
Aaron O'Mullan 4259283f32 Refactor Summary rendering
Introduction is no longer an edge case
2014-06-16 00:09:48 -07:00
Aaron O'Mullan 243d977cd6 Add test for level regarding #318 2014-06-15 23:36:40 -07:00
Aaron O'Mullan 94bbbecedf Support custom intro node, fixes #318 2014-06-15 23:30:43 -07:00
Aaron O'Mullan f49eab42a9 Fix test for #318 2014-06-15 23:29:24 -07:00
Aaron O'Mullan ce6b2bc47d Add extra navigation tests for #318 2014-06-15 23:19:28 -07:00
Aaron O'Mullan 3245d7e430 Drop Travis tests for Node 0.8 2014-06-15 22:49:15 -07:00
Aaron O'Mullan 6ee17c32ae Bump version to 0.5.3 2014-06-15 22:39:18 -07:00
Aaron O'Mullan bf7ffe6d4c Skip extra files and folders in copy phase of generation 2014-06-15 22:37:15 -07:00
Aaron O'Mullan ec7bc6b347 Add link to gitbook.io in readme 2014-06-15 22:36:51 -07:00
Aaron O'Mullan b425db0979 Allow external links in summary, fixes #300 2014-06-08 12:31:52 -07:00
Samy Pessé 30c6d1c9de Remove useless log 2014-06-08 02:21:05 +02:00
Samy Pessé cee1d90fc6 Bump version to 0.5.2 2014-06-08 01:09:51 +02:00
Samy Pessé 522342667c Fix font size for mono font on pdf output 2014-06-08 00:27:18 +02:00
Samy Pessé d1c1277bb9 Fix cover picture for multilangs book convertion to ebook 2014-06-08 00:11:50 +02:00
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
Shaform 1f4cf33dd0 Improve code syntax for links assignment 2014-05-28 06:57:50 +08:00
Shaform 7274aa6940 fix links error 2014-05-27 22:24:23 +08:00
Shaform 125e804cd5 Add reference links support
This commit resolves #184
2014-05-27 17:24:47 +08: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
Aaron O'Mullan 67e7d77302 Bump version to 0.3.1 2014-04-24 10:45:55 -07:00
Aaron O'Mullan 8cbd8b2b48 Reorder plugin names
Improves #149 and improves GitBookIO/editor#12
2014-04-24 01:38:42 -07:00
Aaron O'Mullan 7c404dbf96 Fix plugin tests 2014-04-24 01:34:35 -07:00
Aaron O'Mullan c4a67b6f2f Require plugin names to contain gitbook-*
Fixes #149, fixes GitBookIO/editor#12
2014-04-24 01:28:04 -07:00
Samy Pessé 3b70347625 Fix #146: complete license 2014-04-24 09:00:15 +02:00
Aaron O'Mullan bb0a7935e9 Merge pull request #141 from frodsan/niceties
Niceties
2014-04-23 11:34:08 -07:00
Nattaphoom Ch 8f4b799d3c Make quiz easier to click by surround it with label. 2014-04-24 01:19:13 +07:00
Francesco Rodriguez 931e0c66c9 Update color themes for highlight.js link. 2014-04-23 12:30:05 -03:00
Francesco Rodriguez 0212084862 Fix typo. 2014-04-23 12:28:44 -03:00
Samy Pessé 9abbabbce3 Merge pull request #138 from Hermanya/patch-1
Add @Hermanya as a contributor
2014-04-22 23:47:13 +02:00
Hermanya ac610487e8 Add @Hermanya as a contributor 2014-04-22 17:45:41 -04:00
Aaron O'Mullan a9a0f89892 Bump version to 0.3.0 2014-04-22 14:05:52 -07:00
Samy Pessé 305f221077 Fix #136: replace links to .md by links to .html
Add more tests for links in page
2014-04-22 17:45:12 +02:00
Samy Pessé 53514f2602 Improve sepia theme sidebar 2014-04-22 14:27:48 +02:00
Samy Pessé 00327778cb Improve font-settings dropdown design 2014-04-22 14:12:53 +02:00
Samy Pessé 37fadec787 Merge branch 'master' of https://github.com/Hermanya/gitbook into Hermanya-master 2014-04-22 14:02:19 +02:00
Samy Pessé ada11d0b59 Add section in readme about plugins 2014-04-22 12:51:10 +02:00
Aaron O'Mullan 4c90fdb7b2 Merge pull request #123 from GitbookIO/feature/plugins
Fix #65: Plugins Architecture
2014-04-21 14:26:09 -07:00
Samy Pessé 98c133df3c Change plugins separator to "," 2014-04-21 23:23:53 +02:00
Samy Pessé f86c27b090 Add option --pluginsConfig 2014-04-21 22:58:51 +02:00
Samy Pessé b5b8ed72a5 Update default addons 2014-04-21 22:39:23 +02:00
Samy Pessé 5beff86548 Change js interface for plugins and add configuration 2014-04-21 22:20:41 +02:00
Samy Pessé dd395edae1 Use mathjax plugin version 0.0.2 2014-04-21 16:30:02 +02:00
Aaron O'Mullan d1c1d7b75b Bump version to 0.2.4 2014-04-20 16:41:09 -07:00
Samy Pessé 4a380e9279 Fix #36: add mathjax plugin as default 2014-04-20 15:20:21 +02:00
Samy Pessé 878c105def Use complete assets folder for plugins 2014-04-20 02:04:15 +02:00
Samy Pessé 74d628676b Add option to change templates from plugin 2014-04-20 00:10:46 +02:00
Samy Pessé 6fdd98ec3a Trigger event on document when ready 2014-04-19 20:39:58 +02:00
Samy Pessé 2ed1be3533 Add global js interface for plugins and remove mixpanel 2014-04-19 20:10:03 +02:00
Samy Pessé 0ac04e1584 Add call for hooks "init" and "finish" 2014-04-19 19:50:32 +02:00
Herman Starikov 2a38d33d8a Font settings, closes #102 2014-04-19 13:46:14 -04:00
Samy Pessé fb14caf5b3 Add resources to site build 2014-04-19 19:31:02 +02:00
Samy Pessé cf51772f54 Add command line option "plugins" 2014-04-19 18:00:24 +02:00
Samy Pessé cc8516df83 Load plugins during generation 2014-04-19 17:57:40 +02:00
Samy Pessé c6a624bc6b Add method to load a list of plugins from generator options 2014-04-19 17:54:15 +02:00
Samy Pessé 8be8b825ee Add base loading of default addons
Add mixpanel as default addon
2014-04-19 17:28:44 +02:00
Samy Pessé 56eb4a2897 Add base plugin object and test validation of plugin 2014-04-19 15:41:29 +02:00
Samy Pessé 7a461eaa61 Remove theme readme
Add semver dependency
2014-04-19 14:40:46 +02:00
Samy Pessé fd64c05809 Fix #118: fix single page generation when file from summary doesn't exists 2014-04-18 23:42:02 +02:00
Aaron O'Mullan 6bce93d893 Bump version to 0.2.3 2014-04-18 11:53:18 -07:00
Aaron O'Mullan c9db068227 Refactor navigation generation to be functional
This makes it a lot less error prone and far more robust. As well as a
lot easier to understand
2014-04-18 11:49:55 -07:00
Samy Pessé 8a6d36ac4d Improve stability of mixpanel tracking 2014-04-18 20:42:18 +02:00
Samy Pessé 64622c5945 Load css before content 2014-04-18 20:19:45 +02:00
Samy Pessé 837813edb0 Add warning about theme 2014-04-18 20:16:21 +02:00
Aaron O'Mullan 0d9d62f1a9 Merge pull request #117 from GitbookIO/fix/99
Fix #99
2014-04-18 10:17:20 -07:00
Samy Pessé f7e9abf984 Fix quiz interractivity 2014-04-18 19:10:13 +02:00
Samy Pessé dd0579b60d Update tests for relative images 2014-04-18 17:23:33 +02:00
Samy Pessé b5bb953e47 Fix image path calcul 2014-04-18 17:15:03 +02:00
Samy Pessé ef4a8ea1b6 Fix #116: update url before updating html content 2014-04-18 15:49:53 +02:00
Samy Pessé 49cde96ee7 Add contributors to package.json and change homepage 2014-04-18 15:00:40 +02:00
Samy Pessé 627be20e48 Add editor and twitter links in the readme 2014-04-18 14:48:03 +02:00
Samy Pessé e45bb84b02 Fix navigation on chrome for iOS: disable pushState 2014-04-18 13:09:16 +02:00
Aaron O'Mullan 4e8876c725 Update README.md 2014-04-17 16:39:06 -07:00
Aaron O'Mullan 6d5be15ec9 Bump version to 0.2.2 2014-04-17 15:31:57 -07:00
Samy Pessé 1a8db646f2 Improve styling for pdf and ebook 2014-04-17 12:15:28 +02:00
Samy Pessé d3aba2c906 Fix state update after navigation 2014-04-17 11:48:04 +02:00
Samy Pessé 457a83282f Improve style for pdf and ebook 2014-04-17 11:27:21 +02:00
Samy Pessé 48e94c937d Remove useless caracter from page format 2014-04-17 11:17:40 +02:00
Samy Pessé 6e613e9f71 Fix #107: Render all the page in single page format 2014-04-17 11:16:18 +02:00
270 changed files with 8674 additions and 31505 deletions
+6
View File
@@ -23,3 +23,9 @@ 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
theme/vendors
# vim swapfile
*.swp
+3 -1
View File
@@ -1,5 +1,7 @@
sudo: false
language: node_js
node_js:
- "0.11"
- "0.10"
- "0.8"
before_install:
- npm install svgexport -g
+1
View File
@@ -15,3 +15,4 @@ Contributors
------------
- Nijiko Yonskai
- Herman Starikov
+92
View File
@@ -0,0 +1,92 @@
# Release notes
## 1.5.0
- Fix `serve` command, broken by `1.4.2`
- Add nicer `dark` theme :)
## 1.4.2
- Force `process.exit` after builds, to prevent (possibly) lingering plugins
## 1.4.1
- Fix command 'install' without arguments
## 1.4.0
- Add command `gitbook install` to install plugins from book.json
- `package.json` is no longer necessary
## 1.3.4
- Add glossary to ebooks
- Fix autocover with new hook "finish:before"
- Add X-UA-Compatible meta tag for IE
## 1.3.3
- Fix parsing of lexed content using the client library
## 1.3.2
- ePub files are now passing validation from epubcheck
- Fix replacement of multiple glossary terms in a single sentence
- Fix on windows deep relative links
- Fix search indexer
## 1.3.1
- Fix error with links in markdown
## 1.3.0
- Bundle gitbook parsing library as a client side library in `gitbook.js` and `gitbook.min.js`
## 1.2.0
- Improvements on ebook generation
- Fix incorrect follow of links in ebook generation
- Move Table of Contents at the beginning of the ebook
- Update to last highlight.js (includes Swift)
- Includes of templates and variables (from book.json)
## 1.1.1
- Rewrite quiz logic to be more robust
- Improve integration of glossary
- Improve generation of ebook by using a multiple HTML pages input source
- Fix incorrect page breaks after h1 and h2 divs
- New options to set header and footer in PDF generation
## 1.1.0
- Plugins can now extend the ebook generation (pdf, epub, mobi)
- Update `kramed` to version 0.4.3
## 1.0.3
- Update `mathjax` plugin and MathJAx to version 2.4
- Update `highlight.js` to 8.2.0
## 1.0.2
- Update `mathjax` plugin, fixes issues with inline math rendering (no longer wanted)
## 1.0.1
- New inline math convention (kramdown's), using `$$` rather than `$` as delimiters
- Fix instapaper sharing
- The `exercises` & `quizzes` plugins are now by default
## 1.0.0
- New design
- Support for glossary
- Support for sharing to instapaper
- Support for footnotes
## 0.7.1
- Update `fs-extra` to `0.10.0` (fixes potential race conditions)
## 0.7.0
- Add page break in ebook (pdf, epub, mobi) between chapters/articles
- Start using kramed instead of marked
- Fix display of inline math
- Switch to graceful-fs to fix EMFILE errors
- Add sharing to weibo.com
## 0.6.2
- Support generating a plugin's book info dynamically
- Improve navigation on dark theme
- Improve path normalization when parsing SUMMARY.md
## 0.6.0
- Generate header id the same as github
- Custom links can be added at top of sidebar
- Summary can now be transformed by plugins
- Support importing code snippets
+39 -15
View File
@@ -2,12 +2,21 @@ module.exports = function (grunt) {
var path = require("path");
// Load NPM tasks
grunt.loadNpmTasks('grunt-contrib-copy');
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/vendors"
}
},
less: {
development: {
options: {
@@ -16,27 +25,28 @@ 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"
}
}
},
requirejs: {
compile: {
options: {
name: "app",
name: "gitbook",
baseUrl: "theme/javascript/",
out: "theme/assets/app.js",
preserveLicenseComments: false,
optimize: "uglify", //"uglify",
optimize: "uglify",
include: ["requireLib"],
paths: {
"jQuery": 'vendors/jquery',
"lodash": 'vendors/lodash',
"requireLib": 'vendors/require',
"Mousetrap": 'vendors/mousetrap',
"mixpanel": 'vendors/mixpanel',
"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',
"URIjs": '../vendors/URIjs/src/',
"ace": '../vendors/ace-builds/src-noconflict/'
},
shim: {
'jQuery': {
@@ -48,25 +58,39 @@ module.exports = function (grunt) {
'Mousetrap': {
exports: 'Mousetrap'
},
'mixpanel': {
exports: 'mixpanel'
},
'lunr': {
exports: 'lunr'
}
}
}
}
},
copy: {
vendors: {
files: [
{
expand: true,
cwd: 'theme/vendors/fontawesome/fonts/',
src: ['**'],
dest: 'theme/assets/fonts/fontawesome/',
filter: 'isFile'
}
]
}
}
});
grunt.registerTask("bower-install", [ "bower-install-simple" ]);
// Build
grunt.registerTask('build', [
'bower-install',
'less',
'requirejs'
'requirejs',
'copy:vendors'
]);
grunt.registerTask('default', [
'build'
]);
};
};
+2 -2
View File
@@ -186,7 +186,7 @@ Apache License
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Copyright 2014 FriendCode Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -198,4 +198,4 @@ Apache License
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.
limitations under the License.
+168 -63
View File
@@ -1,9 +1,12 @@
GitBook
=======
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
:warning: This branch contains the version 2.0.
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/).
[![Build Status](https://travis-ci.org/GitbookIO/gitbook.png?branch=master)](https://travis-ci.org/GitbookIO/gitbook)
[![NPM version](https://badge.fury.io/js/gitbook.svg)](http://badge.fury.io/js/gitbook)
GitBook is a command line tool (and Node.js library) for building beautiful books using GitHub/Git and Markdown (or AsciiDoc). Here is an example: [Learn Javascript](https://www.gitbook.com/book/GitBookIO/javascript). You can publish and host book easily online using [gitbook.com](https://www.gitbook.com), a web-editor is [also available](https://www.gitbook.com/editor). 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)
@@ -15,6 +18,11 @@ GitBook can be installed from **NPM** using:
$ npm install gitbook -g
```
Create the directories and files for a book from its [SUMMARY.md](https://github.com/GitbookIO/gitbook#book-format) file using
```
$ gitbook init
```
You can serve a repository as a book using:
```
@@ -24,38 +32,42 @@ $ gitbook serve ./repository
Or simply build the static website using:
```
$ gitbook build ./repository --output=./outputFolder
$ gitbook build ./repository ./outputFolder
```
Options for commands `build` and `serve` are:
## Features
```
-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/)
--theme <path> Path to theme directory
```
* [Output as a website or ebook (pdf, epub, mobi)](#output-formats)
* [Multi-Languages](#multi-languages)
* [Glossary](#glossary)
* [Cover](#cover)
* [AsciiDoc Support](#asciidoc)
* [Variables and Templating](#variables-and-templating)
* [Content References](#content-references)
* [Plugins](#plugins)
* Interractive reader website:
* Search
* Font Settings (Serif, Sans Serif)
* Themes: white, sepia, night
## Output Formats
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**: You need to have [ebook-convert](http://manual.calibre-ebook.com/cli/ebook-convert.html) installed. You can specify the eBook filename as the second argument, otherwise `book` will be used.
* Generate a **PDF** using: `gitbook pdf ./myrepo ./mybook.pdf`
* Generate a **ePub** using: `gitbook epub ./myrepo ./mybook.epub`
* Generate a **MOBI** using: `gitbook mobi ./myrepo ./mybook.mobi`
* **JSON**: This format is used for debugging or extracting metadata from a book. Generate this format using: ```gitbook build ./myrepo --format=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
@@ -63,7 +75,7 @@ The `SUMMARY.md` defines your book's structure. It should contain a list of chap
Example:
```
```markdown
# Summary
This is the summary of my book.
@@ -75,52 +87,13 @@ 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`.
#### 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.
An exercise is defined by 4 simple parts:
* Exercise **Message**/Goals (in markdown/text)
* **Initial** code to show to the user, providing a starting point
* **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).
---
Define a variable `x` equal to 10.
```js
var x =
```
```js
var x = 10;
```
```js
assert(x == 10);
```
```js
// This is context code available everywhere
// The user will be able to call magicFunc in his code
function magicFunc() {
return 3;
}
```
---
Files that are not included in `SUMMARY.md` will not be processed by `gitbook`.
#### Multi-Languages
GitBook supports building books written in multiple languages. Each language should be a sub-directory following the normal GitBook format, and a file named `LANGS.md` should be present at the root of the repository with the following format:
```
```markdown
* [English](en/)
* [French](fr/)
* [Español](es/)
@@ -128,6 +101,138 @@ GitBook supports building books written in multiple languages. Each language sho
You can see a complete example with the [Learn Git](https://github.com/GitbookIO/git) book.
#### Glossary
Allows you to specify terms and their respective definitions to be displayed in the glossary. Based on those terms, `gitbook` will automatically build an index and highlight those terms in pages.
The `GLOSSARY.md` format is very simple :
```markdown
# term
Definition for this term
# Another term
With it's definition, this can contain bold text and all other kinds of inline markup ...
```
#### Variables and Templating
A set of variables can be defined in the `book.json`:
```js
{
"variables": {
"host": "mybook.com"
}
}
```
These variables can be used in the markdown files:
```
The host is {{ book.host }}
```
You can also use condition with these variables:
```
{% if book.host == "mybook.com" %}
{% else %}
{% endif %}
```
Variables of `book.json` are available in the `book` namespace. You can also access informations about the `file` itself and the `gitbook` version:
```
My file is {{ file.path }}
Modified at {{ file.mtime }}
Book built with GitBook {{ gitbook.version }}
```
#### Content References
You can use "content references," or conrefs, when writing books or documentation using GitBook.
Include a file from the same book:
```
{% include "./test.md" %}
```
or from a git repository (with a specific revision):
```
{% include "git+https://github.com/GitbookIO/documentation.git/README.md#1.0.1" %}
```
Includes can be used with variables (see [Variables and Templating](#variables-and-templating)):
```
{% include book.ref_doc_readme %}
```
#### 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`).
Best practices for the `.gitignore` is to ignore build files from **node.js** (`node_modules`, ...) and build files from GitBook: `_book`, `*.epub`, `*.mobi` and `*.pdf` ([Download GitBook.gitignore](https://github.com/github/gitignore/blob/master/GitBook.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**.
#### AsciiDoc
Since version 2.0.0, AsciiDoc can be used instead of Markdown, simply replace the `.md` by the `.adoc` extension. Chapters in the summary are detected from an ordered list in the `SUMMARY.adoc`.
## Publish your book
The platform [GitBook.com](https://www.gitbook.com/) 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 be 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.
Plugins needed to build a book can be installed using: `gitbook install ./`.
##### Official plugins:
| Name | Description |
| ----- | ---- |
| [exercises](https://github.com/GitbookIO/plugin-exercises) | Add interactive exercises to your book. |
| [quizzes](https://github.com/GitbookIO/plugin-quizzes) | Add interactive quizzes to your book. |
| [mathjax](https://github.com/GitbookIO/plugin-mathjax) | Displays mathematical notation in the book. |
| [mixpanel](https://github.com/GitbookIO/plugin-mixpanel) | Mixpanel tracking for your book |
| [infinitescroll](https://github.com/GitbookIO/gitbook-plugin-infinitescroll) | Infinite Scrolling |
##### Other plugins:
| Name | Description |
| ----- | ---- |
| [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 |
| [Heading Anchors](https://github.com/rlmv/gitbook-plugin-anchors) | Add linkable Github-style anchors to headings |
| [JSBin](https://github.com/jcouyang/gitbook-plugin-jsbin) | Embedded jsbin frame into your book |
| [GrVis](https://github.com/romanlytkin/gitbook-grvis) | Gitbook GrViz plugin is used to select from markdown dot and converting it into a picture format svg |
| [PlantUml](https://github.com/romanlytkin/gitbook-plantuml) | Gitbook PlantUml plugin is used to select from markdown uml and converting it into a picture format svg |
| [Mermaid](https://github.com/JozoVilcek/gitbook-plugin-mermaid) | Adds diagrams and flowcharts rendered by [mermaid](https://github.com/knsv/mermaid) |
## Debugging
You can use the options `--log=debug` and `--debug` to get better error messages (with stack trace). For example:
```
$ gitbook build ./ -- log=debug --debug
```
-50
View File
@@ -1,50 +0,0 @@
var path = require('path');
var Q = require('q');
var _ = require('lodash');
var utils = require('./utils');
var generate = require("../lib/generate");
var parse = require("../lib/parse");
var fs = require('../lib/generate/fs');
var generators = require("../lib/generate").generators;
var makeBuildFunc = function(converter) {
return function(dir, options) {
dir = dir || process.cwd();
outputDir = options.output
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,
theme: options.theme
})
);
})
.then(function(output) {
console.log("Successfuly built !");
return output;
}, utils.logError);
};
}
module.exports = {
folder: makeBuildFunc(generate.folder),
file: makeBuildFunc(generate.file)
};
+4 -83
View File
@@ -1,86 +1,7 @@
#! /usr/bin/env node
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var prog = require('commander');
var color = require('bash-color');
var pkg = require('../package.json');
var generators = require("../lib/generate").generators;
var initDir = require("../lib/generate/init");
var fs = require('../lib/generate/fs');
var utils = require('./utils');
var build = require('./build');
// General options
prog
.version(pkg.version);
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('-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('--theme <path>', 'Path to theme directory');
};
buildCommand(prog.command('build [source_dir]'))
.description('Build a gitbook from a directory')
.action(build.folder);
buildCommand(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)
.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 ...');
});
});
buildCommand(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"
}));
});
buildCommand(prog.command('ebook [source_dir]'))
.description('Build a gitbook as a eBook')
.option('-c, --cover <path>', 'Cover image, default is cover.png if exists')
.action(function(dir, options) {
var ext = options.output ? path.extname(options.output) : "epub";
build.file(dir, _.extend(options, {
extension: ext,
format: "ebook"
}));
});
prog
.command('init [source_dir]')
.description('Create files and folders based on contents of SUMMARY.md')
.action(function(dir) {
dir = dir || process.cwd();
return initDir(dir);
});
// Parse and fallback to help if no args
if(_.isEmpty(prog.parse(process.argv).args) && process.argv.length === 2) {
prog.help();
}
console.log(color.red("You need to install 'gitbook-cli' to have access to the gitbook command anywhere on your system."));
console.log(color.red("If you've installe this package globally, you need to uninstall it."));
console.log(color.red(">> Run 'npm uninstall -g gitbook' then 'npm install -g gitbook-cli'"));
-101
View File
@@ -1,101 +0,0 @@
var Q = require('q');
var _ = require('lodash');
var http = require('http');
var send = require('send');
var url = require('url');
var cp = require('child_process');
// 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();});
}
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);
return d.promise;
}
function logError(err) {
console.log(err.stack || err.message || err);
return Q.reject(err);
};
// Exports
module.exports = {
gitURL: gitURL,
githubID: githubID,
titleCase: titleCase,
serveDir: serveDir,
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",
"fontawesome": "4.1.0"
}
}
+766
View File
@@ -0,0 +1,766 @@
var Q = require("q");
var _ = require("lodash");
var path = require("path");
var util = require("util");
var lunr = require('lunr');
var parsers = require("gitbook-parsers");
var color = require('bash-color');
var fs = require("./utils/fs");
var parseNavigation = require("./utils/navigation");
var parseProgress = require("./utils/progress");
var pageUtil = require("./utils/page");
var links = require("./utils/links");
var logger = require("./utils/logger");
var Configuration = require("./configuration");
var TemplateEngine = require("./template");
var Plugin = require("./plugin");
var PluginsList = require("./pluginslist");
var generators = require("./generators");
var Book = function(root, context, parent) {
var that = this;
this.context = _.defaults(context || {}, {
// Extend book configuration
config: {},
// Log function
log: function(msg) {
process.stdout.write(msg);
},
// Log level
logLevel: logger.LEVELS
});
// Log
this.log = logger(this.context.log, this.context.logLevel);
// Root folder of the book
this.root = path.resolve(root);
// Parent book
this.parent = parent;
// Configuration
this.config = new Configuration(this, this.context.config);
Object.defineProperty(this, "options", {
get: function () {
return this.config.options;
}
});
// Template
this.template = new TemplateEngine(this);
// Summary
this.summary = {};
this.navigation = [];
// Glossary
this.glossary = [];
// Langs
this.langs = [];
// Sub-books
this.books = [];
// Files in the book
this.files = [];
// List of plugins
this.plugins = new PluginsList(this);
// Structure files
this.summaryFile = null;
this.glossaryFile = null;
this.readmeFile = null;
this.langsFile = null;
// Search Index
this.searchIndex = lunr(function () {
this.ref('url');
this.field('title', { boost: 10 });
this.field('body');
});
// Bind methods
_.bindAll(this);
};
// Initialize and parse the book: config, summary, glossary
Book.prototype.parse = function() {
var that = this;
var multilingual = false;
return this.parseConfig()
.then(function() {
return that.parsePlugins();
})
.then(function() {
return that.parseLangs()
.then(function() {
multilingual = that.langs.length > 0;
if (multilingual) that.log.info.ln("Parsing multilingual book, with", that.langs.length, "lanuages");
// Sub-books that inherit from the current book configuration
that.books = _.map(that.langs, function(lang) {
that.log.info.ln("Preparing language book", lang.lang);
return new Book(
path.join(that.root, lang.path),
_.merge({}, that.context, {
config: _.extend({}, that.options, {
'output': path.join(that.options.output, lang.path),
'language': lang.lang
})
}),
that
)
});
});
})
.then(function() {
if (multilingual) return;
return that.listAllFiles();
})
.then(function() {
if (multilingual) return;
return that.parseReadme();
})
.then(function() {
if (multilingual) return;
return that.parseSummary();
})
.then(function() {
if (multilingual) return;
return that.parseGlossary();
})
.then(function() {
// Init sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.parse();
});
}, Q());
})
.thenResolve(this);
};
// Generate the output
Book.prototype.generate = function(generator) {
var that = this;
that.options.generator = generator || that.options.generator;
that.log.info.ln("start generation with", that.options.generator, "generator");
return Q()
// Clean output folder
.then(function() {
that.log.info("clean", that.options.generator, "generator");
return fs.clean(that.options.output)
.progress(function(p) {
that.log.debug.ln("remove", p.file, "("+p.i+"/"+p.count+")");
})
.then(function() {
that.log.info.ok();
});
})
// Create generator
.then(function() {
var Generator = generators[generator];
if (!Generator) throw "Generator '"+that.options.generator+"' doesn't exist";
generator = new Generator(that);
return generator.prepare();
})
// Generate content
.then(function() {
if (that.isMultilingual()) {
return that.generateMultiLingual(generator);
} else {
// Separate list of files into the different operations needed
var ops = _.groupBy(that.files, function(file) {
if (file[file.length -1] == "/") {
return "directories";
} else if (_.contains(parsers.extensions, path.extname(file)) && that.navigation[file]) {
return "content";
} else {
return "files";
}
});
return Q()
// First, let's create folder
.then(function() {
return _.reduce(ops["directories"] || [], function(prev, folder) {
return prev.then(function() {
that.log.debug.ln("transferring folder", folder);
return Q(generator.transferFolder(folder));
});
}, Q());
})
// Then, let's copy other files
.then(function() {
return Q.all(_.map(ops["files"] || [], function(file) {
that.log.debug.ln("transferring file", file);
return Q(generator.transferFile(file));
}));
})
// Finally let's generate content
.then(function() {
return _.reduce(ops["content"] || [], function(prev, file) {
return prev.then(function() {
that.log.debug.ln("converting", file);
return Q(generator.convertFile(file));
});
}, Q());
});
}
})
// Finish generation
.then(function() {
return generator.callHook("finish:before");
})
.then(function() {
return generator.finish();
})
.then(function() {
return generator.callHook("finish");
})
.then(function() {
that.log.info.ln("generation is finished");
});
};
// Generate the output for a multilingual book
Book.prototype.generateMultiLingual = function(generator) {
var that = this;
return Q()
.then(function() {
// Generate sub-books
return _.reduce(that.books, function(prev, book) {
return prev.then(function() {
return book.generate(that.options.generator);
});
}, Q());
})
.then(function() {
return generator.langsIndex(that.langs);
});
};
// Extract files from ebook generated
Book.prototype.generateFile = function(output, options) {
var book = this;
options = _.defaults(options || {}, {
ebookFormat: path.extname(output).slice(1)
});
output = output || path.resolve(book.root, "book."+options.ebookFormat);
return fs.tmp.dir()
.then(function(tmpDir) {
book.config.extend({
output: tmpDir
});
return book.generate(options.ebookFormat)
.then(function(_options) {
var copyFile = function(lang) {
var _outputFile = output;
var _tmpDir = tmpDir;
if (lang) {
_outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile);
_tmpDir = path.join(_tmpDir, lang);
}
book.log.info("copy ebook to", output, "...");
return fs.copy(
path.join(_tmpDir, "index."+options.ebookFormat),
_outputFile
)
.then(function() {
book.log.info.ok();
});
};
// Multi-langs book
return Q()
.then(function() {
if (book.isMultilingual()) {
return Q.all(
_.map(book.langs, function(lang) {
return copyFile(lang.lang);
})
);
} else {
return copyFile();
}
})
.then(function() {
return fs.remove(tmpDir);
});
});
});
};
// Parse configuration
Book.prototype.parseConfig = function() {
var that = this;
that.log.info("loading book configuration....")
return that.config.load()
.then(function() {
that.log.info.ok();
});
};
// Parse list of plugins
Book.prototype.parsePlugins = function() {
var that = this;
var failed = [];
// Load plugins
return that.plugins.load(that.options.plugins)
.then(function() {
if (_.size(that.plugins.failed) > 0) return Q.reject(new Error("Error loading plugins: "+that.plugins.failed.join(",")+". Run 'gitbook install' to install plugins from NPM."));
that.log.info.ok(that.plugins.count()+" plugins loaded");
that.log.debug.ln("normalize plugins list");
});
};
// Parse readme to extract defaults title and description
Book.prototype.parseReadme = function() {
var that = this;
var structure = that.config.getStructure("readme");
that.log.debug.ln("start parsing readme:", structure);
return that.findFile(structure)
.then(function(readme) {
if (!readme) throw "No README file";
if (!_.contains(that.files, readme.path)) throw "README file is ignored";
that.readmeFile = readme.path;
that._defaultsStructure(that.readmeFile);
that.log.debug.ln("readme located at", that.readmeFile);
return that.template.renderFile(that.readmeFile)
.then(function(content) {
return readme.parser.readme(content);
});
})
.then(function(readme) {
that.options.title = that.options.title || readme.title;
that.options.description = that.options.description || readme.description;
});
};
// Parse langs to extract list of sub-books
Book.prototype.parseLangs = function() {
var that = this;
var structure = that.config.getStructure("langs");
that.log.debug.ln("start parsing languages index:", structure);
return that.findFile(structure)
.then(function(langs) {
if (!langs) return [];
that.langsFile = langs.path;
that._defaultsStructure(that.langsFile);
that.log.debug.ln("languages index located at", that.langsFile);
return that.template.renderFile(that.langsFile)
.then(function(content) {
return langs.parser.langs(content);
});
})
.then(function(langs) {
that.langs = langs;
});
};
// Parse summary to extract list of chapters
Book.prototype.parseSummary = function() {
var that = this;
var structure = that.config.getStructure("summary");
that.log.debug.ln("start parsing summary:", structure);
return that.findFile(structure)
.then(function(summary) {
if (!summary) throw "No SUMMARY file";
// Remove the summary from the list of files to parse
that.summaryFile = summary.path;
that._defaultsStructure(that.summaryFile);
that.files = _.without(that.files, that.summaryFile);
that.log.debug.ln("summary located at", that.summaryFile);
return that.template.renderFile(that.summaryFile)
.then(function(content) {
return summary.parser.summary(content, {
entryPoint: that.readmeFile,
files: that.files
});
});
})
.then(function(summary) {
that.summary = summary;
that.navigation = parseNavigation(that.summary, that.files);
});
};
// Parse glossary to extract terms
Book.prototype.parseGlossary = function() {
var that = this;
var structure = that.config.getStructure("glossary");
that.log.debug.ln("start parsing glossary: ", structure);
return that.findFile(structure)
.then(function(glossary) {
if (!glossary) return [];
// Remove the glossary from the list of files to parse
that.glossaryFile = glossary.path;
that._defaultsStructure(that.glossaryFile);
that.files = _.without(that.files, that.glossaryFile);
that.log.debug.ln("glossary located at", that.glossaryFile);
return that.template.renderFile(that.glossaryFile)
.then(function(content) {
return glossary.parser.glossary(content);
});
})
.then(function(glossary) {
that.glossary = glossary;
});
};
// Parse a page
Book.prototype.parsePage = function(filename, options) {
var that = this, page = {};
options = _.defaults(options || {}, {
// Transform svg images
convertImages: false,
// Interpolate before templating
interpolateTemplate: _.identity,
// Interpolate after templating
interpolateContent: _.identity
});
var interpolate = function(fn) {
return Q(fn(page))
.then(function(_page) {
page = _page;
});
};
that.log.debug.ln("start parsing file", filename);
var extension = path.extname(filename);
var filetype = parsers.get(extension);
if (!filetype) return Q.reject(new Error("Can't parse file: "+filename));
// Type of parser used
page.type = filetype.name;
// Path relative to book
page.path = filename;
// Path absolute in the system
page.rawPath = path.resolve(that.root, filename);
// Progress in the book
page.progress = parseProgress(that.navigation, filename);
that.log.debug.ln("render template", filename);
// Read file content
return that.readFile(page.path)
.then(function(content) {
page.content = content;
return interpolate(options.interpolateTemplate);
})
// Generate template
.then(function() {
return that.template.renderPage(page);
})
// Parse markup
.then(function(content) {
page.content = content;
that.log.debug.ln("use file parser", filetype.name, "for", filename);
return filetype.parser.page(page.content);
})
// Post process sections
.then(function(_page) {
return _.reduce(_page.sections, function(prev, section) {
return prev.then(function(_sections) {
return that.template.postProcess(section.content || "")
.then(function(content) {
section.content = content;
return _sections.concat([section]);
})
});
}, Q([]));
})
// Prepare html
.then(function(_sections) {
return pageUtil.normalize(_sections, {
book: that,
convertImages: options.convertImages,
input: filename,
navigation: that.navigation,
base: path.dirname(filename) || './',
output: path.dirname(filename) || './',
glossary: that.glossary
});
})
// Interpolate output
.then(function(_sections) {
page.sections = _sections;
return interpolate(options.interpolateContent);
})
.then(function() {
that.indexPage(page);
return page;
});
};
// Find file that can be parsed with a specific filename
Book.prototype.findFile = function(filename) {
var that = this;
return _.reduce(parsers.extensions, function(prev, ext) {
return prev.then(function(output) {
// Stop if already find a parser
if (output) return output;
var filepath = filename+ext;
return that.fileExists(filepath)
.then(function(exists) {
if (!exists) return null;
return {
parser: parsers.get(ext).parser,
path: filepath
};
})
});
}, Q(null));
};
// Check if a file exists in the book
Book.prototype.fileExists = function(filename) {
return fs.exists(
path.join(this.root, filename)
);
};
// Read a file
Book.prototype.readFile = function(filename) {
return fs.readFile(
path.join(this.root, filename),
{ encoding: "utf8" }
);
};
// Return stat for a file
Book.prototype.statFile = function(filename) {
return fs.stat(path.join(this.root, filename));
};
// List all files in the book
Book.prototype.listAllFiles = function() {
var that = this;
return fs.list(this.root, {
ignoreFiles: ['.ignore', '.gitignore', '.bookignore'],
ignoreRules: [
// Skip Git stuff
'.git/',
'.gitignore',
// Skip OS X meta data
'.DS_Store',
// Skip stuff installed by plugins
'node_modules',
// Skip book outputs
'_book',
'*.pdf',
'*.epub',
'*.mobi',
// Skip config files
'.ignore',
'.bookignore',
'book.json',
]
})
.then(function(_files) {
that.files = _files;
});
};
// Return true if the book is a multilingual book
Book.prototype.isMultilingual = function(filename) {
return this.books.length > 0;
};
// Return root of the parent
Book.prototype.parentRoot = function() {
if (this.parent) return this.parent.parentRoot();
return this.root;
};
// Resolve a path in book
Book.prototype.resolve = function(p) {
return path.resolve(this.root, p);
};
// Normalize a link to .html and convert README -> index
Book.prototype.contentLink = function(link) {
if (
path.basename(link) == "README"
|| link == this.readmeFile
) {
link = path.join(path.dirname(link), "index"+path.extname(link));
}
link = links.changeExtension(link, ".html");
return link;
}
// Index a page into the search index
Book.prototype.indexPage = function(page) {
var nav = this.navigation[page.path];
if (!nav) return;
this.log.debug.ln("index page", page.path);
this.searchIndex.add({
url: this.contentLink(page.path),
title: nav.title,
body: pageUtil.extractText(page.sections),
});
};
// Default structure paths to an extension
Book.prototype._defaultsStructure = function(filename) {
var that = this;
var extension = path.extname(filename);
that.readmeFile = that.readmeFile || that.config.getStructure("readme")+extension;
that.summaryFile = that.summaryFile || that.config.getStructure("summary")+extension;
that.glossaryFile = that.glossaryFile || that.config.getStructure("glossary")+extension;
that.langsFile = that.langsFile || that.config.getStructure("langs")+extension;
}
// Init and return a book
Book.init = function(root) {
var book = new Book(root);
var extensionToUse = ".md";
var chaptersPaths = function(chapters) {
return _.reduce(chapters || [], function(accu, chapter) {
if (!chapter.path) return accu;
return accu.concat(
_.filter([
{
title: chapter.title,
path: chapter.path
}
].concat(chaptersPaths(chapter.articles)))
);
}, []);
};
book.log.infoLn("init book at", root);
return fs.mkdirp(root)
.then(function() {
book.log.infoLn("detect structure from SUMMARY (if it exists)");
return book.parseSummary();
})
.fail(function() {
return Q();
})
.then(function() {
var summary = book.summaryFile || "SUMMARY.md";
var chapters = book.summary.chapters || [];
extensionToUse = path.extname(summary);
if (chapters.length == 0) {
chapters = [
{
title: "Summary",
path: "SUMMARY"+extensionToUse
},
{
title: "Introduction",
path: "README"+extensionToUse
}
];
}
return Q(chaptersPaths(chapters));
})
.then(function(chapters) {
// Create files that don't exist
return Q.all(_.map(chapters, function(chapter) {
var absolutePath = path.resolve(book.root, chapter.path);
return fs.exists(absolutePath)
.then(function(exists) {
book.log.infoLn("create", chapter.path);
if(exists) return;
return fs.mkdirp(path.dirname(absolutePath))
.then(function() {
return fs.writeFile(absolutePath, '# '+chapter.title+'\n');
});
});
}));
})
.then(function() {
book.log.infoLn("initialization is finished");
});
};
module.exports= Book;
+232
View File
@@ -0,0 +1,232 @@
var _ = require("lodash");
var Q = require("q");
var path = require("path");
var semver = require('semver');
var pkg = require('../package.json');
var fs = require("./utils/fs");
// Default plugins added to each books
var defaultsPlugins = [];
// Normalize a list of plugins to use
function normalizePluginsList(plugins) {
// Normalize list to an array
plugins = _.isString(plugins) ? plugins.split(",") : (plugins || []);
// Remove empty parts
plugins = _.compact(plugins);
// Divide as {name, version} to handle format like "myplugin@1.0.0"
plugins = _.map(plugins, function(plugin) {
if (plugin.name) return plugin;
var parts = plugin.split("@");
return {
'name': parts[0],
'version': parts[1] // optional
}
});
// List plugins to remove
var toremove = _.chain(plugins)
.filter(function(plugin) {
return plugin.name.length > 0 && plugin.name[0] == "-";
})
.map(function(plugin) {
return plugin.name.slice(1);
})
.value();
// Merge with defaults
plugins = _.chain(plugins)
.concat(_.map(defaultsPlugins, function(plugin) {
return { 'name': plugin }
}))
.uniq()
.value();
// Build final list
plugins = _.filter(plugins, function(plugin) {
return !_.contains(toremove, plugin.name) && !(plugin.name.length > 0 && plugin.name[0] == "-");
});
return plugins;
}
// Normalize a list of plugin name to use
function normalizePluginsNames(plugins) {
return _.pluck(normalizePluginsList(plugins), "name");
};
var Configuration = function(book, options) {
var that = this;
this.book = book;
this.options = _.cloneDeep(Configuration.DEFAULT);
this.options = _.merge(this.options, options || {});
// options.input == book.root
Object.defineProperty(this.options, "input", {
get: function () {
return that.book.root;
}
});
};
// Read and parse the configuration
Configuration.prototype.load = function() {
var that = this;
return Q()
.then(function() {
try {
var _config = require(path.resolve(that.book.root, that.options.configFile));
that.options = _.merge(
that.options,
_.omit(_config, 'configFile', 'defaultsPlugins', 'generator', 'extension')
);
}
catch(err) {
if (err instanceof SyntaxError) return Q.reject(err);
return Q();
}
})
.then(function() {
that.options.output = path.resolve(that.options.output || path.join(that.book.root, "_book"));
that.options.plugins = normalizePluginsList(that.options.plugins);
that.options.defaultsPlugins = normalizePluginsList(that.options.defaultsPlugins || "");
that.options.plugins = _.union(that.options.plugins, that.options.defaultsPlugins);
that.options.plugins = _.uniq(that.options.plugins, "name");
if (!semver.satisfies(pkg.version, that.options.gitbook)) {
throw "GitBook version doesn't satisfy version required by the book: "+that.options.gitbook;
}
if (that.options.gitbook == "*") {
that.book.log.warn.ln("you should specify a gitbook version to use in your book.json, for example: "+(_.first(pkg.version.split("."))+".x.x"));
}
});
};
// Extend the configuration
Configuration.prototype.extend = function(options) {
_.extend(this.options, options);
};
// Get structure file
Configuration.prototype.getStructure = function(name) {
return this.options.structure[name].split(".").slice(0, -1).join(".");
};
// Return normalized language
Configuration.prototype.normalizeLanguage = function() {
return _.first(this.options.language.split("-")).toLowerCase();
};
// Default configuration
Configuration.DEFAULT = {
// Options that can't be extend
"configFile": "book",
"generator": "site",
"extension": null,
// Book metadats (somes are extracted from the README by default)
"title": null,
"description": null,
"isbn": null,
"language": "en",
// version of gitbook to use
"gitbook": "*",
// Structure
"structure": {
"langs": "LANGS.md",
"readme": "README.md",
"glossary": "GLOSSARY.md",
"summary": "SUMMARY.md"
},
// CSS Styles
"styles": {
"website": "styles/website.css",
"ebook": "styles/ebook.css",
"pdf": "styles/pdf.css",
"mobi": "styles/mobi.css",
"epub": "styles/epub.css"
},
// Plugins list, can contain "-name" for removing default plugins
"plugins": [],
// Global configuration for plugins
"pluginsConfig": {
"fontSettings": {
"theme": null, //"sepia", "night" or "white",
"family": "sans",// "serif" or "sans",
"size": 2 // 1 - 4
}
},
// Variables for templating
"variables": {},
// 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": path.resolve(__dirname, '../theme'),
// Links in template (null: default, false: remove, string: new value)
"links": {
// Custom links at top of sidebar
"sidebar": {
//"Custom link name": "https://customlink.com"
},
// Sharing links
"sharing": {
"google": null,
"facebook": null,
"twitter": null,
"weibo": null,
"all": 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 file 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
},
//Header HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"headerTemplate": "",
//Footer HTML template. Available variables: _PAGENUM_, _TITLE_, _AUTHOR_ and _SECTION_.
"footerTemplate": ""
}
};
module.exports= Configuration;
-70
View File
@@ -1,70 +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 ebook
*/
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);
Generator.prototype.finish = function() {
var that = this;
return BaseGenerator.prototype.finish.apply(this)
.then(function() {
var d = Q.defer();
var _options = {
"--cover": that.options.cover
};
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(" ")
].join(" ");
exec(command, function (error, stdout, stderr) {
if (error) {
if (error.code == 127) {
error.message = "Need to install ebook-convert from Calibre";
} else {
error.message = error.message + " "+stdout;
}
return d.reject(error);
}
d.resolve();
});
return d.promise;
});
};
module.exports = Generator;
-60
View File
@@ -1,60 +0,0 @@
var Q = require("q");
var fs = require("fs");
var fsExtra = require("fs-extra");
var Ignore = require("fstream-ignore");
var getFiles = function(path) {
var d = Q.defer();
// Our list of files
var files = [];
var ig = Ignore({
path: path,
ignoreFiles: ['.ignore', '.gitignore', '.bookignore']
});
// Add extra rules to ignore common folders
ig.addIgnoreRules([
'.git/',
'.gitignore',
], '__custom_stuff');
// Push each file to our list
ig.on('child', function (c) {
files.push(
c.path.substr(c.root.path.length + 1) + (c.props.Directory === true ? '/' : '')
);
});
ig.on('end', function() {
// Normalize paths on Windows
if(process.platform === 'win32') {
return d.resolve(files.map(function(file) {
return file.replace(/\\/g, '/');
}));
}
// Simply return paths otherwise
return d.resolve(files);
});
ig.on('error', d.reject);
return d.promise;
};
module.exports = {
list: getFiles,
readFile: Q.denodeify(fs.readFile),
writeFile: Q.denodeify(fs.writeFile),
mkdirp: Q.denodeify(fsExtra.mkdirp),
copy: Q.denodeify(fsExtra.copy),
remove: Q.denodeify(fsExtra.remove),
symlink: Q.denodeify(fsExtra.symlink),
exists: function(path) {
var d = Q.defer();
fs.exists(path, d.resolve);
return d.promise;
},
};
-36
View File
@@ -1,36 +0,0 @@
var path = require("path");
var Q = require("q");
var fs = require("./fs");
var BaseGenerator = function(options) {
this.options = options;
};
BaseGenerator.prototype.convertFile = function(content, input) {
return Q.reject(new Error("Could not convert "+input));
};
BaseGenerator.prototype.transferFile = function(input) {
return fs.copy(
path.join(this.options.input, input),
path.join(this.options.output, input)
);
};
BaseGenerator.prototype.transferFolder = function(input) {
return fs.mkdirp(
path.join(this.options.output, input)
);
};
BaseGenerator.prototype.langsIndex = function(langs) {
return Q.reject(new Error("Langs index is not supported in this generator"));
};
BaseGenerator.prototype.finish = function() {
return Q.reject(new Error("Could not finish generation"));
};
module.exports = BaseGenerator;
-238
View File
@@ -1,238 +0,0 @@
var Q = require("q");
var _ = require("lodash");
var path = require("path");
var swig = require('swig');
var tmp = require('tmp');
var fs = require("./fs");
var parse = require("../parse");
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;
options = _.defaults(options || {}, {
// Folders to use
input: null,
output: null,
// Output generator
generator: "site",
// Book title, keyword, description
title: null,
description: null,
// Origin github repository id
github: null,
githubHost: 'https://github.com/',
// Theming
theme: path.resolve(__dirname, '../../theme')
});
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(", ")));
}
options.output = options.output || path.join(options.input, "_book");
// Clean output folder
return fs.remove(options.output)
.then(function() {
return fs.mkdirp(options.output);
})
// List all files in the repository
.then(function() {
return fs.list(options.input)
.then(function(_files) {
files = _files;
})
})
// Create the generator
.then(function() {
generator = new generators[options.generator](options);
})
// 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)
}));
})
);
})
// 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);
options.title = options.title || _readme.title;
options.description = options.description || _readme.description || defaultDescription;
});
})
// 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();
});
}
})
// Return all options
.then(function() {
return options;
});
};
/*
* Extract files from generate output in a temporary folder
*/
var generateFile = function(options) {
options = _.defaults(options || {}, {
input: null,
output: null,
extension: null
});
return Q.nfcall(tmp.dir)
.then(function(tmpDir) {
return generate(
_.extend({},
options,
{
output: tmpDir
})
)
.then(function(_options) {
var ext = options.extension;
var outputFile = options.output || path.resolve(options.input, "book."+ext);
var copyFile = function(lang) {
var _outputFile = outputFile;
var _tmpDir = tmpDir;
if (lang) {
_outputFile = _outputFile.slice(0, -path.extname(_outputFile).length)+"_"+lang+path.extname(_outputFile);
_tmpDir = path.join(_tmpDir, lang);
}
return fs.copy(
path.join(_tmpDir, "index."+ext),
_outputFile
);
};
// Multi-langs book
return Q()
.then(function() {
if (_options.langsSummary) {
return Q.all(
_.map(_options.langsSummary.list, function(lang) {
return copyFile(lang.lang);
})
);
} else {
return copyFile();
}
})
.then(function() {
return fs.remove(tmpDir);
});
});
});
};
module.exports = {
generators: generators,
folder: generate,
file: generateFile
};
-69
View File
@@ -1,69 +0,0 @@
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var fs = require('./fs');
var parse = require('../parse');
// Extract paths out of a summary
function paths(summary) {
return _.reduce(summary.chapters, function(accu, chapter) {
return accu.concat(
_.filter([chapter.path].concat(_.pluck(chapter.articles, 'path')))
);
}, []);
}
// Get the parent folders out of a group of files
function folders(files) {
return _.chain(files)
.map(function(file) {
return path.dirname(file);
})
.uniq()
.value();
}
function initDir(dir) {
return fs.readFile(path.join(dir, 'SUMMARY.md'), 'utf8')
.then(function(src) {
// Parse summary
return parse.summary(src);
})
.then(function(summary) {
// Extract paths from summary
return paths(summary);
})
.then(function(paths) {
// Convert to absolute paths
return _.map(paths, function(file) {
return path.resolve(file);
});
})
.then(function(files) {
// Create folders
return Q.all(_.map(folders(files), function(folder) {
return fs.mkdirp(folder);
}))
.then(_.constant(files));
})
.then(function(files) {
// Create files that don't exist
return Q.all(_.map(files, function(file) {
return fs.exists(file)
.then(function(exists) {
if(exists) return;
return fs.writeFile(file, '');
});
}));
})
.fail(function(err) {
console.error(err.stack);
});
}
// Exports
module.exports = initDir;
-47
View File
@@ -1,47 +0,0 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../generator");
var Generator = function() {
BaseGenerator.apply(this, arguments);
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.transferFile = function(input) {
// ignore
};
Generator.prototype.convertFile = function(content, input) {
var that = this;
var json = {
progress: parse.progress(this.options.navigation, input)
};
return Q()
.then(function() {
return parse.page(content, {
repo: that.options.githubId,
dir: path.dirname(input) || '/'
});
})
.then(function(sections) {
json.sections = sections;
})
.then(function() {
return fs.writeFile(
path.join(that.options.output, input.replace(".md", ".json")),
JSON.stringify(json, null, 4)
);
});
};
Generator.prototype.finish = function() {
// ignore
};
module.exports = Generator;
-84
View File
@@ -1,84 +0,0 @@
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 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
*/
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Load base template
this.template = swig.compileFile(path.resolve(this.options.theme, 'templates/page.html'));
// List of pages content
this.pages = {};
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.convertFile = function(content, input) {
var that = this;
var json = {
path: input,
progress: parse.progress(this.options.navigation, input)
};
return Q()
.then(function() {
return parse.page(content, {
repo: that.options.githubId,
dir: path.dirname(input) || '/',
outdir: './',
});
})
.then(function(sections) {
json.content = sections;
})
.then(function() {
that.pages[input] = json;
});
};
Generator.prototype.finish = function() {
var that = this;
var basePath = ".";
var output = path.join(this.options.output, "index.html");
return Q()
// Generate html
.then(function(pages) {
return that._writeTemplate(that.template, {
pages: that.pages,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
}, output);
})
// Copy assets
.then(function() {
return fs.copy(
path.join(that.options.theme, "assets"),
path.join(that.options.output, "gitbook")
);
});
};
module.exports = Generator;
-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;
-156
View File
@@ -1,156 +0,0 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var swig = require('swig');
var fs = require("../fs");
var parse = require("../../parse");
var BaseGenerator = require("../generator");
var indexer = require('./search_indexer');
// 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);
// Attach methods to instance
_.bindAll(this);
this.revision = Date.now();
this.indexer = indexer();
// Load base template
this.template = swig.compileFile(path.resolve(this.options.theme, 'templates/site.html'));
this.langsTemplate = swig.compileFile(path.resolve(this.options.theme, 'templates/langs.html'));
};
util.inherits(Generator, BaseGenerator);
// Generate a template
Generator.prototype._writeTemplate = function(tpl, options, output) {
var that = this;
return Q()
.then(function(sections) {
return tpl(_.extend({
revision: that.revision,
title: that.options.title,
description: that.options.description,
githubAuthor: that.options.github ? that.options.github.split("/")[0] : "",
githubId: that.options.github,
githubHost: that.options.githubHost,
summary: that.options.summary,
allNavigation: that.options.navigation
}, options));
})
.then(function(html) {
return fs.writeFile(
output,
html
);
});
};
Generator.prototype.indexPage = function(lexed, pagePath) {
this.indexer.add(lexed, pagePath);
return Q();
};
// 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";
var input = path.join(this.options.input, _input);
var output = path.join(this.options.output, _output);
var basePath = path.relative(path.dirname(output), this.options.output) || ".";
return Q()
.then(function() {
// Lex page
return parse.lex(content);
})
.then(function(lexed) {
// Index page in search
return that.indexPage(lexed, _output)
.then(_.constant(lexed));
})
.then(function(lexed) {
// Get HTML generated sections
return parse.page(lexed, {
repo: that.options.githubId,
dir: path.dirname(_input) || '/',
outdir: path.dirname(_input) || '/',
});
})
.then(function(sections) {
return that._writeTemplate(that.template, {
progress: progress,
_input: _input,
content: sections,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
}, output);
});
};
// Generate languages index
Generator.prototype.langsIndex = function(langs) {
var that = this;
var basePath = ".";
return this._writeTemplate(this.langsTemplate, {
langs: langs.list,
basePath: basePath,
staticBase: path.join(basePath, "gitbook"),
}, path.join(this.options.output, "index.html"))
.then(function() {
// Copy assets
return that.copyAssets();
});
};
// Copy assets
Generator.prototype.copyAssets = function() {
var that = this;
return fs.copy(
path.join(that.options.theme, "assets"),
path.join(that.options.output, "gitbook")
);
};
// Dump search index to disk
Generator.prototype.writeSearchIndex = function() {
return fs.writeFile(
path.join(this.options.output, 'search_index.json'),
this.indexer.dump()
);
};
Generator.prototype.finish = function() {
return this.copyAssets()
.then(this.writeSearchIndex);
};
module.exports = Generator;
-71
View File
@@ -1,71 +0,0 @@
var Q = require("q");
var _ = require("lodash");
var lunr = require('lunr');
var marked = require('marked');
var textRenderer = require('marked-text-renderer');
function Indexer() {
if(!(this instanceof Indexer)) {
return new Indexer();
}
_.bindAll(this);
// Setup lunr index
this.idx = lunr(function () {
this.ref('url');
this.field('title', { boost: 10 });
this.field('body');
});
this.renderer = textRenderer();
}
Indexer.prototype.text = function(nodes) {
// Copy section
var section = _.toArray(nodes);
// marked's Render expects this, we don't use it yet
section.links = {};
var options = _.extend({}, marked.defaults, {
renderer: this.renderer
});
return marked.parser(section, options);
};
Indexer.prototype.addSection = function(path, section) {
var url = [path, section.id].join('#');
var title = this.text(
_.filter(section, {'type': 'heading'})
);
var body = this.text(
_.omit(section, {'type': 'heading'})
);
// Add to lunr index
this.idx.add({
url: url,
title: title,
body: body,
});
};
Indexer.prototype.add = function(lexedPage, url) {
var sections = lexedPage;
_.map(sections, _.partial(this.addSection, url));
};
Indexer.prototype.dump = function() {
return JSON.stringify(this.idx);
};
// Exports
module.exports = Indexer;
+83
View File
@@ -0,0 +1,83 @@
var _ = require("lodash");
var path = require("path");
var Q = require("q");
var fs = require("./utils/fs");
var Plugin = require("./plugin");
var BaseGenerator = function(book) {
this.book = book;
Object.defineProperty(this, "options", {
get: function () {
return this.book.options;
}
});
_.bindAll(this);
};
BaseGenerator.prototype.callHook = function(name, data) {
return this.book.plugins.hook(name, data);
};
// Prepare the genertor
BaseGenerator.prototype.prepare = function() {
var that = this;
return that.callHook("init");
};
// Write a parsed file to the output
BaseGenerator.prototype.convertFile = function(input) {
return Q.reject(new Error("Could not convert "+input));
};
// Copy file to the output (non parsable)
BaseGenerator.prototype.transferFile = function(input) {
return fs.copy(
path.join(this.book.root, input),
path.join(this.options.output, input)
);
};
// Copy a folder to the output
BaseGenerator.prototype.transferFolder = function(input) {
return fs.mkdirp(
path.join(this.book.options.output, input)
);
};
// Copy the cover picture
BaseGenerator.prototype.copyCover = function() {
var that = this;
return Q.all([
fs.copy(path.join(that.book.root, "cover.jpg"), path.join(that.options.output, "cover.jpg")),
fs.copy(path.join(that.book.root, "cover_small.jpg"), path.join(that.options.output, "cover_small.jpg"))
])
.fail(function() {
// If orignaly from multi-lang, try copy from parent
if (!that.isMultilingual()) return;
return Q.all([
fs.copy(path.join(that.book.parentRoot(), "cover.jpg"), path.join(that.options.output, "cover.jpg")),
fs.copy(path.join(that.book.parentRoot(), "cover_small.jpg"), path.join(that.options.output, "cover_small.jpg"))
]);
})
.fail(function(err) {
return Q();
});
};
// Generate the langs index
BaseGenerator.prototype.langsIndex = function(langs) {
return Q.reject(new Error("Langs index is not supported in this generator"));
};
// At teh end of the generation
BaseGenerator.prototype.finish = function() {
return Q.reject(new Error("Could not finish generation"));
};
module.exports = BaseGenerator;
+137
View File
@@ -0,0 +1,137 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var exec = require('child_process').exec;
var fs = require("../utils/fs");
var stringUtils = require("../utils/string");
var BaseGenerator = require("./website");
var Generator = function(book, format) {
BaseGenerator.apply(this, arguments);
// eBook format
this.ebookFormat = format;
// Resources namespace
this.namespace = "ebook";
// Styles to use
this.styles = _.compact(["ebook", this.ebookFormat]);
// Convert images (svg -> png)
this.convertImages = true;
};
util.inherits(Generator, BaseGenerator);
Generator.prototype.prepareTemplates = function() {
this.templates["page"] = this.book.plugins.template("ebook:page") || path.resolve(this.options.theme, 'templates/ebook/page.html');
this.templates["summary"] = this.book.plugins.template("ebook:summary") || path.resolve(this.options.theme, 'templates/ebook/summary.html');
this.templates["glossary"] = this.book.plugins.template("ebook:glossary") || path.resolve(this.options.theme, 'templates/ebook/glossary.html');
return Q();
};
// Generate table of contents
Generator.prototype.writeSummary = function() {
var that = this;
that.book.log.info.ln("write SUMMARY.html");
return this._writeTemplate(this.templates["summary"], {}, path.join(this.options.output, "SUMMARY.html"));
};
Generator.prototype.langsIndex = function(langs) {
return Q();
};
Generator.prototype.finish = function() {
var that = this;
return BaseGenerator.prototype.finish.apply(this)
.then(function() {
return that.writeSummary();
})
.then(function() {
if (!that.ebookFormat) return Q();
var d = Q.defer();
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,
"--title": that.options.title,
"--comments": that.options.description,
"--isbn": that.options.isbn,
"--authors": that.options.author,
"--publisher": "GitBook",
"--chapter": "descendant-or-self::*[contains(concat(' ', normalize-space(@class), ' '), ' book-chapter ')]",
"--chapter-mark": "pagebreak",
"--page-breaks-before": "/",
"--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 ')]",
"--no-chapters-in-toc": true,
"--max-levels": "1",
"--breadth-first": true
};
if (that.ebookFormat == "pdf") {
var pdfOptions = that.options.pdf;
_.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-default-font-size": String(pdfOptions.fontSize),
"--pdf-mono-font-size": String(pdfOptions.fontSize),
"--paper-size": String(pdfOptions.paperSize),
"--pdf-page-numbers": Boolean(pdfOptions.pageNumbers),
"--pdf-header-template": String(pdfOptions.headerTemplate),
"--pdf-footer-template": String(pdfOptions.footerTemplate)
});
}
var command = [
"ebook-convert",
path.join(that.options.output, "SUMMARY.html"),
path.join(that.options.output, "index."+that.ebookFormat),
stringUtils.optionsToShellArgs(_options)
].join(" ");
that.book.log.info("start conversion to", that.ebookFormat, "....");
var child = exec(command, function (error, stdout, stderr) {
if (error) {
that.book.log.info.fail();
if (error.code == 127) {
error.message = "Need to install ebook-convert from Calibre";
} else {
error.message = error.message + " "+stdout;
}
return d.reject(error);
}
that.book.log.info.ok();
d.resolve();
});
child.stdout.on('data', function (data) {
that.book.log.debug(data);
});
child.stderr.on('data', function (data) {
that.book.log.debug(data);
});
return d.promise;
});
};
module.exports = Generator;
+11
View File
@@ -0,0 +1,11 @@
var _ = require("lodash");
var EbookGenerator = require("./ebook");
module.exports = {
json: require("./json"),
website: require("./website"),
ebook: EbookGenerator,
pdf: _.partialRight(EbookGenerator, "pdf"),
mobi: _.partialRight(EbookGenerator, "mobi"),
epub: _.partialRight(EbookGenerator, "epub")
};
+72
View File
@@ -0,0 +1,72 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var fs = require("../utils/fs");
var BaseGenerator = require("../generator");
var links = require("../utils/links");
var Generator = function() {
BaseGenerator.apply(this, arguments);
};
util.inherits(Generator, BaseGenerator);
// Ignore some methods
Generator.prototype.transferFile = function(input) { };
Generator.prototype.finish = function() { };
// Convert an input file
Generator.prototype.convertFile = function(input) {
var that = this;
return that.book.parsePage(input)
.then(function(page) {
var json = {
progress: page.progress,
sections: page.sections
};
var output = links.changeExtension(page.path, ".json");
output = path.join(that.options.output, output);
return fs.writeFile(
output,
JSON.stringify(json, null, 4)
);
});
};
// Generate languages index
// Contains the first languages readme and langs infos
Generator.prototype.langsIndex = function(langs) {
var that = this;
if (langs.length == 0) return Q.reject("Need at least one language");
var mainLang = _.first(langs).lang;
var readme = links.changeExtension(that.book.readmeFile, ".json");
return Q()
.then(function() {
// Read readme from main language
return fs.readFile(
path.join(that.options.output, mainLang, readme)
);
})
.then(function(content) {
// Extend it with infos about the languages
var json = JSON.parse(content);
_.extend(json, {
langs: langs
});
// Write it as README.json
return fs.writeFile(
path.join(that.options.output, "README.json"),
JSON.stringify(json, null, 4)
);
});
};
module.exports = Generator;
+285
View File
@@ -0,0 +1,285 @@
var util = require("util");
var path = require("path");
var Q = require("q");
var _ = require("lodash");
var nunjucks = require("nunjucks");
var AutoEscapeExtension = require("nunjucks-autoescape");
var FilterExtension = require("nunjucks-filter");
var I18nExtension = require("nunjucks-i18n");
var fs = require("../utils/fs");
var BaseGenerator = require("../generator");
var links = require("../utils/links");
var pageUtil = require("../utils/page");
var pkg = require("../../package.json");
var Generator = function() {
BaseGenerator.apply(this, arguments);
// Revision
this.revision = new Date();
// Resources namespace
this.namespace = "website";
// Style to integrates in the output
this.styles = ["website"];
// Convert images (svg -> png)
this.convertImages = false;
// Templates
this.templates = {};
};
util.inherits(Generator, BaseGenerator);
// Prepare the genertor
Generator.prototype.prepare = function() {
return BaseGenerator.prototype.prepare.apply(this)
.then(this.prepareStyles)
.then(this.prepareTemplates)
.then(this.prepareTemplateEngine);
};
// Prepare all styles
Generator.prototype.prepareStyles = function() {
var that = this;
this.styles = _.chain(this.styles)
.map(function(style) {
var stylePath = that.options.styles[style];
if (fs.existsSync(path.resolve(that.book.root, stylePath))) {
return stylePath;
}
return null;
})
.compact()
.value();
return Q();
};
// Prepare templates
Generator.prototype.prepareTemplates = function() {
this.templates["page"] = this.book.plugins.template("site:page") || path.resolve(this.options.theme, 'templates/website/page.html');
this.templates["langs"] = this.book.plugins.template("site:langs") || path.resolve(this.options.theme, 'templates/website/langs.html');
this.templates["glossary"] = this.book.plugins.template("site:glossary") || path.resolve(this.options.theme, 'templates/website/glossary.html');
return Q();
};
// Prepare template engine
Generator.prototype.prepareTemplateEngine = function() {
var that = this;
return fs.readdir(path.resolve(__dirname, "../../theme/i18n"))
.then(function(locales) {
var language = that.book.config.normalizeLanguage();
locales = _.chain(locales)
.map(function(local) {
local = path.basename(local, ".json");
return [local, require("../../theme/i18n/"+local)];
})
.object()
.value();
if (!_.contains(_.keys(locales), language) && language != "en") {
that.book.log.warn.ln("Language '"+language+"' is not available as a layout locales (en, "+_.keys(locales).join(", ")+")");
}
var folders = _.chain(that.templates)
.values()
.map(path.dirname)
.uniq()
.value();
that.env = new nunjucks.Environment(
new nunjucks.FileSystemLoader(folders),
{
autoescape: true
}
);
// Add filter
that.env.addFilter("contentLink", that.book.contentLink.bind(that.book));
that.env.addFilter('lvl', function(lvl) {
return lvl.split(".").length;
});
// Add extension
that.env.addExtension('AutoEscapeExtension', new AutoEscapeExtension(that.env));
that.env.addExtension('FilterExtension', new FilterExtension(that.env));
that.env.addExtension('I18nExtension', new I18nExtension({
env: that.env,
translations: locales,
locale: "language"
}));
});
};
// Finis generation
Generator.prototype.finish = function() {
return this.copyAssets()
.then(this.copyCover)
.then(this.writeGlossary)
.then(this.writeSearchIndex);
};
// Convert an input file
Generator.prototype.convertFile = function(input) {
var that = this;
return that.book.parsePage(input, {
convertImages: that.convertImages,
interpolateTemplate: function(page) {
return that.callHook("page:before", page);
},
interpolateContent: function(page) {
return that.callHook("page", page);
}
})
.then(function(page) {
var relativeOutput = that.book.contentLink(page.path);
var output = path.join(that.options.output, relativeOutput);
var basePath = path.relative(path.dirname(output), that.options.output) || ".";
if (process.platform === 'win32') basePath = basePath.replace(/\\/g, '/');
that.book.log.info.ln("write parsed file", page.path, "to", relativeOutput);
return that._writeTemplate(that.templates["page"], {
progress: page.progress,
_input: page.path,
content: page.sections,
basePath: basePath,
staticBase: links.join(basePath, "gitbook")
}, output);
});
};
// Write the index for langs
Generator.prototype.langsIndex = function(langs) {
var that = this;
return this._writeTemplate(this.templates["langs"], {
langs: langs
}, path.join(this.options.output, "index.html"));
};
// Write glossary
Generator.prototype.writeGlossary = function() {
var that = this;
// No glossary
if (this.book.glossary.length == 0) return Q();
return this._writeTemplate(this.templates["glossary"], {}, path.join(this.options.output, "GLOSSARY.html"));
};
// Write the search index
Generator.prototype.writeSearchIndex = function() {
var that = this;
return fs.writeFile(
path.join(this.options.output, "search_index.json"),
JSON.stringify(this.book.searchIndex)
);
};
// Convert a page into a normalized data set
Generator.prototype.normalizePage = function(page) {
var that = this;
var _callHook = function(name) {
return that.callHook(name, page)
.then(function(_page) {
page = _page;
return page;
});
};
return Q()
.then(function() {
return _callHook("page");
})
.then(function() {
return page;
});
};
// Generate a template
Generator.prototype._writeTemplate = function(tpl, options, output, interpolate) {
var that = this;
interpolate = interpolate || _.identity;
return Q()
.then(function(sections) {
return that.env.render(
tpl,
_.extend({
gitbook: {
version: pkg.version
},
styles: that.styles,
revision: that.revision,
title: that.options.title,
description: that.options.description,
language: that.book.config.normalizeLanguage(),
glossary: that.book.glossary,
summary: that.book.summary,
allNavigation: that.book.navigation,
plugins: {
resources: that.book.plugins.resources(that.namespace)
},
pluginsConfig: JSON.stringify(that.options.pluginsConfig),
htmlSnippet: _.partial(_.partialRight(that.book.plugins.html, that, options), that.namespace),
options: that.options,
basePath: ".",
staticBase: path.join(".", "gitbook"),
}, options)
);
})
.then(interpolate)
.then(function(html) {
return fs.writeFile(
output,
html
);
});
};
// Copy assets
Generator.prototype.copyAssets = function() {
var that = this;
// Copy gitbook assets
return fs.copy(
path.join(that.options.theme, "assets"),
path.join(that.options.output, "gitbook")
)
// Copy plugins assets
.then(function() {
return Q.all(
_.map(that.book.plugins.list, function(plugin) {
var pluginAssets = path.join(that.options.output, "gitbook/plugins/", plugin.name);
return plugin.copyAssets(pluginAssets, that.namespace);
})
);
});
};
module.exports = Generator;
+208 -3
View File
@@ -1,5 +1,210 @@
module.exports = {
parse: require('./parse/'),
generate: require('./generate/')
var Q = require("q");
var _ = require("lodash");
var path = require("path");
var tinylr = require('tiny-lr-fork');
var color = require('bash-color');
var Book = require("./book");
var Plugin = require("./plugin");
var Server = require("./utils/server");
var stringUtils = require("./utils/string");
var watch = require("./utils/watch");
var logger = require("./utils/logger");
var LOG_OPTION = {
name: "log",
description: "Minimum log level to display",
values: _.chain(logger.LEVELS).keys().map(stringUtils.toLowerCase).value(),
defaults: "info"
};
var FORMAT_OPTION = {
name: "format",
description: "Format to build to",
values: ["website", "json", "ebook"],
defaults: "website"
};
module.exports = {
Book: Book,
LOG_LEVELS: logger.LEVELS,
commands: _.flatten([
{
name: "build [book] [output]",
description: "build a book",
options: [
FORMAT_OPTION,
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var output = args[1] || path.join(input, "_book");
var book = new Book(input, _.extend({}, {
'config': {
'output': output
},
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generate(kwargs.format);
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
_.map(["pdf", "epub", "mobi"], function(ebookType) {
return {
name: ebookType+" [book] [output]",
description: "build a book to "+ebookType,
options: [
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var output = args[1];
var book = new Book(input, _.extend({}, {
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generateFile(output, {
ebookFormat: ebookType
});
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
};
}),
{
name: "serve [book]",
description: "Build then serve a gitbook from a directory",
options: [
{
name: "port",
description: "Port for server to listen on",
defaults: 4000
},
{
name: "lrport",
description: "Port for livereload server to listen on",
defaults: 35729
},
{
name: "watch",
description: "Enable/disable file watcher",
defaults: true
},
FORMAT_OPTION,
LOG_OPTION
],
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var server = new Server();
// Init livereload server
var lrServer = tinylr({});
var lrPath = undefined;
var generate = function() {
if (server.isRunning()) console.log("Stopping server");
return server.stop()
.then(function() {
var book = new Book(input, _.extend({}, {
'config': {
'defaultsPlugins': ["livereload"]
},
'logLevel': kwargs.log
}));
return book.parse()
.then(function() {
return book.generate(kwargs.format);
})
.thenResolve(book);
})
.then(function(book) {
console.log();
console.log('Starting server ...');
return server.start(book.options.output, kwargs.port)
.then(function() {
console.log('Serving book on http://localhost:'+kwargs.port);
if (lrPath) {
// trigger livereload
lrServer.changed({
body: {
files: [lrPath]
}
});
}
if (!kwargs.watch) return;
return watch(book.root)
.then(function(filepath) {
// set livereload path
lrPath = filepath;
console.log("Restart after change in files");
console.log('');
return generate();
})
})
});
};
return Q.nfcall(lrServer.listen.bind(lrServer), kwargs.lrport)
.then(function() {
console.log('Live reload server started on port:', kwargs.lrport);
console.log('Press CTRL+C to quit ...');
console.log('')
return generate();
});
}
},
{
name: "install [book]",
description: "install plugins dependencies",
exec: function(args, kwargs) {
var input = args[0] || process.cwd();
var book = new Book(input);
return book.config.load()
.then(function() {
return book.plugins.install();
})
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
},
{
name: "init [directory]",
description: "create files and folders based on contents of SUMMARY.md",
exec: function(args, kwargs) {
return Book.init(args[0] || process.cwd())
.then(function(){
console.log("");
console.log(color.green("Done, without error"));
});
}
}
])
};
-9
View File
@@ -1,9 +0,0 @@
module.exports = {
summary: require('./summary'),
langs: require('./langs'),
page: require('./page'),
lex: require('./lex'),
progress: require('./progress'),
navigation: require('./navigation'),
readme: require('./readme')
};
-23
View File
@@ -1,23 +0,0 @@
var _ = require("lodash")
var parseSummary = require("./summary");
var parseLangs = function(content) {
var summary = parseSummary(content);
return {
list: _.chain(summary.chapters)
.filter(function(entry) {
return entry.path != null;
})
.map(function(entry) {
return {
title: entry.title,
path: entry.path,
lang: entry.path.replace("/", "")
};
})
.value()
}
};
module.exports = parseLangs;
-125
View File
@@ -1,125 +0,0 @@
var _ = require('lodash');
var marked = require('marked');
// Split a page up into sections (lesson, exercises, ...)
function splitSections(nodes) {
var section = [];
return _.reduce(nodes, function(sections, el) {
if(el.type === 'hr') {
sections.push(section);
section = [];
} else {
section.push(el);
}
return sections;
}, []).concat([section]); // Add remaining nodes
}
function isQuizNode(node) {
return (/^[(\[][ x][)\]]/).test(node.text || node);
}
function isExercise(nodes) {
var codeType = { type: 'code' };
// Number of code nodes in section
var len = _.filter(nodes, codeType).length;
return (
// Got 3 or 4 code blocks
(len === 3 || len === 4) &&
// Ensure all nodes are at the end
_.all(_.last(nodes, len), codeType)
);
}
function isQuiz(nodes) {
if (nodes.length < 3) {
return false;
}
// Support having a first paragraph block
// before our series of questions
var quizNodes = nodes.slice(nodes[0].type === 'paragraph' ? 1 : 0);
// No questions
if (!_.some(quizNodes, { type: 'blockquote_start' })) {
return false;
}
// Check if section has list of questions
// or table of questions
var listIdx = _.findIndex(quizNodes, { type: 'list_item_start' });
var tableIdx = _.findIndex(quizNodes, { type: 'table' });
if(
// List of questions
listIdx !== -1 && isQuizNode(quizNodes[listIdx + 1]) ||
// Table of questions
(
tableIdx !== -1 &&
_.every(quizNodes[tableIdx].cells[0].slice(1), isQuizNode)
)
) {
return true;
}
return false;
}
// What is the type of this section
function sectionType(nodes, idx) {
if(isExercise(nodes)) {
return 'exercise';
} else if(isQuiz(nodes)) {
return 'quiz';
}
return 'normal';
}
// Generate a uniqueId to identify this section in our code
function sectionId(section, idx) {
return _.uniqueId('gitbook_');
}
function lexPage(src) {
// Lex file
var nodes = marked.lexer(src);
return _.chain(splitSections(nodes))
.map(function(section, idx) {
// Detect section type
section.type = sectionType(section, idx);
return section;
})
.map(function(section, idx) {
// Give each section an ID
section.id = sectionId(section, idx);
return section;
})
.filter(function(section) {
return !_.isEmpty(section);
})
.reduce(function(sections, section) {
var last = _.last(sections);
// Merge normal sections together
if(last && last.type === section.type && last.type === 'normal') {
last.push.apply(last, [{'type': 'hr'}].concat(section));
} else {
// Add to list of sections
sections.push(section);
}
return sections;
}, [])
.value();
}
// Exports
module.exports = lexPage;
-89
View File
@@ -1,89 +0,0 @@
var _ = require('lodash');
// Cleans up an article/chapter object
// remove 'articles' attributes
function clean(obj) {
return obj && _.omit(obj, ['articles']);
}
// Returns from a summary a map of
/*
{
"file/path.md": {
prev: ...,
next: ...,
},
...
}
*/
function navigation(summary, files) {
// Support single files as well as list
files = _.isArray(files) ? files : (_.isString(files) ? [files] : null);
// Mapping of prev/next for a give path
var mapping = {};
// Special README nav
var README_NAV = {
path: 'README.md',
title: 'Introduction',
};
// Walk the chapter/article tree and create navigation entires
_.each(summary.chapters, function(chapter, idx, chapters) {
// Skip if no path
if(!chapter.path) return;
var currentChapter = clean(chapter);
var prevChapter = (idx-1 < 0) ? README_NAV : chapters[idx-1];
var nextChapter = (idx+1 >= chapters.length) ? null : chapters[idx+1];
var prev = (!prevChapter || _.isEmpty(prevChapter.articles)) ?
prevChapter : _.last(prevChapter.articles);
var next = (!chapter || _.isEmpty(chapter.articles)) ?
nextChapter : _.first(chapter.articles);
// Add chapter mapping
mapping[chapter.path] = {
title: chapter.title,
prev: clean(prev),
next: clean(next),
level: (idx+1).toString(),
};
// Check a chapter's articles
_.each(chapter.articles, function(article, _idx, articles) {
// Skip if no path
if(!article.path) return;
var prev = (_idx-1 < 0) ? currentChapter : clean(articles[_idx-1]);
var next = (_idx+1 >= articles.length) ? nextChapter : clean(articles[_idx+1]);
mapping[article.path] = {
title: article.title,
prev: clean(prev),
next: clean(next),
level: [idx+1, _idx+1].join('.'),
};
});
});
// Hack for README.html
mapping['README.md'] = {
title: README_NAV.title,
prev: null,
next: clean(summary.chapters[0]),
level: '0',
};
// Filter for only files we want
if(files) {
return _.pick(mapping, files);
}
return mapping;
}
// Exports
module.exports = navigation;
-156
View File
@@ -1,156 +0,0 @@
var _ = require('lodash');
var marked = require('marked');
var hljs = require('highlight.js');
var lex = require('./lex');
var renderer = require('./renderer');
var lnormalize = require('../utils/lang').normalize;
// Render a section using our custom renderer
function render(section, _options) {
// Copy section
section = _.toArray(section);
// marked's Render expects this, we don't use it yet
section.links = {};
// Build options using defaults and our custom renderer
var options = _.extend({}, marked.defaults, {
renderer: renderer(null, _options),
// Synchronous highlighting with highlight.js
highlight: function (code, lang) {
if(!lang) return code;
// Normalize lang
lang = lnormalize(lang);
try {
return hljs.highlight(lang, code).value;
} catch(e) { }
return code;
}
});
return marked.parser(section, options);
}
function quizQuestion(node) {
if (node.text) {
node.text = node.text.replace(/^([\[(])x([\])])/, "$1 $2");
} else {
return node.replace(/^([\[(])x([\])])/, "$1 $2");
}
}
function parsePage(src, options) {
options = options || {};
// Lex if not already lexed
return (_.isArray(src) ? src : lex(src))
.map(function(section) {
// Transform given type
if(section.type === 'exercise') {
var nonCodeNodes = _.reject(section, {
'type': 'code'
});
var codeNodes = _.filter(section, {
'type': 'code'
});
// Languages in code blocks
var langs = _.pluck(codeNodes, 'lang').map(lnormalize);
// Check that they are all the same
var validLangs = _.all(_.map(langs, function(lang) {
return lang && lang === langs[0];
}));
// Main language
var lang = validLangs ? langs[0] : null;
return {
id: section.id,
type: section.type,
content: render(nonCodeNodes),
lang: lang,
code: {
base: codeNodes[0].text,
solution: codeNodes[1].text,
validation: codeNodes[2].text,
// Context is optional
context: codeNodes[3] ? codeNodes[3].text : null,
}
};
} else if (section.type === 'quiz') {
var quiz = [], question, foundFeedback = false;
var nonQuizNodes = section[0].type === 'paragraph' && section[1].type !== 'list_start' ? [section[0]] : [];
var quizNodes = section.slice(0);
quizNodes.splice(0, nonQuizNodes.length);
for (var i = 0; i < quizNodes.length; i++) {
var node = quizNodes[i];
if (question && (((node.type === 'list_end' || node.type === 'blockquote_end') && i === quizNodes.length - 1)
|| node.type === 'table' || (node.type === 'paragraph' && !foundFeedback))) {
quiz.push({
base: render(question.questionNodes),
solution: render(question.solutionNodes),
feedback: render(question.feedbackNodes)
});
}
if (node.type === 'table' || (node.type === 'paragraph' && !foundFeedback)) {
question = { questionNodes: [], solutionNodes: [], feedbackNodes: [] };
}
if (node.type === 'blockquote_start') {
foundFeedback = true;
} else if (node.type === 'blockquote_end') {
foundFeedback = false;
}
if (node.type === 'table') {
question.solutionNodes.push(_.cloneDeep(node));
node.cells = node.cells.map(function(row) {
return row.map(quizQuestion);
});
question.questionNodes.push(node);
} else if (!/blockquote/.test(node.type)) {
if (foundFeedback) {
question.feedbackNodes.push(node);
} else if (node.type === 'paragraph' || node.type === 'text'){
question.solutionNodes.push(_.cloneDeep(node));
quizQuestion(node);
question.questionNodes.push(node);
} else {
question.solutionNodes.push(node);
question.questionNodes.push(node);
}
}
}
return {
id: section.id,
type: section.type,
content: render(nonQuizNodes),
quiz: quiz
};
}
// Render normal pages
return {
id: section.id,
type: section.type,
content: render(section, options)
};
});
}
// Exports
module.exports = parsePage;
-32
View File
@@ -1,32 +0,0 @@
var _ = require('lodash');
var marked = require('marked');
function extractFirstNode(nodes, nType) {
return _.chain(nodes)
.filter(function(node) {
return node.type == nType;
})
.pluck("text")
.first()
.value();
}
function parseReadme(src) {
var nodes, title, description;
// Parse content
nodes = marked.lexer(src);
var title = extractFirstNode(nodes, "heading");
var description = extractFirstNode(nodes, "paragraph");
return {
title: title,
description: description
};
}
// Exports
module.exports = parseReadme;
-115
View File
@@ -1,115 +0,0 @@
var url = require('url');
var inherits = require('util').inherits;
var path = require('path');
var marked = require('marked');
var rendererId = 0;
function GitBookRenderer(options, extra_options) {
if(!(this instanceof GitBookRenderer)) {
return new GitBookRenderer(options, extra_options);
}
GitBookRenderer.super_.call(this, options);
this._extra_options = extra_options;
this.quizRowId = 0;
this.id = rendererId++;
}
inherits(GitBookRenderer, marked.Renderer);
GitBookRenderer.prototype._unsanitized = function(href) {
var prot = '';
try {
prot = decodeURIComponent(unescape(href))
.replace(/[^\w:]/g, '')
.toLowerCase();
} catch (e) {
return true;
}
if(prot.indexOf('javascript:') === 0) {
return true;
}
return false;
};
GitBookRenderer.prototype.link = function(href, title, text) {
// Don't build if it looks malicious
if (this.options.sanitize && this._unsanitized(href)) {
return '';
}
// Parsed version of the url
var parsed = url.parse(href);
var o = this._extra_options;
// Relative link, rewrite it to point to github repo
if(!parsed.protocol && parsed.path && parsed.path[0] != '/' && o && o.repo && o.dir) {
href = url.resolve('https://github.com/' + o.repo + '/blob/', [o.dir, href].join("/"));
parsed = url.parse(href);
}
// Generate HTML for link
var out = '<a href="' + parsed.href + '"';
// Title if no null
if (title) {
out += ' title="' + title + '"';
}
// Target blank if external
if(parsed.protocol) {
out += ' target="_blank"';
}
out += '>' + text + '</a>';
return out;
};
GitBookRenderer.prototype.image = function(href, title, text) {
// Our "fixed" href
var _href = href;
// Parsed version of the url
var parsed = url.parse(href);
// Options
var o = this._extra_options;
// Relative image, rewrite it depending output
if(!parsed.protocol && parsed.path && parsed.path[0] != '/' && o && o.dir && o.outdir) {
var outdir = o.outdir.charAt(o.outdir.length - 1) === '/' ? o.outdir : o.outdir + '/';
_href = url.resolve(outdir, [o.dir, href].join('/'));
}
return GitBookRenderer.super_.prototype.image.call(this, _href, title, text);
};
GitBookRenderer.prototype.tablerow = function(content) {
this.quizRowId += 1;
return GitBookRenderer.super_.prototype.tablerow(content);
};
var fieldRegex = /^([(\[])([ x])[\])]/;
GitBookRenderer.prototype._createCheckboxAndRadios = function(text) {
var match = fieldRegex.exec(text);
if (!match) {
return text;
}
var field = "<input name='quiz-row-" + this.id + "-" + this.quizRowId + "' type='";
field += match[1] === '(' ? "radio" : "checkbox";
field += match[2] === 'x' ? "' checked/>" : "'/>";
return text.replace(fieldRegex, field);
}
GitBookRenderer.prototype.tablecell = function(content, flags) {
return GitBookRenderer.super_.prototype.tablecell(this._createCheckboxAndRadios(content), flags);
};
GitBookRenderer.prototype.listitem = function(text) {
return GitBookRenderer.super_.prototype.listitem(this._createCheckboxAndRadios(text));
};
// Exports
module.exports = GitBookRenderer;
-117
View File
@@ -1,117 +0,0 @@
var _ = require('lodash');
var marked = require('marked');
// Utility function for splitting a list into groups
function splitBy(list, starter, ender) {
var starts = 0;
var ends = 0;
var group = [];
// Groups
return _.reduce(list, function(groups, value) {
// Ignore start and end delimiters in resulted groups
if(starter(value)) {
starts++;
} else if(ender(value)) {
ends++;
}
// Add current value to group
group.push(value);
// We've got a matching
if(starts === ends && starts !== 0) {
// Add group to end groups
// (remove starter and ender token)
groups.push(group.slice(1, -1));
// Reset group
group = [];
}
return groups;
}, []);
}
function listSplit(nodes, start_type, end_type) {
return splitBy(nodes, function(el) {
return el.type === start_type;
}, function(el) {
return el.type === end_type;
});
}
// Get the biggest list
// out of a list of marked nodes
function filterList(nodes) {
return _.chain(nodes)
.toArray()
.rest(function(el) {
// Get everything after list_start
return el.type !== 'list_start';
})
.reverse()
.rest(function(el) {
// Get everything after list_end (remember we're reversed)
return el.type !== 'list_end';
})
.reverse()
.value().slice(1, -1);
}
// Parses an Article or Chapter title
// supports extracting links
function parseTitle(src, nums) {
// Check if it's a link
var matches = marked.InlineLexer.rules.link.exec(src);
var level = nums.join('.');
// Not a link, return plain text
if(!matches) {
return {
title: src,
level: level,
path: null,
};
}
return {
title: matches[1],
level: level,
// Replace .md references with .html
path: matches[2],
};
}
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 parseSummary(src) {
var nodes = marked.lexer(src);
// Get out list of chapters
var chapterList = filterList(nodes);
// Split out chapter sections
var chapters = _.chain(listSplit(chapterList, 'list_item_start', 'list_item_end'))
.map(parseChapter)
.value();
return {
chapters: chapters
};
}
// Exports
module.exports = parseSummary;
+180
View File
@@ -0,0 +1,180 @@
var _ = require("lodash");
var Q = require("q");
var semver = require("semver");
var path = require("path");
var url = require("url");
var fs = require("./utils/fs");
var resolve = require('resolve');
var pkg = require("../package.json");
var Plugin = function(book, name) {
this.book = book;
this.name = name;
this.packageInfos = {};
this.infos = {};
// Bind methods
_.bindAll(this);
_.each([
"gitbook-plugin-"+name,
"gitbook-"+name,
name,
], function(_name) {
if (this.load(_name, __dirname)) return false;
if (this.load(_name, book.root)) return false;
}, this);
};
// Type of plugins resources
Plugin.RESOURCES = ["js", "css"];
Plugin.HOOKS = [
"init", "finish"
]
// Load from a name
Plugin.prototype.load = function(name, baseDir) {
try {
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) {
this.packageInfos = {};
this.infos = {};
return false;
}
};
Plugin.prototype.normalizeResource = function(resource) {
// Parse the resource path
var parsed = url.parse(resource);
// This is a remote resource
// so we will simply link to using it's URL
if (parsed.protocol) {
return {
"url": resource
};
}
// This will be copied over from disk
// and shipped with the book's build
return { "path": this.name+"/"+resource };
};
// Return resources
Plugin.prototype._getResources = function(base) {
base = base;
var book = this.infos[base];
// Compatibility with version 1.x.x
if (base == "website") book = book || this.infos["book"];
// Nothing specified, fallback to default
if (!book) {
return Q({});
}
// Dynamic function
if(typeof book === "function") {
// Call giving it the context of our book
return Q().then(book.bind(this.book));
}
// Plain data object
return Q(_.cloneDeep(book));
};
// Normalize resources and return them
Plugin.prototype.getResources = function(base) {
var that = this;
return this._getResources(base)
.then(function(resources) {
_.each(Plugin.RESOURCES, function(resourceType) {
resources[resourceType] = (resources[resourceType] || []).map(that.normalizeResource);
});
return resources;
});
};
// Normalize filters and return them
Plugin.prototype.getFilters = function() {
return this.infos.filters || {};
};
// Normalize blocks and return them
Plugin.prototype.getBlocks = function() {
return this.infos.blocks || {};
};
// Test if it's a valid plugin
Plugin.prototype.isValid = function() {
var that = this;
var isValid = (
this.packageInfos &&
this.packageInfos.name &&
this.packageInfos.engines &&
this.packageInfos.engines.gitbook &&
semver.satisfies(pkg.version, this.packageInfos.engines.gitbook)
);
// Valid hooks
_.each(this.infos.hooks, function(hook, hookName) {
if (_.contains(Plugin.HOOKS, hookName)) return;
that.book.log.warn.ln("Hook '"+hookName+" 'used by plugin '"+that.packageInfos.name+"' has been removed or is deprecated");
});
return isValid;
};
// Resolve file path
Plugin.prototype.resolveFile = function(filename) {
return path.resolve(this.baseDir, filename);
};
// Resolve file path
Plugin.prototype.callHook = function(name, data) {
// Our book will be the context to apply
var context = this.book;
var hookFunc = this.infos.hooks? this.infos.hooks[name] : null;
data = data || {};
if (!hookFunc) return Q(data);
this.book.log.debug.ln("call hook", name);
if (!_.contains(Plugin.HOOKS, name)) this.book.log.warn.ln("hook '"+name+"' used by plugin '"+this.name+"' is deprecated, and will be remove in the coming versions");
return Q()
.then(function() {
return hookFunc.apply(context, [data]);
});
};
// Copy plugin assets fodler
Plugin.prototype.copyAssets = function(out, base) {
var that = this;
return this.getResources(base)
.get('assets')
.then(function(assets) {
// Assets are undefined
if(!assets) return false;
return fs.copy(
that.resolveFile(assets),
out
).then(_.constant(true));
}, _.constant(false));
};
module.exports = Plugin;
+225
View File
@@ -0,0 +1,225 @@
var _ = require("lodash");
var Q = require("q");
var npmi = require('npmi');
var npm = require('npm');
var semver = require('semver');
var Plugin = require("./plugin");
var pkg = require("../package.json");
var initNPM = _.memoize(function() {
return Q.nfcall(npm.load, { silent: true, loglevel: 'silent' });
});
var PluginsList = function(book, plugins) {
this.book = book;
this.log = this.book.log;
// List of Plugin objects
this.list = [];
// List of names of failed plugins
this.failed = [];
// Namespaces
this.namespaces = _.chain(["website", "ebook"])
.map(function(namespace) {
return [
namespace,
{
html: {},
resources: _.chain(Plugin.RESOURCES)
.map(function(type) {
return [type, []];
})
.object()
.value()
}
];
})
.object()
.value();
// Bind methods
_.bindAll(this);
if (plugins) this.load(plugins);
};
// return count of plugins
PluginsList.prototype.count = function() {
return this.list.length;
};
// Add and load a plugin
PluginsList.prototype.load = function(plugin, options) {
var that = this;
if (_.isArray(plugin)) {
return _.reduce(plugin, function(prev, p) {
return prev.then(function() {
return that.load(p);
});
}, Q());
}
if (_.isObject(plugin) && !(plugin instanceof Plugin)) plugin = plugin.name;
if (_.isString(plugin)) plugin = new Plugin(this.book, plugin);
that.log.info("load plugin", plugin.name, "....");
if (!plugin.isValid()) {
that.log.info.fail();
that.failed.push(plugin.name);
return Q();
} else {
that.log.info.ok();
// Push in the list
that.list.push(plugin);
}
// Extract filters
_.each(plugin.getFilters(), function(filterFunc, filterName) {
that.book.template.addFilter(filterName, filterFunc);
});
// Extract blocks
_.each(plugin.getBlocks(), function(block, blockName) {
that.book.template.addBlock(blockName, block);
});
return _.reduce(_.keys(that.namespaces), function(prev, namespaceName) {
return prev.then(function() {
return plugin.getResources(namespaceName)
.then(function(plResources) {
var namespace = that.namespaces[namespaceName];
// Extract js and css
_.each(Plugin.RESOURCES, function(resourceType) {
namespace.resources[resourceType] = (namespace.resources[resourceType] || []).concat(plResources[resourceType] || []);
});
// Map of html resources by name added by each plugin
_.each(plResources.html || {}, function(value, tag) {
// Turn into function if not one already
if (!_.isFunction(value)) value = _.constant(value);
namespace.html[tag] = namespace.html[tag] || [];
namespace.html[tag].push(value);
});
})
});
}, Q());
};
// Call a hook
PluginsList.prototype.hook = function(name, data) {
return _.reduce(this.list, function(prev, plugin) {
return prev.then(function(ret) {
return plugin.callHook(name, ret);
});
}, Q(data));
};
// Return a template from a plugin
PluginsList.prototype.template = function(name) {
var withTpl = _.find(this.list, function(plugin) {
return (
plugin.infos.templates &&
plugin.infos.templates[name]
);
});
if (!withTpl) return null;
return withTpl.resolveFile(withTpl.infos.templates[name]);
};
// Return an html snippet
PluginsList.prototype.html = function(namespace, tag, context, options) {
var htmlSnippets = this.namespaces[namespace].html[tag];
return _.map(htmlSnippets || [], function(code) {
return code.call(context, options);
}).join("\n");
};
// Return a resources map for a namespace
PluginsList.prototype.resources = function(namespace) {
return this.namespaces[namespace].resources;
};
// Install plugins from a book
PluginsList.prototype.install = function() {
var that = this;
var defaultsPlugins = _.pluck(that.book.options.defaultsPlugins)
// Remove defaults (no need to install)
var plugins = _.filter(that.book.options.plugins, function(plugin) {
return !_.contains(defaultsPlugins, plugin.name);
});
// Install plugins one by one
that.book.log.info.ln(plugins.length+" plugins to install");
return _.reduce(plugins, function(prev, plugin) {
return prev.then(function() {
var fullname = "gitbook-plugin-"+plugin.name;
return Q()
// Resolve version if needed
.then(function() {
if (plugin.version) return plugin.version;
that.book.log.info.ln("No version specified, resolve plugin", plugin.name);
return initNPM()
.then(function() {
return Q.nfcall(npm.commands.view, [fullname+"@*", "engines"], true);
})
.then(function(versions) {
return _.chain(versions)
.pairs()
.map(function(v) {
return {
version: v[0],
gitbook: (v[1].engines || {})["gitbook"]
}
})
.filter(function(v) {
return v.gitbook && semver.satisfies(pkg.version, v.gitbook);
})
.sort(function(v1, v2) {
return semver.lt(v1.version, v2.version)? 1 : -1;
})
.pluck("version")
.first()
.value();
});
})
// Install the plugin with the resolved version
.then(function(version) {
if (!version) {
throw "Found no satisfactory version for plugin "+plugin.name;
}
that.book.log.info.ln("install plugin", plugin.name, "from npm ("+fullname+") with version", version);
return Q.nfcall(npmi, {
'name': fullname,
'version': version,
'path': that.book.root,
'npmLoad': {
'loglevel': 'silent',
'loaded': true,
'prefix': that.book.root
}
});
})
.then(function() {
that.book.log.info.ok("plugin", plugin.name, "installed with success");
});
});
}, Q());
};
module.exports = PluginsList;
+370
View File
@@ -0,0 +1,370 @@
var _ = require("lodash");
var Q = require("q");
var path = require("path");
var nunjucks = require("nunjucks");
var git = require("./utils/git");
var stringUtils = require("./utils/string");
var fs = require("./utils/fs");
var pkg = require("../package.json");
// The loader should handle relative and git url
var BookLoader = nunjucks.Loader.extend({
async: true,
init: function(book) {
this.book = book;
},
getSource: function(fileurl, callback) {
var that = this;
git.resolveFile(fileurl)
.then(function(filepath) {
// Is local file
if (!filepath) filepath = path.resolve(that.book.root, fileurl);
else that.book.log.debug.ln("resolve from git", fileurl, "to", filepath)
// Read file from absolute path
return fs.readFile(filepath)
.then(function(source) {
return {
src: source.toString(),
path: filepath
}
});
})
.nodeify(callback);
},
resolve: function(from, to) {
return path.resolve(path.dirname(from), to);
}
});
var TemplateEngine = function(book) {
this.book = book;
this.log = this.book.log;
// Nunjucks env
this.env = new nunjucks.Environment(
new BookLoader(book),
{
// Escaping is done after by the markdown parser
autoescape: false,
// Tags
tags: {
blockStart: '{%',
blockEnd: '%}',
variableStart: '{{',
variableEnd: '}}',
commentStart: '{###',
commentEnd: '###}'
}
}
);
// List of tags shortcuts
this.shortcuts = [];
// Map of blocks
this.blocks = {};
// Bind methods
_.bindAll(this);
// Default block "html" that return html not parsed
this.addBlock("html", {
process: _.identity
});
};
// Process a block in a context
TemplateEngine.prototype.processBlock = function(blk) {
if (_.isString(blk)) blk = { body: blk };
blk = _.defaults(blk, {
parse: false
});
blk.id = _.uniqueId("blk");
//Parsable block, just return it
if (blk.parse) {
return blk.body;
}
// Add to global map
this.blocks[blk.id] = blk;
// Return it as a macro
return "%+%"+blk.id+"%+%";
};
// Replace blocks by body after processing
// This is done to avoid that markdown processer parse the block content
TemplateEngine.prototype.replaceBlocks = function(content) {
var that = this;
return content.replace(/\%\+\%([\s\S]+?)\%\+\%/g, function(match, key) {
var blk = that.blocks[key];
if (!blk) return match;
var body = blk.body;
// Remove it from map
delete that.blocks[key];
return body;
});
};
// Bind a function to a context
TemplateEngine.prototype.bindContext = function(func) {
var that = this;
return function() {
var ctx = {
ctx: this.ctx,
book: that.book,
generator: that.book.options.generator
};
return func.apply(ctx, arguments);
};
};
// Add filter
TemplateEngine.prototype.addFilter = function(filterName, func) {
try {
this.env.getFilter(filterName);
this.log.warn.ln("conflict in filters, '"+filterName+"' is already set");
return false;
} catch(e) {}
this.log.debug.ln("add filter '"+filterName+"'");
this.env.addFilter(filterName, this.bindContext(function() {
var ctx = this;
var args = Array.prototype.slice.apply(arguments);
var callback = _.last(args);
Q()
.then(function() {
return func.apply(ctx, args.slice(0, -1));
})
.nodeify(callback);
}), true);
return true;
};
// Add a block
TemplateEngine.prototype.addBlock = function(name, block) {
var that = this;
block = _.defaults(block || {}, {
shortcuts: [],
end: "end"+name,
process: _.identity,
blocks: []
});
var extName = 'Block'+name+'Extension';
if (this.env.getExtension(extName)) {
this.log.warn.ln("conflict in blocks, '"+name+"' is already defined");
return false;
}
this.log.debug.ln("add block '"+name+"'");
var Ext = function () {
this.tags = [name];
this.parse = function(parser, nodes, lexer) {
var body = null;
var lastBlockName = null;
var lastBlockArgs = null;
var allBlocks = block.blocks.concat([block.end]);
var subbodies = {};
var tok = parser.nextToken();
var args = parser.parseSignature(null, true);
parser.advanceAfterBlockEnd(tok.value);
while (1) {
// Read body
var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
// Handle body with previous block name and args
if (lastBlockName) {
subbodies[lastBlockName] = subbodies[lastBlockName] || [];
subbodies[lastBlockName].push({
body: currentBody,
args: lastBlockArgs
});
} else {
body = currentBody;
}
// Read new block
lastBlockName = parser.peekToken().value;
if (lastBlockName == block.end) {
break;
}
// Parse signature and move to the end of the block
lastBlockArgs = parser.parseSignature(null, true);
parser.advanceAfterBlockEnd(lastBlockName);
}
parser.advanceAfterBlockEnd();
var bodies = [body];
_.each(block.blocks, function(blockName) {
subbodies[blockName] = subbodies[blockName] || [];
if (subbodies[blockName].length == 0) {
subbodies[blockName].push({
args: new nodes.NodeList(),
body: new nodes.NodeList()
});
}
bodies.push(subbodies[blockName][0].body);
});
return new nodes.CallExtensionAsync(this, 'run', args, bodies);
};
this.run = function(context) {
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
// Extract blocks body
var _blocks = _.chain(block.blocks)
.reverse()
.map(function(blockName){
return {
name: blockName,
body: args.pop()()
};
})
.reverse()
.value();
var body = args.pop();
var kwargs = args.pop() || {};
var func = that.bindContext(block.process);
Q()
.then(function() {
return func.call(context, {
body: body(),
args: args,
kwargs: kwargs,
blocks: _blocks
});
})
// process the block returned
.then(that.processBlock)
.nodeify(callback)
};
};
// Add the Extension
this.env.addExtension(extName, new Ext());
// Add shortcuts
if (!_.isArray(block.shortcuts)) block.shortcuts = [block.shortcuts];
_.each(block.shortcuts, function(shortcut) {
this.log.debug.ln("add template shortcut from '"+shortcut.start+"' to block '"+name+"' for parsers ", shortcut.parsers);
this.shortcuts.push({
parsers: shortcut.parsers,
start: shortcut.start,
end: shortcut.end,
tag: {
start: name,
end: block.end
}
});
}, this);
};
// Apply a shortcut to a string
TemplateEngine.prototype._applyShortcut = function(parser, content, shortcut) {
if (!_.contains(shortcut.parsers, parser)) return content;
var regex = new RegExp(
stringUtils.escapeRegex(shortcut.start) + "([\\s\\S]*?[^\\$])" + stringUtils.escapeRegex(shortcut.end),
'g'
);
return content.replace(regex, function(all, match) {
return "{% "+shortcut.tag.start+" %}"+ match + "{% "+shortcut.tag.end+" %}";
});
};
// Render a string from the book
TemplateEngine.prototype.renderString = function(content, context, options) {
var context = _.extend({}, context, {
// Variables from book.json
book: this.book.options.variables,
// infos about gitbook
gitbook: {
version: pkg.version
}
});
options = _.defaults(options || {}, {
path: null,
type: null
});
if (options.path) options.path = this.book.resolve(options.path);
// Replace shortcuts
content = _.reduce(this.shortcuts, _.partial(this._applyShortcut.bind(this), options.type), content);
return Q.nfcall(this.env.renderString.bind(this.env), content, context, options);
};
// Render a file from the book
TemplateEngine.prototype.renderFile = function(filename, options) {
var that = this, context;
return that.book.readFile(filename)
.then(function(content) {
return that.renderString(content, {}, {
path: filename
});
});
};
// Render a page from the book
TemplateEngine.prototype.renderPage = function(page) {
var that = this, context;
return that.book.statFile(page.path)
.then(function(stat) {
context = {
// infos about the file
file: {
path: page.path,
mtime: stat.mtime
}
};
return that.renderString(page.content, context, {
path: page.path,
type: page.type
});
});
};
// Post process content
TemplateEngine.prototype.postProcess = function(content) {
return Q(content)
.then(this.replaceBlocks);
};
module.exports = TemplateEngine;
+178
View File
@@ -0,0 +1,178 @@
var _ = require("lodash");
var Q = require("q");
var tmp = require("tmp");
var path = require("path");
var fs = require("graceful-fs");
var fsExtra = require("fs-extra");
var Ignore = require("fstream-ignore");
var fsUtils = {
tmp: {
file: function(opt) {
return Q.nfcall(tmp.file.bind(tmp), opt).get(0)
},
dir: function() {
return Q.nfcall(tmp.dir.bind(tmp)).get(0)
}
},
list: listFiles,
stat: Q.denodeify(fs.stat),
readdir: Q.denodeify(fs.readdir),
readFile: Q.denodeify(fs.readFile),
writeFile: writeFile,
writeStream: writeStream,
mkdirp: Q.denodeify(fsExtra.mkdirp),
copy: Q.denodeify(fsExtra.copy),
remove: Q.denodeify(fsExtra.remove),
symlink: Q.denodeify(fsExtra.symlink),
exists: function(path) {
var d = Q.defer();
fs.exists(path, d.resolve);
return d.promise;
},
existsSync: fs.existsSync.bind(fs),
readFileSync: fs.readFileSync.bind(fs),
clean: cleanFolder,
getUniqueFilename: getUniqueFilename,
}
// Write a file
function writeFile(filename, data, options) {
var d = Q.defer();
try {
fs.writeFileSync(filename, data, options)
} catch(err) {
d.reject(err);
}
d.resolve();
return d.promise;
}
// Write a stream to a file
function writeStream(filename, st) {
var d = Q.defer();
var wstream = fs.createWriteStream(filename);
wstream.on('finish', function () {
d.resolve();
});
wstream.on('error', function (err) {
d.reject(err);
});
st.pipe(wstream);
return d.promise;
}
// Find a filename available
function getUniqueFilename(base, filename) {
if (!filename) {
filename = base;
base = "/";
}
filename = path.resolve(base, filename);
var ext = path.extname(filename);
filename = path.join(path.dirname(filename), path.basename(filename, ext));
var _filename = filename+ext;
var i = 0;
while (1) {
if (!fs.existsSync(filename)) break;
_filename = filename+"_"+i+ext;
i = i + 1;
}
return path.relative(base, _filename);
}
// List files in a directory
function listFiles(root, options) {
options = _.defaults(options || {}, {
ignoreFiles: [],
ignoreRules: []
});
var d = Q.defer();
// Our list of files
var files = [];
var ig = Ignore({
path: root,
ignoreFiles: options.ignoreFiles
});
// Add extra rules to ignore common folders
ig.addIgnoreRules(options.ignoreRules, '__custom_stuff');
// Push each file to our list
ig.on('child', function (c) {
files.push(
c.path.substr(c.root.path.length + 1) + (c.props.Directory === true ? '/' : '')
);
});
ig.on('end', function() {
// Normalize paths on Windows
if(process.platform === 'win32') {
return d.resolve(files.map(function(file) {
return file.replace(/\\/g, '/');
}));
}
// Simply return paths otherwise
return d.resolve(files);
});
ig.on('error', d.reject);
return d.promise;
}
// Clean a folder without removing .git and .svn
// Creates it if non existant
function cleanFolder(root) {
if (!fs.existsSync(root)) return fsUtils.mkdirp(root);
return listFiles(root, {
ignoreFiles: [],
ignoreRules: [
// Skip Git and SVN stuff
'.git/',
'.svn/'
]
})
.then(function(files) {
var d = Q.defer();
_.reduce(files, function(prev, file, i) {
return prev.then(function() {
var _file = path.join(root, file);
d.notify({
i: i+1,
count: files.length,
file: _file
});
return fsUtils.remove(_file);
});
}, Q())
.then(function() {
d.resolve();
}, function(err) {
d.reject(err);
});
return d.promise;
});
}
module.exports = fsUtils;
+112
View File
@@ -0,0 +1,112 @@
var Q = require("q");
var _ = require("lodash");
var url = require("url");
var tmp = require("tmp");
var path = require("path");
var crc = require("crc");
var exec = Q.denodeify(require("child_process").exec);
var URI = require("URIjs");
var fs = require("./fs");
var GIT_PREFIX = "git+";
var GIT_TMP = null;
// Check if an url is a git dependency url
function checkGitUrl(giturl) {
return (giturl.indexOf(GIT_PREFIX) === 0);
}
// Validates a SHA in hexadecimal
function validateSha(str) {
return (/[0-9a-f]{40}/).test(str);
}
// Parse and extract infos
function parseGitUrl(giturl) {
var ref, uri, fileParts, filepath;
if (!checkGitUrl(giturl)) return null;
giturl = giturl.slice(GIT_PREFIX.length);
uri = new URI(giturl);
ref = uri.fragment() || "master";
uri.fragment(null);
// Extract file inside the repo (after the .git)
fileParts =uri.path().split(".git");
filepath = fileParts.length > 1? fileParts.slice(1).join(".git") : "";
if (filepath[0] == "/") filepath = filepath.slice(1);
// Recreate pathname without the real filename
uri.path(_.first(fileParts)+".git");
return {
host: uri.toString(),
ref: ref || "master",
filepath: filepath
};
}
// Clone a git repo from a specific ref
function cloneGitRepo(host, ref) {
var isBranch = false;
ref = ref || "master";
if (!validateSha(ref)) isBranch = true;
return Q()
// Create temporary folder to store git repos
.then(function() {
if (GIT_TMP) return;
return fs.tmp.dir()
.then(function(_tmp) {
GIT_TMP = _tmp;
});
})
// Return or clone the git repo
.then(function() {
// Unique ID for repo/ref combinaison
var repoId = crc.crc32(host+"#"+ref).toString(16);
// Absolute path to the folder
var repoPath = path.resolve(GIT_TMP, repoId);
return fs.exists(repoPath)
.then(function(doExists) {
if (doExists) return;
// Clone repo
return exec("git clone "+host+" "+repoPath)
.then(function() {
return exec("git checkout "+ref, { cwd: repoPath });
})
})
.thenResolve(repoPath);
});
}
// Get file from a git repo
function resolveFileFromGit(giturl) {
if (_.isString(giturl)) giturl = parseGitUrl(giturl);
if (!giturl) return Q(null);
// Clone or get from cache
return cloneGitRepo(giturl.host, giturl.ref)
.then(function(repo) {
// Resolve relative path
return path.resolve(repo, giturl.filepath);
});
};
module.exports = {
checkUrl: checkGitUrl,
parseUrl: parseGitUrl,
resolveFile: resolveFileFromGit
};
+34
View File
@@ -0,0 +1,34 @@
var _ = require("lodash");
var Q = require("q");
var fs = require("./fs");
var exec = require('child_process').exec;
var links = require("./links");
// Convert a svg file
var convertSVG = function(source, dest, options) {
if (!fs.existsSync(source)) return Q.reject(new Error("File doesn't exist: "+source));
var d = Q.defer();
options = _.defaults(options || {}, {
});
var command = "svgexport "+source+" "+dest;
var child = exec(command, function (error, stdout, stderr) {
if (error) {
if (error.code == 127) error = new Error("Need to install 'svgexport' using 'npm install svgexport -g'");
return d.reject(error);
}
d.resolve();
});
return d.promise;
};
module.exports = {
convertSVG: convertSVG,
INVALID: [".svg"]
};
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
lang: require('./lang'),
};
-18
View File
@@ -1,18 +0,0 @@
var MAP = {
'py': 'python',
'js': 'javascript',
'rb': 'ruby',
};
function normalize(lang) {
if(!lang) { return null; }
var lower = lang.toLowerCase();
return MAP[lower] || lower;
}
// Exports
module.exports = {
normalize: normalize,
MAP: MAP
};
+69
View File
@@ -0,0 +1,69 @@
var url = require('url');
var path = require('path');
// Is the link an external link
var isExternal = function(href) {
try {
return Boolean(url.parse(href).protocol);
} catch(err) { }
return false;
};
// Return true if the link is relative
var isRelative = function(href) {
try {
var parsed = url.parse(href);
return !!(!parsed.protocol && parsed.path);// && parsed.path[0] != '/';
} catch(err) {}
return true;
};
// Relative to absolute path
// dir: directory parent of the file currently in rendering process
// outdir: directory parent from the html output
var toAbsolute = function(_href, dir, outdir) {
if (isExternal(_href)) return _href;
// Path '_href' inside the base folder
var hrefInRoot = path.normalize(path.join(dir, _href));
if (_href[0] == "/") hrefInRoot = path.normalize(_href.slice(1));
// Make it relative to output
_href = path.relative(outdir, hrefInRoot);
// Normalize windows paths
_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;
};
// Change extension
var changeExtension = function(filename, newext) {
return path.join(
path.dirname(filename),
path.basename(filename, path.extname(filename))+newext
);
};
module.exports = {
isRelative: isRelative,
isExternal: isExternal,
toAbsolute: toAbsolute,
join: join,
changeExtension: changeExtension
};
+102
View File
@@ -0,0 +1,102 @@
var _ = require('lodash');
var util = require('util');
var color = require('bash-color');
var LEVELS = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
DISABLED: 10
};
var COLORS = {
DEBUG: color.purple,
INFO: color.cyan,
WARN: color.yellow,
ERROR: color.red
};
module.exports = function(_write, logLevel) {
var logger = {};
var lastChar = '\n';
if (_.isString(logLevel)) logLevel = LEVELS[logLevel.toUpperCase()];
// Write a simple message
logger.write = function(msg) {
msg = msg.toString();
lastChar = _.last(msg);
return _write(msg);
};
// Format a message
logger.format = function() {
return util.format.apply(util, arguments);
};
// Write a line
logger.writeLn = function(msg) {
return this.write((msg || "")+"\n");
};
// Write a message with a certain level
logger.log = function(level) {
if (level < logLevel) return;
var levelKey = _.findKey(LEVELS, function(v) { return v == level; });
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
if (lastChar == '\n') {
msg = COLORS[levelKey](levelKey.toLowerCase()+":")+" "+msg;
}
return logger.write(msg);
};
logger.logLn = function() {
if (lastChar != '\n') logger.write("\n");
var args = Array.prototype.slice.apply(arguments);
args.push("\n");
logger.log.apply(logger, args);
};
// Write a OK
logger.ok = function(level) {
var args = Array.prototype.slice.apply(arguments, [1]);
var msg = logger.format.apply(logger, args);
if (arguments.length > 1) {
logger.logLn(level, color.green('>> ') + msg.trim().replace(/\n/g, color.green('\n>> ')));
} else {
logger.log(level, color.green("OK"), "\n");
}
};
// Write an "FAIL"
logger.fail = function(level) {
return logger.log(level, color.red("ERROR")+"\n");
};
_.each(_.omit(LEVELS, 'DISABLED'), function(level, levelKey) {
levelKey = levelKey.toLowerCase();
logger[levelKey] = _.partial(logger.log, level);
logger[levelKey].ln = _.partial(logger.logLn, level);
logger[levelKey].ok = _.partial(logger.ok, level);
logger[levelKey].fail = _.partial(logger.fail, level);
logger[levelKey].promise = function(p) {
return p.
then(function(st) {
logger[levelKey].ok();
return st;
}, function(err) {
logger[levelKey].fail();
throw err;
});
}
});
return logger;
};
module.exports.LEVELS = LEVELS;
module.exports.COLORS = COLORS;
+80
View File
@@ -0,0 +1,80 @@
var _ = require('lodash');
// Cleans up an article/chapter object
// remove 'articles' attributes
function clean(obj) {
return obj && _.omit(obj, ['articles']);
}
function flattenChapters(chapters) {
return _.reduce(chapters, function(accu, chapter) {
return accu.concat([clean(chapter)].concat(flattenChapters(chapter.articles)));
}, []);
}
// Returns from a summary a map of
/*
{
"file/path.md": {
prev: ...,
next: ...,
},
...
}
*/
function navigation(summary, files) {
// Support single files as well as list
files = _.isArray(files) ? files : (_.isString(files) ? [files] : null);
// List of all navNodes
// Flatten chapters, then add in default README node if needed etc ...
var navNodes = flattenChapters(summary.chapters);
// Mapping of prev/next for a give path
var mapping = _.chain(navNodes)
.map(function(current, i) {
var prev = null, next = null;
// Skip if no path
if(!current.path) return null;
// Find prev
prev = _.chain(navNodes)
.slice(0, i)
.reverse()
.find(function(node) {
return node.exists && !node.external;
})
.value();
// Find next
next = _.chain(navNodes)
.slice(i+1)
.find(function(node) {
return node.exists && !node.external;
})
.value();
return [current.path, {
title: current.title,
prev: prev,
next: next,
level: current.level,
}];
})
.compact()
.object()
.value();
// Filter for only files we want
if(files) {
return _.pick(mapping, files);
}
return mapping;
}
// Exports
module.exports = navigation;
+318
View File
@@ -0,0 +1,318 @@
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var cheerio = require('cheerio');
var domSerializer = require('dom-serializer');
var request = require('request');
var links = require('./links');
var imgUtils = require('./images');
var fs = require('./fs');
// Render a cheerio dom as html
var renderDom = function($, dom, options) {
if (!dom && $._root && $._root.children) {
dom = $._root.children;
}
options = options|| dom.options || $._options;
return domSerializer(dom, options);
};
// Map of images that have been converted
var imgConversionCache = {};
function replaceText($, el, search, replace, text_only ) {
return $(el).each(function(){
var node = this.firstChild,
val,
new_val,
// Elements to be removed at the end.
remove = [];
// Only continue if firstChild exists.
if ( node ) {
// Loop over all childNodes.
do {
// Only process text nodes.
if ( node.nodeType === 3 ) {
// The original node value.
val = node.nodeValue;
// The new value.
new_val = val.replace( search, replace );
// Only replace text if the new value is actually different!
if ( new_val !== val ) {
if ( !text_only && /</.test( new_val ) ) {
// The new value contains HTML, set it in a slower but far more
// robust way.
$(node).before( new_val );
// Don't remove the node yet, or the loop will lose its place.
remove.push( node );
} else {
// The new value contains no HTML, so it can be set in this
// very fast, simple way.
node.nodeValue = new_val;
}
}
}
} while ( node = node.nextSibling );
}
// Time to remove those elements!
remove.length && $(remove).remove();
});
};
function pregQuote( str ) {
return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
};
// Adapt an html snippet to be relative to a base folder
function normalizeHtml(src, options) {
var $ = cheerio.load(src, { xmlMode: false});
var toConvert = [];
var svgContent = {};
var outputRoot = options.book.options.output;
imgConversionCache[outputRoot] = imgConversionCache[outputRoot] || {};
// Find svg images to extract and process
if (options.convertImages) {
$("svg").each(function() {
var content = renderDom($, $(this));
var svgId = _.uniqueId("svg");
var dest = svgId+".svg";
// Generate filename
dest = "/"+fs.getUniqueFilename(outputRoot, dest);
svgContent[dest] = content;
$(this).replaceWith($("<img>").attr("src", dest));
});
}
// Find images to normalize
$("img").each(function() {
var origin = undefined;
var src = $(this).attr("src");
var isExternal = links.isExternal(src);
// Transform as relative to the bases
if (links.isRelative(src)) {
src = links.toAbsolute(src, options.base, options.output);
}
// Convert if needed
if (options.convertImages) {
// If image is external and ebook, then downlaod the images
if (isExternal) {
origin = src;
src = "/"+fs.getUniqueFilename(outputRoot, path.basename(src));
src = links.toAbsolute(src, options.base, options.output);
isExternal = false;
}
var ext = path.extname(src);
var srcAbs = path.join("/", options.base, src);
// Test image extension
if (_.contains(imgUtils.INVALID, ext)) {
if (imgConversionCache[outputRoot][srcAbs]) {
// Already converted
src = imgConversionCache[outputRoot][srcAbs];
} else {
// Not converted yet
var dest = "";
// Replace extension
dest = path.join(path.dirname(srcAbs), path.basename(srcAbs, ext)+".png");
dest = dest[0] == "/"? dest.slice(1) : dest;
// Get a name that doesn't exists
dest = fs.getUniqueFilename(outputRoot, dest);
options.book.log.debug.ln("detect invalid image (will be converted to png):", srcAbs);
// Add to cache
imgConversionCache[outputRoot][srcAbs] = "/"+dest;
// Push to convert
toConvert.push({
origin: origin,
content: svgContent[srcAbs],
source: isExternal? srcAbs : path.join("./", srcAbs),
dest: path.join("./", dest)
});
src = path.join("/", dest);
}
// Reset as relative to output
src = links.toAbsolute(src, options.base, options.output);
}
else if (origin) {
// Need to downlaod image
toConvert.push({
origin: origin,
source: path.join("./", srcAbs)
});
}
}
$(this).attr("src", src);
});
$("a").each(function() {
var href = $(this).attr("href");
if (links.isRelative(href)) {
var absolutePath = path.join(options.base, href);
// If is in navigation relative: change extsnio nto be .html
if (options.navigation[absolutePath]) href = links.changeExtension(href, ".html");
// Transform as absolute
href = links.toAbsolute(href, options.base, options.output);
} else {
$(this).attr("target", "_blank");
}
// Transform extension
$(this).attr("href", href);
});
// Replace glossayr terms
_.each(options.glossary, function(term) {
var r = new RegExp( "\\b(" + pregQuote(term.name.toLowerCase()) + ")\\b" , 'gi' );
var includedInFiles = false;
$("*").each(function() {
replaceText($, this, r, function(match) {
if (!includedInFiles) {
includedInFiles = true;
term.files = term.files || [];
term.files.push(options.navigation[options.input]);
}
return "<a href='"+links.toAbsolute("GLOSSARY.html", options.base, options.output)+"#"+term.id+"' class='glossary-term' title='"+term.description+"'>"+match+"</span>";
});
});
});
return {
html: renderDom($),
images: toConvert
};
};
// Convert svg images to png
function convertImages(images, options) {
if (!options.convertImages) return Q();
options.book.log.info.ln("convert ", images.length, "images to png");
return _.reduce(images, function(prev, image) {
var imgin = path.resolve(options.book.options.output, image.source);
return prev
// Write image if need to be download
.then(function() {
if (!image.origin) return;
options.book.log.info("download image", image.origin);
return options.book.log.info.promise(fs.writeStream(imgin, request(image.origin)));
})
// Write svg if content
.then(function() {
if (!image.content) return;
return fs.writeFile(imgin, image.content);
})
// Convert
.then(function(){
if (!image.dest) return;
var imgout = path.resolve(options.book.options.output, image.dest);
options.book.log.debug("convert image", image.source, "to", image.dest, "...");
return options.book.log.debug.promise(imgUtils.convertSVG(imgin, imgout));
});
}, Q())
.then(function() {
options.book.log.info.ok(images.length+" images converted with success");
});
};
// Adapt page content to be relative to a base folder
function normalizePage(sections, options) {
options = _.defaults(options || {}, {
// Current book
book: null,
// Do we need to convert svg?
convertImages: false,
// Current file path
input: ".",
// Navigation to use to transform path
navigation: {},
// Directory parent of the file currently in rendering process
base: "./",
// Directory parent from the html output
output: "./",
// Glossary terms
glossary: []
});
// List of images to convert
var toConvert = [];
sections = _.map(sections, function(section) {
if (section.type != "normal") return section;
var out = normalizeHtml(section.content, options);;
toConvert = toConvert.concat(out.images);
section.content = out.html;
return section;
});
return Q()
.then(function() {
toConvert = _.uniq(toConvert, 'source');
return convertImages(toConvert, options);
})
.thenResolve(sections);
};
// Extract text from sections
function extractText(sections) {
return _.reduce(sections, function(prev, section) {
if (section.type != "normal") return prev;
var $ = cheerio.load(section.content);
$("*").each(function() {
prev = prev+" "+$(this).text();
});
return prev;
}, "");
};
module.exports = {
normalize: normalizePage,
extractText: extractText
};
@@ -11,9 +11,6 @@ var calculProgress = function(navigation, current) {
nav.path = path;
return nav;
})
.sortBy(function(nav) {
return nav.level;
})
.map(function(nav, i) {
// Calcul percent
nav.percent = (i * 100) / Math.max((n - 1), 1);
@@ -27,7 +24,7 @@ var calculProgress = function(navigation, current) {
} else if (done) {
prevPercent = nav.percent;
}
return nav;
})
.value();
@@ -47,4 +44,4 @@ var calculProgress = function(navigation, current) {
};
}
module.exports = calculProgress;
module.exports = calculProgress;
+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;
+32
View File
@@ -0,0 +1,32 @@
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(" ");
}
function escapeRegex(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
module.exports = {
escapeRegex: escapeRegex,
escapeShellArg: escapeShellArg,
optionsToShellArgs: optionsToShellArgs,
toLowerCase: String.prototype.toLowerCase.call.bind(String.prototype.toLowerCase)
};
+38
View File
@@ -0,0 +1,38 @@
var Q = require('q');
var _ = require('lodash');
var path = require('path');
var Gaze = require('gaze').Gaze;
var parsers = require('gitbook-parsers')
function watch(dir) {
var d = Q.defer();
dir = path.resolve(dir);
var toWatch = [
"book.json", "book.js"
];
_.each(parsers.extensions, function(ext) {
toWatch.push("**/*"+ext);
});
var gaze = new Gaze(toWatch, {
cwd: dir
});
gaze.once("all", function(e, filepath) {
gaze.close();
d.resolve(filepath);
});
gaze.once("error", function(err) {
gaze.close();
d.reject(err);
});
return d.promise;
}
module.exports = watch;
+44 -17
View File
@@ -1,47 +1,74 @@
{
"name": "gitbook",
"version": "0.2.1",
"version": "2.0.0-alpha.1",
"homepage": "https://www.gitbook.com",
"description": "Library and cmd utility to generate GitBooks",
"main": "lib/index.js",
"dependencies": {
"q": "1.0.1",
"lunr": "0.5.7",
"lodash": "2.4.1",
"marked": "0.3.2",
"marked-text-renderer": "0.0.1",
"lunr": "0.5.2",
"swig": "1.3.2",
"graceful-fs": "3.0.5",
"resolve": "0.6.3",
"fs-extra": "0.14.0",
"fstream-ignore": "1.0.2",
"gitbook-parsers": "0.3.0",
"nunjucks": "git+https://github.com/SamyPesse/nunjucks.git#4019d1b7379372336b86ce1b0bf84352a2029747",
"nunjucks-autoescape": "0.1.0",
"nunjucks-filter": "0.1.0",
"nunjucks-i18n": "1.0.0",
"semver": "2.2.1",
"npmi": "0.1.1",
"cheerio": "0.18.0",
"gitbook-plugin-livereload": "0.0.1",
"gaze": "~0.5.1",
"send": "0.2.0",
"fstream-ignore": "0.0.7",
"commander": "2.2.0",
"fs-extra": "0.8.1",
"highlight.js": "8.0.0",
"tmp": "0.0.23"
"tiny-lr-fork": "0.0.5",
"tmp": "0.0.24",
"crc": "3.2.1",
"bash-color": "0.0.3",
"URIjs": "1.14.1",
"request": "2.51.0",
"npm": "2.4.1",
"dom-serializer": "git+https://github.com/SamyPesse/dom-serializer.git#58d8ea24b9b7b4137ee5f30a41abce4d7c4a922a"
},
"devDependencies": {
"mocha": "1.18.2",
"grunt": "~0.4.2",
"grunt-cli": "0.1.11",
"grunt-contrib-copy": "0.5.0",
"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"
},
"bin": {
"gitbook": "./bin/gitbook.js"
"test": "export TESTING=true; mocha --reporter list --timeout 15000"
},
"repository": {
"type": "git",
"url": "https://github.com/GitbookIO/gitbook.git"
},
"bin": {
"gitbook": "./bin/gitbook.js"
},
"keywords": [
"git",
"book",
"gitbook"
],
"author": "Aaron O'Mullan <aaron.omullan@friendco.de>",
"author": "FriendCode Inc. <contact@gitbook.com>",
"license": "Apache 2",
"bugs": {
"url": "https://github.com/GitbookIO/gitbook/issues"
}
},
"contributors": [
{
"name": "Aaron O'Mullan",
"email": "aaron@gitbook.com"
},
{
"name": "Samy Pessé",
"email": "samy@gitbook.com"
}
]
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 227 KiB

+14
View File
@@ -0,0 +1,14 @@
var path = require('path');
var assert = require('assert');
var Book = require('../').Book;
describe('Configuration parsing', function () {
it('should correctly load from json', function() {
assert(books[0].options.title == "Test");
});
it('should correctly load from javascript', function() {
assert(books[4].options.title == "Test 2");
});
});
+40
View File
@@ -0,0 +1,40 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var fs = require("fs");
var fsUtil = require("../lib/utils/fs");
describe('eBook Generator', function () {
it('should correctly generate ebook pages', function(done) {
testGeneration(books[1], "ebook", function(output) {
assert(fs.existsSync(path.join(output, "SUMMARY.html")));
}, done);
});
it('should correctly convert svg images to png', function(done) {
testGeneration(books[4], "ebook", function(output) {
var readmeContent = fs.readFileSync(path.join(output, "index.html"), {encoding: "utf8"});
var pageContent = fs.readFileSync(path.join(output, "sub/PAGE.html"), {encoding: "utf8"});
// Remote image
assert(pageContent.indexOf('src="../Tux.png"') >= 0);
assert(fs.existsSync(path.join(output, "Tux.png")));
assert(fs.existsSync(path.join(output, "test.png")));
assert(fs.existsSync(path.join(output, "NewTux.png")));
assert(!fs.existsSync(path.join(output, "test_0.png")));
assert(!fs.existsSync(path.join(output, "sub/test.png")));
assert(!fs.existsSync(path.join(output, "sub/NewTux.png")));
assert(pageContent.indexOf('src="../test.png"') >= 0);
assert(pageContent.indexOf('src="../NewTux.png"') >= 0);
assert(pageContent.indexOf('<svg') < 0);
assert(readmeContent.indexOf('src="test.png"') >= 0);
assert(readmeContent.indexOf('src="NewTux.png"') >= 0);
}, done);
});
});
-3
View File
@@ -1,3 +0,0 @@
# Nice course
Check out this source file [in C++](../src/something.cpp)
-11
View File
@@ -1,11 +0,0 @@
## Wow such book
Some nice content here
---
A beautiful separator, but non an exercise or a quiz !
---
Some more beautiful text, because `this` book is awesome ...
-68
View File
@@ -1,68 +0,0 @@
# Python basics
Python is a nice language, you can add stuff. Bla bla bla.
Lets jump into an exercise :
---
It's dead simple, `c` must be the sum of `a` and `b`
```py
a = 1
b = 2
```
```py
a = 1
b = 2
c = a + b
```
```py
assert(c, 3)
```
---
Some more nice content ....
[Cool stuff](http://gitbook.io)
[Link to another Markdown file](./xyz/file.md)
And look at this pretty picture:
![Pretty](../assets/my-pretty-picture.png "Pretty")
Lets go for another exercise but this time with some context :
---
Exercise with some context code :
Using the `double` function provided, build a `quadruple` function
```py
```
```py
def quadruple(x):
return double(double(x))
```
```py
assert(quadruple(8), 32)
```
```py
def double(x):
return x * 2
```
---
-48
View File
@@ -1,48 +0,0 @@
# Gitbook quiz
Gitbook lets you write a quiz using GFM tables:
---
Here's a quiz about Gitbook
| | Good | Bad |
| ---------------- | ---- | --- |
| What is Gitbook? | (x) | ( ) |
> Gitbook is good
What does Gitbook support?
- [x] Table-based questions with radio buttons
- [x] Table-based questions with checkboxes
- [ ] Telepathy
- [x] List-based questions with checkboxes
- [x] List-based questions with radio buttons
- [ ] Moon-on-a-stick
> Gitbook supports table and list based quiz questions using either radio buttons or checkboxes.
>
> Gitbook is not telepathic and does not give you the moon on a stick.
---
Some more nice content ....
[Cool stuff](http://gitbook.io)
[Link to another Markdown file](./xyz/file.md)
---
Quiz test 2: What does Gitbook support?
- [x] Table-based questions with radio buttons
- [x] Table-based questions with checkboxes
- [ ] Telepathy
- [x] List-based questions with checkboxes
- [x] List-based questions with radio buttons
- [ ] Moon-on-a-stick
> Gitbook supports table and list based quiz questions using either radio buttons or checkboxes.
---
-7
View File
@@ -1,7 +0,0 @@
# This is the title
This is the book description.
other content
...
-68
View File
@@ -1,68 +0,0 @@
# Title
Some text
---
## NOT Exercise
Simple subsection NOT exercise
```
x = 1
```
What is this
```
y = [1, 2, 3]
```
```
z = {a: 1, b: 2}
```
---
## Exercise
Define a variable `x` equal to 10.
```js
var x =
```
```js
var x = 10;
```
```js
assert(x == 10);
```
```js
// This is context code available everywhere
// The user will be able to call magicFunc in his code
function magicFunc() {
return 3;
}
```
---
## Another exercise
Bla bla bla ... This time with no `context` code.
```js
var x =
```
```js
var x = 10;
```
```js
assert(x == 10);
```
-10
View File
@@ -1,10 +0,0 @@
# Summary
* [Chapter 1](chapter-1/README.md)
* [Article 1](chapter-1/ARTICLE1.md)
* [Article 2](chapter-1/ARTICLE2.md)
* [Chapter 2](chapter-2/README.md)
* [Chapter 3](chapter-3/README.md)
* [Chapter 4](chapter-4/README.md)
* Unfinished article
* Unfinished Chapter
+6
View File
@@ -0,0 +1,6 @@
# Glossary
## Description
This is the dexcription of a description.
+3
View File
@@ -0,0 +1,3 @@
# Other Title
A description
+1
View File
@@ -0,0 +1 @@
# Summary
+3
View File
@@ -0,0 +1,3 @@
{
"title": "Test"
}
+3
View File
@@ -0,0 +1,3 @@
body {
background: red;
}
+9
View File
@@ -0,0 +1,9 @@
# Test
a test text
# Test 2
a second test
+4
View File
@@ -0,0 +1,4 @@
# Test
This file is not parsed by gitbook because the structure is defined in book.json.
+6
View File
@@ -0,0 +1,6 @@
# Summary
* [Chapter 1](test.md)
* [Article 1](sub/test1.md)
* [Chapter 2](test2.md)
* [Google](https://www.google.com)
+5
View File
@@ -0,0 +1,5 @@
{
"structure": {
"readme": "intro.md"
}
}
+4
View File
@@ -0,0 +1,4 @@
# My Book
Test description
+6
View File
@@ -0,0 +1,6 @@
# This file is used to etst links transformations:
This is a relative link [test](../intro.md).
![image](../image.png)
+4
View File
@@ -0,0 +1,4 @@
# Languages
* [English](en/)
* [French](fr/)
+2
View File
@@ -0,0 +1,2 @@
# Multi-Languages test
+1
View File
@@ -0,0 +1 @@
# English Book
+1
View File
@@ -0,0 +1 @@
# Summary
+1
View File
@@ -0,0 +1 @@
# French Book
+1
View File
@@ -0,0 +1 @@
# Summary
+4
View File
@@ -0,0 +1,4 @@
= My Book
Test description
+5
View File
@@ -0,0 +1,5 @@
= Summary
. link:test.adoc[Chapter 1]
.. link:test1.adoc[Article 1]
. link:test2.adoc[Chapter 2]
View File
View File
View File
+7
View File
@@ -0,0 +1,7 @@
# Other Title
A description
![test image to be converted](./test.svg)
![test url](http://upload.wikimedia.org/wikipedia/commons/b/b0/NewTux.svg)
![test image to be converted, second use](./test.svg)
+3
View File
@@ -0,0 +1,3 @@
# Summary
* [Page](sub/PAGE.md)
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
title: "Test 2"
};
+18
View File
@@ -0,0 +1,18 @@
##
## Image from root page
![test image to be converted](../test.svg)
![test url](http://upload.wikimedia.org/wikipedia/commons/b/b0/NewTux.svg)
## Inline svg
{% html %}
<svg width="400" height="110">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)">
</svg>
{% endhtml %}
## Remote image
![remote url](http://upload.wikimedia.org/wikipedia/commons/a/af/Tux.png)
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-root" width="100%" height="100%" viewBox="0 0 480 360">
<title id="test-title">basic SVG tiny doc</title>
<g id="test-body-content">
<text font-family="Arial" font-size="14" text-anchor="middle" x="225" y="25">hello world</text>
</g>
<text id="revision" x="10" y="340" font-size="40" stroke="none" fill="black">Revision: 1.1</text>
<rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 709 B

+5
View File
@@ -0,0 +1,5 @@
# Test Content inclusion
{% include "./test.md" %}
Git1:{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}
Git2:{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}
+1
View File
@@ -0,0 +1 @@
# Summary
+1
View File
@@ -0,0 +1 @@
Hello World
+32
View File
@@ -0,0 +1,32 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var fs = require("fs");
var git = require("../lib/utils/git");
describe('GIT parser and getter', function () {
it('should correctly parse an https url', function() {
var parts = git.parseUrl("git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md");
assert(parts);
assert.equal(parts.host, "https://gist.github.com/69ea4542e4c8967d2fa7.git");
assert.equal(parts.ref, "master");
assert.equal(parts.filepath, "test.md");
});
it('should correctly parse an https url with a reference', function() {
var parts = git.parseUrl("git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md#0.1.2");
assert(parts);
assert.equal(parts.host, "https://gist.github.com/69ea4542e4c8967d2fa7.git");
assert.equal(parts.ref, "0.1.2");
assert.equal(parts.filepath, "test.md");
});
it('should correctly parse an ssh url', function() {
var parts = git.parseUrl("git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1");
assert(parts);
assert.equal(parts.host, "git@github.com:GitbookIO/gitbook.git");
assert.equal(parts.ref, "e1594cde2c32e4ff48f6c4eff3d3d461743d74e1");
assert.equal(parts.filepath, "directory/README.md");
});
});
+62
View File
@@ -0,0 +1,62 @@
var path = require('path');
var Q = require('q');
var fs = require('fs');
var _ = require('lodash');
var fsUtil = require("../lib/utils/fs");
var Book = require('../').Book;
var LOG_LEVELS = require('../').LOG_LEVELS;
// Nicety for mocha / Q
global.qdone = function qdone(promise, done) {
return promise.then(function() {
return done();
}, function(err) {
return done(err);
}).done();
};
// Test generation of a book
global.testGeneration = function(book, type, func, done) {
var OUTPUT_PATH = book.options.output;
qdone(
book.generate(type)
.then(function() {
func(OUTPUT_PATH);
})
.fin(function() {
return fsUtil.remove(OUTPUT_PATH);
}),
done);
};
// Books for testings
var books = fs.readdirSync(path.join(__dirname, './fixtures/'));
global.books = _.chain(books)
.sortBy()
.map(function(book) {
if (book.indexOf("test") !== 0) return null;
return new Book(path.join(__dirname, './fixtures/', book), {
logLevel: LOG_LEVELS.DISABLED
});
})
.compact()
.value();
// Init before doing tests
before(function(done) {
qdone(
_.reduce(global.books, function(prev, book) {
return prev.then(function() {
return fsUtil.remove(path.join(book.root, "_book"));
})
.then(function() {
return book.parse();
});
}, Q()),
done
);
});
+59
View File
@@ -0,0 +1,59 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var fs = require("fs");
describe('JSON generator', function () {
it('should correctly generate a book to json with glossary', function(done) {
testGeneration(books[0], "json", function(output) {
assert(fs.existsSync(path.join(output, "README.json")));
var readme = JSON.parse(fs.readFileSync(path.join(output, "README.json")));
assert(readme.sections[0].content.indexOf('class="glossary-term"') > 0);
}, done);
});
it('should correctly generate a book to json with sub folders', function(done) {
testGeneration(books[1], "json", function(output) {
assert(!fs.existsSync(path.join(output, "README.json")));
assert(fs.existsSync(path.join(output, "intro.json")));
assert(fs.existsSync(path.join(output, "sub/test1.json")));
var test1 = JSON.parse(fs.readFileSync(path.join(output, "sub/test1.json")));
assert(test1.sections[0].content.indexOf("intro.html") > 0);
}, done);
});
it('should correctly generate a multilingual book to json', function(done) {
testGeneration(books[2], "json", function(output) {
assert(fs.existsSync(path.join(output, "README.json")));
assert(fs.existsSync(path.join(output, "en/README.json")));
assert(fs.existsSync(path.join(output, "fr/README.json")));
}, done);
});
it('should correctly generate an asciidoc book to json', function(done) {
testGeneration(books[3], "json", function(output) {
assert(fs.existsSync(path.join(output, "README.json")));
assert(fs.existsSync(path.join(output, "test.json")));
assert(fs.existsSync(path.join(output, "test1.json")));
assert(fs.existsSync(path.join(output, "test2.json")));
}, done);
});
it('should correctly generate a book with local inclusion', function(done) {
testGeneration(books[5], "json", function(output) {
var readme = JSON.parse(fs.readFileSync(path.join(output, "README.json")));
assert(readme.sections[0].content.indexOf('Hello World') > 0);
}, done);
});
it('should correctly generate a book with external inclusion', function(done) {
testGeneration(books[5], "json", function(output) {
var readme = JSON.parse(fs.readFileSync(path.join(output, "README.json")));
assert(readme.sections[0].content.indexOf('Git1:Hello from git') > 0);
assert(readme.sections[0].content.indexOf('Git2:First Hello. Hello from git') > 0);
}, done);
});
});
+33
View File
@@ -0,0 +1,33 @@
var path = require('path');
var _ = require('lodash');
var assert = require('assert');
var links = require("../lib/utils/links");
describe('Links', function () {
it('should correctly test external links', function() {
assert(links.isExternal("http://google.fr"));
assert(links.isExternal("https://google.fr"));
assert(!links.isExternal("test.md"));
assert(!links.isExternal("folder/test.md"));
assert(!links.isExternal("/folder/test.md"));
});
describe('toAbsolute', function() {
it('should correctly transform as absolute', function() {
assert.equal(links.toAbsolute("http://google.fr"), "http://google.fr");
assert.equal(links.toAbsolute("test.md", "./", "./"), "test.md");
assert.equal(links.toAbsolute("folder/test.md", "./", "./"), "folder/test.md");
});
it('should correctly handle windows path', function() {
assert.equal(links.toAbsolute("folder\\test.md", "./", "./"), "folder/test.md");
});
it('should correctly handle absolute path', function() {
assert.equal(links.toAbsolute("/test.md", "./", "./"), "test.md");
assert.equal(links.toAbsolute("/test.md", "test", "test"), "../test.md");
assert.equal(links.toAbsolute("/sub/test.md", "test", "test"), "../sub/test.md");
});
});
});
-74
View File
@@ -1,74 +0,0 @@
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var summary = require('../').parse.summary;
var navigation = require('../').parse.navigation;
var CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/SUMMARY.md'), 'utf8');
var LEXED = summary(CONTENT);
describe('Summary navigation', function() {
it('should provide next & prev entries for a file', function() {
var nav = navigation(LEXED, [
'README.md',
'chapter-1/README.md',
'chapter-1/ARTICLE1.md',
'chapter-1/ARTICLE2.md',
'chapter-2/README.md',
]);
// Make sure it found the files we gave it
assert(nav['README.md']);
assert(nav['chapter-1/README.md']);
assert(nav['chapter-1/ARTICLE1.md']);
assert(nav['chapter-1/ARTICLE2.md']);
assert(nav['chapter-2/README.md']);
assert.equal(nav['README.md'].prev, null);
assert.equal(nav['README.md'].next.path, 'chapter-1/README.md');
assert.equal(nav['chapter-1/README.md'].prev.path, 'README.md');
assert.equal(nav['chapter-1/README.md'].next.path, 'chapter-1/ARTICLE1.md');
assert.equal(nav['chapter-1/ARTICLE1.md'].prev.path, 'chapter-1/README.md');
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-2/README.md'].prev.path, 'chapter-1/ARTICLE2.md');
assert.equal(nav['chapter-2/README.md'].next.path, 'chapter-3/README.md');
});
it('should give full tree, when not limited', function() {
var nav = navigation(LEXED);
assert(nav['README.md']);
assert(nav['chapter-1/README.md']);
assert(nav['chapter-1/ARTICLE1.md']);
assert(nav['chapter-1/ARTICLE2.md']);
assert(nav['chapter-2/README.md']);
assert(nav['chapter-3/README.md']);
});
it('should detect levels correctly', function() {
var nav = navigation(LEXED);
assert.equal(nav['README.md'].level, '0');
assert.equal(nav['chapter-1/README.md'].level, '1');
assert.equal(nav['chapter-1/ARTICLE1.md'].level, '1.1');
assert.equal(nav['chapter-1/ARTICLE2.md'].level, '1.2');
assert.equal(nav['chapter-2/README.md'].level, '2');
assert.equal(nav['chapter-3/README.md'].level, '3');
});
it('should not accept null paths', function() {
var nav = navigation(LEXED);
assert(!nav[null]);
});
});
-96
View File
@@ -1,96 +0,0 @@
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var page = require('../').parse.page;
function loadPage (name, options) {
var CONTENT = fs.readFileSync(path.join(__dirname, './fixtures/' + name + '.md'), 'utf8');
return page(CONTENT, options);
}
var LEXED = loadPage('PAGE', {
dir: 'course',
outdir: '_book'
});
var QUIZ_LEXED = loadPage('QUIZ_PAGE');
var HR_LEXED = loadPage('HR_PAGE');
describe('Page parsing', function() {
it('should detect sections', function() {
assert.equal(LEXED.length, 4);
});
it('should detect section types', function() {
assert.equal(LEXED[0].type, 'normal');
assert.equal(LEXED[1].type, 'exercise');
assert.equal(LEXED[2].type, 'normal');
assert.equal(QUIZ_LEXED[0].type, 'normal');
assert.equal(QUIZ_LEXED[1].type, 'quiz');
assert.equal(QUIZ_LEXED[2].type, 'normal');
assert.equal(QUIZ_LEXED[3].type, 'quiz');
});
it('should gen content for normal sections', function() {
assert(LEXED[0].content);
assert(LEXED[2].content);
});
it('should make image URLs relative', function() {
assert(LEXED[2].content.indexOf('_book/assets/my-pretty-picture.png') !== -1);
});
it('should gen code and content for exercise sections', function() {
assert(LEXED[1].content);
assert(LEXED[1].code);
assert(LEXED[1].code.base);
assert(LEXED[1].code.solution);
assert(LEXED[1].code.validation);
assert(LEXED[1].code.context === null);
assert(LEXED[3].content);
assert(LEXED[3].code);
assert(LEXED[3].code.base);
assert(LEXED[3].code.solution);
assert(LEXED[3].code.validation);
assert(LEXED[3].code.context);
});
it('should merge sections correctly', function() {
// One big section
assert.equal(HR_LEXED.length, 1);
// HRs inserted correctly
assert.equal(HR_LEXED[0].content.match(/<hr>/g).length, 2);
});
it('should detect an exercise\'s language', function() {
assert.equal(LEXED[1].lang, 'python');
});
it('should render a quiz', function() {
assert(QUIZ_LEXED[1].content);
assert(QUIZ_LEXED[1].quiz);
assert(QUIZ_LEXED[1].quiz[0].base);
assert(QUIZ_LEXED[1].quiz[0].solution);
assert(QUIZ_LEXED[1].quiz[0].feedback);
assert(QUIZ_LEXED[1].quiz[1].base);
assert(QUIZ_LEXED[1].quiz[1].solution);
assert(QUIZ_LEXED[1].quiz[1].feedback);
});
});
describe('Relative links', function() {
it('should be resolved to their GitHub counterparts', function() {
var LEXED = loadPage('GITHUB_LINKS', {
// GitHub repo ID
repo: 'GitBookIO/javascript',
// Imaginary folder of markdown file
dir: 'course'
});
assert(LEXED[0].content.indexOf('https://github.com/GitBookIO/javascript/blob/src/something.cpp') !== -1);
});
});

Some files were not shown because too many files have changed in this diff Show More