summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.eslintrc15
-rw-r--r--.gitignore10
-rw-r--r--CHANGES.md11
-rw-r--r--CONTRIBUTING.md51
-rw-r--r--book.js5
-rw-r--r--docs/SUMMARY.md7
-rw-r--r--docs/_layouts/website/page.html26
-rw-r--r--docs/api/README.md24
-rw-r--r--docs/api/components.md86
-rw-r--r--docs/api/connect.md33
-rw-r--r--docs/api/i18n.md44
-rw-r--r--docs/api/navigation.md23
-rw-r--r--docs/api/node.md97
-rw-r--r--lerna.json4
-rw-r--r--lib/api/decodeGlobal.js22
-rw-r--r--lib/api/decodePage.js44
-rw-r--r--lib/api/deprecate.js122
-rw-r--r--lib/api/encodeGlobal.js257
-rw-r--r--lib/api/index.js8
-rw-r--r--lib/browser.js26
-rw-r--r--lib/cli/getBook.js23
-rw-r--r--lib/cli/getOutputFolder.js17
-rw-r--r--lib/cli/init.js17
-rw-r--r--lib/cli/server.js128
-rw-r--r--lib/constants/configDefault.js6
-rw-r--r--lib/constants/defaultBlocks.js51
-rw-r--r--lib/index.js10
-rw-r--r--lib/json/encodeBook.js39
-rw-r--r--lib/json/encodeBookWithPage.js22
-rw-r--r--lib/json/encodeFile.js21
-rw-r--r--lib/json/encodeGlossary.js21
-rw-r--r--lib/json/encodeLanguages.js26
-rw-r--r--lib/json/encodeOutput.js25
-rw-r--r--lib/json/encodeOutputWithPage.js23
-rw-r--r--lib/json/encodePage.js39
-rw-r--r--lib/json/encodeReadme.js17
-rw-r--r--lib/json/encodeSummary.js20
-rw-r--r--lib/json/encodeSummaryArticle.js28
-rw-r--r--lib/json/encodeSummaryPart.js17
-rw-r--r--lib/json/index.js13
-rw-r--r--lib/models/__tests__/templateBlock.js205
-rw-r--r--lib/models/book.js364
-rw-r--r--lib/models/ignore.js42
-rw-r--r--lib/models/output.js107
-rw-r--r--lib/models/page.js70
-rw-r--r--lib/models/plugin.js169
-rw-r--r--lib/models/templateBlock.js281
-rw-r--r--lib/models/templateEngine.js139
-rw-r--r--lib/models/templateOutput.js42
-rw-r--r--lib/output/createTemplateEngine.js45
-rw-r--r--lib/output/ebook/getPDFTemplate.js41
-rw-r--r--lib/output/ebook/onFinish.js91
-rw-r--r--lib/output/ebook/options.js17
-rw-r--r--lib/output/generatePage.js79
-rw-r--r--lib/output/getModifiers.js73
-rw-r--r--lib/output/helper/fileToOutput.js32
-rw-r--r--lib/output/helper/fileToURL.js31
-rw-r--r--lib/output/helper/resolveFileToURL.js26
-rw-r--r--lib/output/modifiers/__tests__/fetchRemoteImages.js40
-rw-r--r--lib/output/modifiers/__tests__/highlightCode.js60
-rw-r--r--lib/output/modifiers/__tests__/inlinePng.js25
-rw-r--r--lib/output/modifiers/__tests__/resolveLinks.js104
-rw-r--r--lib/output/modifiers/__tests__/svgToImg.js25
-rw-r--r--lib/output/modifiers/__tests__/svgToPng.js33
-rw-r--r--lib/output/modifiers/fetchRemoteImages.js44
-rw-r--r--lib/output/modifiers/highlightCode.js58
-rw-r--r--lib/output/modifiers/modifyHTML.js25
-rw-r--r--lib/output/modifiers/resolveLinks.js53
-rw-r--r--lib/output/preparePages.js26
-rw-r--r--lib/output/website/__tests__/i18n.js38
-rw-r--r--lib/output/website/copyPluginAssets.js117
-rw-r--r--lib/output/website/createTemplateEngine.js151
-rw-r--r--lib/output/website/index.js11
-rw-r--r--lib/output/website/listSearchPaths.js23
-rw-r--r--lib/output/website/onAsset.js28
-rw-r--r--lib/output/website/onFinish.js35
-rw-r--r--lib/output/website/onInit.js20
-rw-r--r--lib/output/website/onPage.js76
-rw-r--r--lib/output/website/options.js14
-rw-r--r--lib/output/website/prepareI18n.js30
-rw-r--r--lib/output/website/prepareResources.js54
-rw-r--r--lib/parse/listAssets.js43
-rw-r--r--lib/plugins/__tests__/findForBook.js19
-rw-r--r--lib/plugins/__tests__/installPlugin.js29
-rw-r--r--lib/plugins/__tests__/installPlugins.js30
-rw-r--r--lib/plugins/__tests__/listDependencies.js38
-rw-r--r--lib/plugins/findInstalled.js91
-rw-r--r--lib/plugins/index.js10
-rw-r--r--lib/plugins/installPlugin.js47
-rw-r--r--lib/plugins/listBlocks.js18
-rw-r--r--lib/plugins/listFilters.js17
-rw-r--r--lib/plugins/listResources.js45
-rw-r--r--lib/plugins/locateRootFolder.js22
-rw-r--r--lib/plugins/validateConfig.js71
-rw-r--r--lib/templating/__tests__/conrefsLoader.js98
-rw-r--r--lib/templating/__tests__/postRender.js51
-rw-r--r--lib/templating/__tests__/replaceShortcuts.js27
-rw-r--r--lib/templating/index.js10
-rw-r--r--lib/templating/postRender.js53
-rw-r--r--lib/templating/themesLoader.js115
-rw-r--r--lib/utils/git.js133
-rw-r--r--package.json96
-rw-r--r--packages/gitbook-core/.babelrc3
-rw-r--r--packages/gitbook-core/.gitignore1
-rw-r--r--packages/gitbook-core/.npmignore3
-rw-r--r--packages/gitbook-core/package.json51
-rw-r--r--packages/gitbook-core/src/actions/TYPES.js16
-rw-r--r--packages/gitbook-core/src/actions/components.js37
-rw-r--r--packages/gitbook-core/src/actions/history.js188
-rw-r--r--packages/gitbook-core/src/actions/i18n.js33
-rw-r--r--packages/gitbook-core/src/components/Backdrop.js56
-rw-r--r--packages/gitbook-core/src/components/Button.js22
-rw-r--r--packages/gitbook-core/src/components/ButtonGroup.js23
-rw-r--r--packages/gitbook-core/src/components/ContextProvider.js34
-rw-r--r--packages/gitbook-core/src/components/Dropdown.js126
-rw-r--r--packages/gitbook-core/src/components/HTMLContent.js77
-rw-r--r--packages/gitbook-core/src/components/HotKeys.js59
-rw-r--r--packages/gitbook-core/src/components/I18nProvider.js28
-rw-r--r--packages/gitbook-core/src/components/Icon.js28
-rw-r--r--packages/gitbook-core/src/components/Import.js48
-rw-r--r--packages/gitbook-core/src/components/InjectedComponent.js117
-rw-r--r--packages/gitbook-core/src/components/Link.js37
-rw-r--r--packages/gitbook-core/src/components/PJAXWrapper.js102
-rw-r--r--packages/gitbook-core/src/components/Panel.js22
-rw-r--r--packages/gitbook-core/src/components/Tooltipped.js44
-rw-r--r--packages/gitbook-core/src/index.js73
-rw-r--r--packages/gitbook-core/src/lib/bootstrap.js29
-rw-r--r--packages/gitbook-core/src/lib/composeReducer.js16
-rw-r--r--packages/gitbook-core/src/lib/connect.js70
-rw-r--r--packages/gitbook-core/src/lib/createContext.js76
-rw-r--r--packages/gitbook-core/src/lib/createPlugin.js27
-rw-r--r--packages/gitbook-core/src/lib/createReducer.js27
-rw-r--r--packages/gitbook-core/src/lib/getPayload.js19
-rw-r--r--packages/gitbook-core/src/lib/renderWithContext.js55
-rw-r--r--packages/gitbook-core/src/models/Context.js58
-rw-r--r--packages/gitbook-core/src/models/File.js54
-rw-r--r--packages/gitbook-core/src/models/Language.js12
-rw-r--r--packages/gitbook-core/src/models/Languages.js40
-rw-r--r--packages/gitbook-core/src/models/Location.js78
-rw-r--r--packages/gitbook-core/src/models/Page.js24
-rw-r--r--packages/gitbook-core/src/models/Plugin.js21
-rw-r--r--packages/gitbook-core/src/models/Readme.js21
-rw-r--r--packages/gitbook-core/src/models/SummaryArticle.js32
-rw-r--r--packages/gitbook-core/src/models/SummaryPart.js17
-rw-r--r--packages/gitbook-core/src/propTypes/Context.js11
-rw-r--r--packages/gitbook-core/src/propTypes/File.js13
-rw-r--r--packages/gitbook-core/src/propTypes/History.js11
-rw-r--r--packages/gitbook-core/src/propTypes/Language.js7
-rw-r--r--packages/gitbook-core/src/propTypes/Languages.js12
-rw-r--r--packages/gitbook-core/src/propTypes/Location.js12
-rw-r--r--packages/gitbook-core/src/propTypes/Page.js16
-rw-r--r--packages/gitbook-core/src/propTypes/Readme.js11
-rw-r--r--packages/gitbook-core/src/propTypes/Summary.js14
-rw-r--r--packages/gitbook-core/src/propTypes/SummaryArticle.js22
-rw-r--r--packages/gitbook-core/src/propTypes/SummaryPart.js14
-rw-r--r--packages/gitbook-core/src/propTypes/i18n.js10
-rw-r--r--packages/gitbook-core/src/propTypes/index.js19
-rw-r--r--packages/gitbook-core/src/reducers/components.js20
-rw-r--r--packages/gitbook-core/src/reducers/config.js15
-rw-r--r--packages/gitbook-core/src/reducers/file.js16
-rw-r--r--packages/gitbook-core/src/reducers/history.js82
-rw-r--r--packages/gitbook-core/src/reducers/i18n.js27
-rw-r--r--packages/gitbook-core/src/reducers/index.js15
-rw-r--r--packages/gitbook-core/src/reducers/languages.js12
-rw-r--r--packages/gitbook-core/src/reducers/page.js16
-rw-r--r--packages/gitbook-core/src/reducers/readme.js5
-rw-r--r--packages/gitbook-core/src/reducers/summary.js28
-rw-r--r--packages/gitbook-core/src/server.js2
-rw-r--r--packages/gitbook-plugin-copy-code/.gitignore31
-rw-r--r--packages/gitbook-plugin-copy-code/.npmignore2
-rw-r--r--packages/gitbook-plugin-copy-code/_assets/website/button.css27
-rw-r--r--packages/gitbook-plugin-copy-code/index.js10
-rw-r--r--packages/gitbook-plugin-copy-code/package.json29
-rw-r--r--packages/gitbook-plugin-copy-code/src/index.js82
-rw-r--r--packages/gitbook-plugin-headings/.gitignore31
-rw-r--r--packages/gitbook-plugin-headings/.npmignore2
-rw-r--r--packages/gitbook-plugin-headings/_assets/website/headings.css41
-rw-r--r--packages/gitbook-plugin-headings/index.js10
-rw-r--r--packages/gitbook-plugin-headings/package.json38
-rw-r--r--packages/gitbook-plugin-headings/src/index.js60
-rw-r--r--packages/gitbook-plugin-highlight/.gitignore31
-rw-r--r--packages/gitbook-plugin-highlight/.npmignore2
-rw-r--r--packages/gitbook-plugin-highlight/_assets/website/white.css92
-rw-r--r--packages/gitbook-plugin-highlight/index.js10
-rw-r--r--packages/gitbook-plugin-highlight/package.json29
-rw-r--r--packages/gitbook-plugin-highlight/src/ALIASES.js10
-rw-r--r--packages/gitbook-plugin-highlight/src/CodeBlock.js55
-rw-r--r--packages/gitbook-plugin-highlight/src/getLanguage.js34
-rw-r--r--packages/gitbook-plugin-highlight/src/index.js9
-rw-r--r--packages/gitbook-plugin-hints/.gitignore31
-rw-r--r--packages/gitbook-plugin-hints/.npmignore2
-rw-r--r--packages/gitbook-plugin-hints/README.md41
-rw-r--r--packages/gitbook-plugin-hints/_assets/website/plugin.css43
-rw-r--r--packages/gitbook-plugin-hints/index.js12
-rw-r--r--packages/gitbook-plugin-hints/package.json29
-rw-r--r--packages/gitbook-plugin-hints/src/index.js45
-rw-r--r--packages/gitbook-plugin-livereload/.gitignore31
-rw-r--r--packages/gitbook-plugin-livereload/LICENSE201
-rw-r--r--packages/gitbook-plugin-livereload/README.md3
-rw-r--r--packages/gitbook-plugin-livereload/package.json29
-rw-r--r--packages/gitbook-plugin-livereload/src/index.js18
-rw-r--r--packages/gitbook-plugin-lunr/.gitignore31
-rw-r--r--packages/gitbook-plugin-lunr/.npmignore2
-rw-r--r--packages/gitbook-plugin-lunr/index.js99
-rw-r--r--packages/gitbook-plugin-lunr/package.json44
-rw-r--r--packages/gitbook-plugin-lunr/src/actions.js43
-rw-r--r--packages/gitbook-plugin-lunr/src/index.js28
-rw-r--r--packages/gitbook-plugin-lunr/src/reducer.js31
-rw-r--r--packages/gitbook-plugin-search/.gitignore1
-rw-r--r--packages/gitbook-plugin-search/.npmignore1
-rw-r--r--packages/gitbook-plugin-search/README.md41
-rw-r--r--packages/gitbook-plugin-search/index.js4
-rw-r--r--packages/gitbook-plugin-search/package.json31
-rw-r--r--packages/gitbook-plugin-search/src/actions/search.js121
-rw-r--r--packages/gitbook-plugin-search/src/actions/types.js8
-rw-r--r--packages/gitbook-plugin-search/src/components/Input.js73
-rw-r--r--packages/gitbook-plugin-search/src/components/Results.js80
-rw-r--r--packages/gitbook-plugin-search/src/index.js33
-rw-r--r--packages/gitbook-plugin-search/src/models/Result.js20
-rw-r--r--packages/gitbook-plugin-search/src/reducers/index.js3
-rw-r--r--packages/gitbook-plugin-search/src/reducers/search.js56
-rw-r--r--packages/gitbook-plugin-sharing/.gitignore31
-rw-r--r--packages/gitbook-plugin-sharing/.npmignore2
-rw-r--r--packages/gitbook-plugin-sharing/README.md38
-rw-r--r--packages/gitbook-plugin-sharing/index.js10
-rw-r--r--packages/gitbook-plugin-sharing/package.json77
-rw-r--r--packages/gitbook-plugin-sharing/src/SITES.js72
-rw-r--r--packages/gitbook-plugin-sharing/src/components/ShareButton.js47
-rw-r--r--packages/gitbook-plugin-sharing/src/components/SharingButtons.js63
-rw-r--r--packages/gitbook-plugin-sharing/src/components/SiteButton.js29
-rw-r--r--packages/gitbook-plugin-sharing/src/index.js9
-rw-r--r--packages/gitbook-plugin-sharing/src/optionsShape.js20
-rw-r--r--packages/gitbook-plugin-sharing/src/shapes/options.js19
-rw-r--r--packages/gitbook-plugin-sharing/src/shapes/site.js13
-rw-r--r--packages/gitbook-plugin-theme-default/.gitignore1
-rw-r--r--packages/gitbook-plugin-theme-default/.npmignore1
-rw-r--r--packages/gitbook-plugin-theme-default/index.js3
-rw-r--r--packages/gitbook-plugin-theme-default/less/Body.less9
-rw-r--r--packages/gitbook-plugin-theme-default/less/Button.less22
-rw-r--r--packages/gitbook-plugin-theme-default/less/Dropdown.less56
-rw-r--r--packages/gitbook-plugin-theme-default/less/LoadingBar.less30
-rw-r--r--packages/gitbook-plugin-theme-default/less/Page.less16
-rw-r--r--packages/gitbook-plugin-theme-default/less/Panel.less7
-rw-r--r--packages/gitbook-plugin-theme-default/less/Search.less38
-rw-r--r--packages/gitbook-plugin-theme-default/less/Sidebar.less29
-rw-r--r--packages/gitbook-plugin-theme-default/less/Summary.less51
-rw-r--r--packages/gitbook-plugin-theme-default/less/Toolbar.less27
-rw-r--r--packages/gitbook-plugin-theme-default/less/Tooltipped.less100
-rw-r--r--packages/gitbook-plugin-theme-default/less/main.less50
-rw-r--r--packages/gitbook-plugin-theme-default/less/mixins.less15
-rw-r--r--packages/gitbook-plugin-theme-default/less/reset.less396
-rw-r--r--packages/gitbook-plugin-theme-default/less/variables.less55
-rw-r--r--packages/gitbook-plugin-theme-default/package.json80
-rwxr-xr-xpackages/gitbook-plugin-theme-default/prepublish.sh11
-rw-r--r--packages/gitbook-plugin-theme-default/src/actions/sidebar.js13
-rw-r--r--packages/gitbook-plugin-theme-default/src/actions/types.js4
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Body.js121
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/LoadingBar.js124
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Page.js30
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Sidebar.js25
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Summary.js111
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Theme.js57
-rw-r--r--packages/gitbook-plugin-theme-default/src/components/Toolbar.js43
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/ar.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/bn.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/ca.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/cs.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/de.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/el.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/en.json21
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/es.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/fa.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/fi.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/fr.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/he.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/index.js30
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/it.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/ja.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/ko.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/nl.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/no.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/pl.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/pt.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/ro.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/ru.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/sv.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/tr.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/uk.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/vi.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/zh-hans.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/i18n/zh-tw.json20
-rw-r--r--packages/gitbook-plugin-theme-default/src/index.js14
-rw-r--r--packages/gitbook-plugin-theme-default/src/reducers/index.js5
-rw-r--r--packages/gitbook-plugin-theme-default/src/reducers/sidebar.js18
-rw-r--r--packages/gitbook-plugin/CONTRIBUTING.md11
-rw-r--r--packages/gitbook-plugin/README.md1
-rw-r--r--packages/gitbook-plugin/package.json39
-rw-r--r--packages/gitbook-plugin/src/cli.js84
-rw-r--r--packages/gitbook-plugin/src/compile.js41
-rw-r--r--packages/gitbook-plugin/src/create.js61
-rw-r--r--packages/gitbook-plugin/src/index.js (renamed from lib/modifiers/summary/__tests__/editArticle.js)0
-rw-r--r--packages/gitbook-plugin/template/.eslintignore2
-rw-r--r--packages/gitbook-plugin/template/.eslintrc3
-rw-r--r--packages/gitbook-plugin/template/.gitignore31
-rw-r--r--packages/gitbook-plugin/template/.npmignore2
-rw-r--r--packages/gitbook-plugin/template/index.js10
-rw-r--r--packages/gitbook-plugin/template/src/index.js11
-rw-r--r--packages/gitbook/.babelrc3
-rw-r--r--packages/gitbook/.gitignore1
-rw-r--r--packages/gitbook/.npmignore2
-rwxr-xr-xpackages/gitbook/bin/gitbook.js (renamed from bin/gitbook.js)2
-rw-r--r--packages/gitbook/package.json107
-rw-r--r--packages/gitbook/src/__tests__/gitbook.js (renamed from lib/__tests__/gitbook.js)2
-rw-r--r--packages/gitbook/src/__tests__/init.js (renamed from lib/__tests__/init.js)6
-rw-r--r--packages/gitbook/src/__tests__/module.js (renamed from lib/__tests__/module.js)0
-rw-r--r--packages/gitbook/src/api/decodeConfig.js (renamed from lib/api/decodeConfig.js)2
-rw-r--r--packages/gitbook/src/api/decodeGlobal.js22
-rw-r--r--packages/gitbook/src/api/decodePage.js34
-rw-r--r--packages/gitbook/src/api/deprecate.js120
-rw-r--r--packages/gitbook/src/api/encodeConfig.js (renamed from lib/api/encodeConfig.js)22
-rw-r--r--packages/gitbook/src/api/encodeGlobal.js264
-rw-r--r--packages/gitbook/src/api/encodeNavigation.js (renamed from lib/api/encodeNavigation.js)46
-rw-r--r--packages/gitbook/src/api/encodePage.js (renamed from lib/api/encodePage.js)34
-rw-r--r--packages/gitbook/src/api/encodeProgress.js (renamed from lib/api/encodeProgress.js)22
-rw-r--r--packages/gitbook/src/api/encodeSummary.js (renamed from lib/api/encodeSummary.js)21
-rw-r--r--packages/gitbook/src/api/index.js7
-rw-r--r--packages/gitbook/src/browser.js23
-rw-r--r--packages/gitbook/src/browser/__tests__/render.js4
-rw-r--r--packages/gitbook/src/browser/loadPlugins.js31
-rw-r--r--packages/gitbook/src/browser/render.js103
-rw-r--r--packages/gitbook/src/cli/build.js (renamed from lib/cli/build.js)20
-rw-r--r--packages/gitbook/src/cli/buildEbook.js (renamed from lib/cli/buildEbook.js)40
-rw-r--r--packages/gitbook/src/cli/getBook.js23
-rw-r--r--packages/gitbook/src/cli/getOutputFolder.js17
-rw-r--r--packages/gitbook/src/cli/index.js (renamed from lib/cli/index.js)2
-rw-r--r--packages/gitbook/src/cli/init.js17
-rw-r--r--packages/gitbook/src/cli/install.js (renamed from lib/cli/install.js)12
-rw-r--r--packages/gitbook/src/cli/options.js (renamed from lib/cli/options.js)8
-rw-r--r--packages/gitbook/src/cli/parse.js (renamed from lib/cli/parse.js)40
-rw-r--r--packages/gitbook/src/cli/serve.js (renamed from lib/cli/serve.js)54
-rw-r--r--packages/gitbook/src/cli/server.js127
-rw-r--r--packages/gitbook/src/cli/watch.js (renamed from lib/cli/watch.js)16
-rw-r--r--packages/gitbook/src/constants/__tests__/configSchema.js (renamed from lib/constants/__tests__/configSchema.js)12
-rw-r--r--packages/gitbook/src/constants/configDefault.js6
-rw-r--r--packages/gitbook/src/constants/configFiles.js (renamed from lib/constants/configFiles.js)0
-rw-r--r--packages/gitbook/src/constants/configSchema.js (renamed from lib/constants/configSchema.js)2
-rw-r--r--packages/gitbook/src/constants/defaultBlocks.js5
-rw-r--r--packages/gitbook/src/constants/defaultFilters.js (renamed from lib/constants/defaultFilters.js)8
-rw-r--r--packages/gitbook/src/constants/defaultPlugins.js (renamed from lib/constants/defaultPlugins.js)14
-rw-r--r--packages/gitbook/src/constants/extsAsciidoc.js (renamed from lib/constants/extsAsciidoc.js)0
-rw-r--r--packages/gitbook/src/constants/extsMarkdown.js (renamed from lib/constants/extsMarkdown.js)0
-rw-r--r--packages/gitbook/src/constants/ignoreFiles.js (renamed from lib/constants/ignoreFiles.js)0
-rw-r--r--packages/gitbook/src/constants/pluginAssetsFolder.js (renamed from lib/constants/pluginAssetsFolder.js)0
-rw-r--r--packages/gitbook/src/constants/pluginHooks.js (renamed from lib/constants/pluginHooks.js)0
-rw-r--r--packages/gitbook/src/constants/pluginPrefix.js (renamed from lib/constants/pluginPrefix.js)0
-rw-r--r--packages/gitbook/src/constants/pluginResources.js (renamed from lib/constants/pluginResources.js)2
-rw-r--r--packages/gitbook/src/constants/templatesFolder.js (renamed from lib/constants/templatesFolder.js)0
-rw-r--r--packages/gitbook/src/constants/themePrefix.js (renamed from lib/constants/themePrefix.js)2
-rw-r--r--packages/gitbook/src/fs/__tests__/mock.js (renamed from lib/fs/__tests__/mock.js)5
-rw-r--r--packages/gitbook/src/fs/mock.js (renamed from lib/fs/mock.js)44
-rw-r--r--packages/gitbook/src/fs/node.js (renamed from lib/fs/node.js)18
-rw-r--r--packages/gitbook/src/gitbook.js (renamed from lib/gitbook.js)14
-rw-r--r--packages/gitbook/src/index.js9
-rw-r--r--packages/gitbook/src/init.js (renamed from lib/init.js)42
-rw-r--r--packages/gitbook/src/json/encodeFile.js23
-rw-r--r--packages/gitbook/src/json/encodeGlossary.js22
-rw-r--r--packages/gitbook/src/json/encodeGlossaryEntry.js (renamed from lib/json/encodeGlossaryEntry.js)10
-rw-r--r--packages/gitbook/src/json/encodeLanguages.js29
-rw-r--r--packages/gitbook/src/json/encodePage.js41
-rw-r--r--packages/gitbook/src/json/encodeReadme.js18
-rw-r--r--packages/gitbook/src/json/encodeState.js42
-rw-r--r--packages/gitbook/src/json/encodeSummary.js23
-rw-r--r--packages/gitbook/src/json/encodeSummaryArticle.js30
-rw-r--r--packages/gitbook/src/json/encodeSummaryPart.js19
-rw-r--r--packages/gitbook/src/json/index.js10
-rw-r--r--packages/gitbook/src/models/__tests__/config.js (renamed from lib/models/__tests__/config.js)33
-rw-r--r--packages/gitbook/src/models/__tests__/glossary.js (renamed from lib/models/__tests__/glossary.js)15
-rw-r--r--packages/gitbook/src/models/__tests__/glossaryEntry.js (renamed from lib/models/__tests__/glossaryEntry.js)5
-rw-r--r--packages/gitbook/src/models/__tests__/page.js (renamed from lib/models/__tests__/page.js)10
-rw-r--r--packages/gitbook/src/models/__tests__/plugin.js (renamed from lib/models/__tests__/plugin.js)9
-rw-r--r--packages/gitbook/src/models/__tests__/pluginDependency.js (renamed from lib/models/__tests__/pluginDependency.js)22
-rw-r--r--packages/gitbook/src/models/__tests__/summary.js (renamed from lib/models/__tests__/summary.js)21
-rw-r--r--packages/gitbook/src/models/__tests__/summaryArticle.js (renamed from lib/models/__tests__/summaryArticle.js)21
-rw-r--r--packages/gitbook/src/models/__tests__/summaryPart.js (renamed from lib/models/__tests__/summaryPart.js)7
-rw-r--r--packages/gitbook/src/models/__tests__/templateBlock.js218
-rw-r--r--packages/gitbook/src/models/__tests__/templateEngine.js (renamed from lib/models/__tests__/templateEngine.js)28
-rw-r--r--packages/gitbook/src/models/__tests__/uriIndex.js84
-rw-r--r--packages/gitbook/src/models/book.js357
-rw-r--r--packages/gitbook/src/models/config.js (renamed from lib/models/config.js)28
-rw-r--r--packages/gitbook/src/models/file.js (renamed from lib/models/file.js)10
-rw-r--r--packages/gitbook/src/models/fs.js (renamed from lib/models/fs.js)207
-rw-r--r--packages/gitbook/src/models/glossary.js (renamed from lib/models/glossary.js)34
-rw-r--r--packages/gitbook/src/models/glossaryEntry.js (renamed from lib/models/glossaryEntry.js)6
-rw-r--r--packages/gitbook/src/models/ignore.js43
-rw-r--r--packages/gitbook/src/models/language.js (renamed from lib/models/language.js)6
-rw-r--r--packages/gitbook/src/models/languages.js (renamed from lib/models/languages.js)14
-rw-r--r--packages/gitbook/src/models/output.js112
-rw-r--r--packages/gitbook/src/models/page.js69
-rw-r--r--packages/gitbook/src/models/parser.js (renamed from lib/models/parser.js)32
-rw-r--r--packages/gitbook/src/models/plugin.js149
-rw-r--r--packages/gitbook/src/models/pluginDependency.js (renamed from lib/models/pluginDependency.js)30
-rw-r--r--packages/gitbook/src/models/readme.js (renamed from lib/models/readme.js)8
-rw-r--r--packages/gitbook/src/models/summary.js (renamed from lib/models/summary.js)52
-rw-r--r--packages/gitbook/src/models/summaryArticle.js (renamed from lib/models/summaryArticle.js)36
-rw-r--r--packages/gitbook/src/models/summaryPart.js (renamed from lib/models/summaryPart.js)14
-rw-r--r--packages/gitbook/src/models/templateBlock.js253
-rw-r--r--packages/gitbook/src/models/templateEngine.js133
-rw-r--r--packages/gitbook/src/models/templateShortcut.js (renamed from lib/models/templateShortcut.js)8
-rw-r--r--packages/gitbook/src/models/uriIndex.js159
-rw-r--r--packages/gitbook/src/modifiers/config/__tests__/addPlugin.js (renamed from lib/modifiers/config/__tests__/addPlugin.js)15
-rw-r--r--packages/gitbook/src/modifiers/config/__tests__/removePlugin.js (renamed from lib/modifiers/config/__tests__/removePlugin.js)19
-rw-r--r--packages/gitbook/src/modifiers/config/__tests__/togglePlugin.js (renamed from lib/modifiers/config/__tests__/togglePlugin.js)15
-rw-r--r--packages/gitbook/src/modifiers/config/addPlugin.js (renamed from lib/modifiers/config/addPlugin.js)10
-rw-r--r--packages/gitbook/src/modifiers/config/editPlugin.js (renamed from lib/modifiers/config/editPlugin.js)2
-rw-r--r--packages/gitbook/src/modifiers/config/getPluginConfig.js (renamed from lib/modifiers/config/getPluginConfig.js)4
-rw-r--r--packages/gitbook/src/modifiers/config/hasPlugin.js (renamed from lib/modifiers/config/hasPlugin.js)0
-rw-r--r--packages/gitbook/src/modifiers/config/index.js (renamed from lib/modifiers/config/index.js)0
-rw-r--r--packages/gitbook/src/modifiers/config/isDefaultPlugin.js (renamed from lib/modifiers/config/isDefaultPlugin.js)4
-rw-r--r--packages/gitbook/src/modifiers/config/removePlugin.js (renamed from lib/modifiers/config/removePlugin.js)6
-rw-r--r--packages/gitbook/src/modifiers/config/togglePlugin.js (renamed from lib/modifiers/config/togglePlugin.js)8
-rw-r--r--packages/gitbook/src/modifiers/index.js (renamed from lib/modifiers/index.js)0
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/editArticle.js0
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/editPartTitle.js (renamed from lib/modifiers/summary/__tests__/editPartTitle.js)19
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/insertArticle.js (renamed from lib/modifiers/summary/__tests__/insertArticle.js)26
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/insertPart.js (renamed from lib/modifiers/summary/__tests__/insertPart.js)24
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/mergeAtLevel.js (renamed from lib/modifiers/summary/__tests__/mergeAtLevel.js)22
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/moveArticle.js (renamed from lib/modifiers/summary/__tests__/moveArticle.js)24
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/moveArticleAfter.js (renamed from lib/modifiers/summary/__tests__/moveArticleAfter.js)26
-rw-r--r--packages/gitbook/src/modifiers/summary/__tests__/removeArticle.js (renamed from lib/modifiers/summary/__tests__/removeArticle.js)14
-rw-r--r--packages/gitbook/src/modifiers/summary/editArticleRef.js (renamed from lib/modifiers/summary/editArticleRef.js)2
-rw-r--r--packages/gitbook/src/modifiers/summary/editArticleTitle.js (renamed from lib/modifiers/summary/editArticleTitle.js)2
-rw-r--r--packages/gitbook/src/modifiers/summary/editPartTitle.js (renamed from lib/modifiers/summary/editPartTitle.js)4
-rw-r--r--packages/gitbook/src/modifiers/summary/index.js (renamed from lib/modifiers/summary/index.js)0
-rw-r--r--packages/gitbook/src/modifiers/summary/indexArticleLevels.js (renamed from lib/modifiers/summary/indexArticleLevels.js)4
-rw-r--r--packages/gitbook/src/modifiers/summary/indexLevels.js (renamed from lib/modifiers/summary/indexLevels.js)4
-rw-r--r--packages/gitbook/src/modifiers/summary/indexPartLevels.js (renamed from lib/modifiers/summary/indexPartLevels.js)8
-rw-r--r--packages/gitbook/src/modifiers/summary/insertArticle.js (renamed from lib/modifiers/summary/insertArticle.js)18
-rw-r--r--packages/gitbook/src/modifiers/summary/insertPart.js (renamed from lib/modifiers/summary/insertPart.js)6
-rw-r--r--packages/gitbook/src/modifiers/summary/mergeAtLevel.js (renamed from lib/modifiers/summary/mergeAtLevel.js)16
-rw-r--r--packages/gitbook/src/modifiers/summary/moveArticle.js (renamed from lib/modifiers/summary/moveArticle.js)14
-rw-r--r--packages/gitbook/src/modifiers/summary/moveArticleAfter.js (renamed from lib/modifiers/summary/moveArticleAfter.js)20
-rw-r--r--packages/gitbook/src/modifiers/summary/removeArticle.js (renamed from lib/modifiers/summary/removeArticle.js)14
-rw-r--r--packages/gitbook/src/modifiers/summary/removePart.js (renamed from lib/modifiers/summary/removePart.js)4
-rw-r--r--packages/gitbook/src/modifiers/summary/unshiftArticle.js (renamed from lib/modifiers/summary/unshiftArticle.js)12
-rw-r--r--packages/gitbook/src/output/__tests__/createMock.js (renamed from lib/output/__tests__/createMock.js)22
-rw-r--r--packages/gitbook/src/output/__tests__/ebook.js (renamed from lib/output/__tests__/ebook.js)5
-rw-r--r--packages/gitbook/src/output/__tests__/generateMock.js (renamed from lib/output/__tests__/generateMock.js)20
-rw-r--r--packages/gitbook/src/output/__tests__/json.js (renamed from lib/output/__tests__/json.js)4
-rw-r--r--packages/gitbook/src/output/__tests__/website.js (renamed from lib/output/__tests__/website.js)74
-rw-r--r--packages/gitbook/src/output/callHook.js (renamed from lib/output/callHook.js)30
-rw-r--r--packages/gitbook/src/output/callPageHook.js (renamed from lib/output/callPageHook.js)18
-rw-r--r--packages/gitbook/src/output/createTemplateEngine.js48
-rw-r--r--packages/gitbook/src/output/ebook/getConvertOptions.js (renamed from lib/output/ebook/getConvertOptions.js)24
-rw-r--r--packages/gitbook/src/output/ebook/getCoverPath.js (renamed from lib/output/ebook/getCoverPath.js)14
-rw-r--r--packages/gitbook/src/output/ebook/getPDFTemplate.js36
-rw-r--r--packages/gitbook/src/output/ebook/index.js (renamed from lib/output/ebook/index.js)4
-rw-r--r--packages/gitbook/src/output/ebook/onFinish.js85
-rw-r--r--packages/gitbook/src/output/ebook/onPage.js (renamed from lib/output/ebook/onPage.js)17
-rw-r--r--packages/gitbook/src/output/ebook/options.js14
-rw-r--r--packages/gitbook/src/output/generateAssets.js (renamed from lib/output/generateAssets.js)18
-rw-r--r--packages/gitbook/src/output/generateBook.js (renamed from lib/output/generateBook.js)86
-rw-r--r--packages/gitbook/src/output/generatePage.js68
-rw-r--r--packages/gitbook/src/output/generatePages.js (renamed from lib/output/generatePages.js)10
-rw-r--r--packages/gitbook/src/output/getModifiers.js42
-rw-r--r--packages/gitbook/src/output/helper/index.js (renamed from lib/output/helper/index.js)0
-rw-r--r--packages/gitbook/src/output/helper/writeFile.js (renamed from lib/output/helper/writeFile.js)6
-rw-r--r--packages/gitbook/src/output/index.js (renamed from lib/output/index.js)6
-rw-r--r--packages/gitbook/src/output/json/index.js (renamed from lib/output/json/index.js)0
-rw-r--r--packages/gitbook/src/output/json/onFinish.js (renamed from lib/output/json/onFinish.js)31
-rw-r--r--packages/gitbook/src/output/json/onPage.js (renamed from lib/output/json/onPage.js)20
-rw-r--r--packages/gitbook/src/output/json/options.js (renamed from lib/output/json/options.js)4
-rw-r--r--packages/gitbook/src/output/modifiers/__tests__/addHeadingId.js (renamed from lib/output/modifiers/__tests__/addHeadingId.js)13
-rw-r--r--packages/gitbook/src/output/modifiers/__tests__/annotateText.js (renamed from lib/output/modifiers/__tests__/annotateText.js)23
-rw-r--r--packages/gitbook/src/output/modifiers/__tests__/fetchRemoteImages.js39
-rw-r--r--packages/gitbook/src/output/modifiers/__tests__/inlinePng.js24
-rw-r--r--packages/gitbook/src/output/modifiers/__tests__/resolveLinks.js34
-rw-r--r--packages/gitbook/src/output/modifiers/__tests__/svgToImg.js24
-rw-r--r--packages/gitbook/src/output/modifiers/__tests__/svgToPng.js32
-rw-r--r--packages/gitbook/src/output/modifiers/addHeadingId.js (renamed from lib/output/modifiers/addHeadingId.js)18
-rw-r--r--packages/gitbook/src/output/modifiers/annotateText.js (renamed from lib/output/modifiers/annotateText.js)45
-rw-r--r--packages/gitbook/src/output/modifiers/editHTMLElement.js (renamed from lib/output/modifiers/editHTMLElement.js)6
-rw-r--r--packages/gitbook/src/output/modifiers/fetchRemoteImages.js44
-rw-r--r--packages/gitbook/src/output/modifiers/index.js (renamed from lib/output/modifiers/index.js)3
-rw-r--r--packages/gitbook/src/output/modifiers/inlineAssets.js (renamed from lib/output/modifiers/inlineAssets.js)20
-rw-r--r--packages/gitbook/src/output/modifiers/inlinePng.js (renamed from lib/output/modifiers/inlinePng.js)35
-rw-r--r--packages/gitbook/src/output/modifiers/modifyHTML.js25
-rw-r--r--packages/gitbook/src/output/modifiers/resolveImages.js (renamed from lib/output/modifiers/resolveImages.js)22
-rw-r--r--packages/gitbook/src/output/modifiers/resolveLinks.js30
-rw-r--r--packages/gitbook/src/output/modifiers/svgToImg.js (renamed from lib/output/modifiers/svgToImg.js)26
-rw-r--r--packages/gitbook/src/output/modifiers/svgToPng.js (renamed from lib/output/modifiers/svgToPng.js)24
-rw-r--r--packages/gitbook/src/output/prepareAssets.js (renamed from lib/output/prepareAssets.js)18
-rw-r--r--packages/gitbook/src/output/preparePages.js35
-rw-r--r--packages/gitbook/src/output/preparePlugins.js (renamed from lib/output/preparePlugins.js)8
-rw-r--r--packages/gitbook/src/output/website/copyPluginAssets.js111
-rw-r--r--packages/gitbook/src/output/website/index.js10
-rw-r--r--packages/gitbook/src/output/website/onAsset.js29
-rw-r--r--packages/gitbook/src/output/website/onFinish.js30
-rw-r--r--packages/gitbook/src/output/website/onInit.js15
-rw-r--r--packages/gitbook/src/output/website/onPage.js34
-rw-r--r--packages/gitbook/src/output/website/options.js10
-rw-r--r--packages/gitbook/src/output/website/state.js (renamed from lib/output/website/state.js)11
-rw-r--r--packages/gitbook/src/parse/__tests__/listAssets.js (renamed from lib/parse/__tests__/listAssets.js)14
-rw-r--r--packages/gitbook/src/parse/__tests__/parseBook.js (renamed from lib/parse/__tests__/parseBook.js)38
-rw-r--r--packages/gitbook/src/parse/__tests__/parseGlossary.js (renamed from lib/parse/__tests__/parseGlossary.js)24
-rw-r--r--packages/gitbook/src/parse/__tests__/parseIgnore.js (renamed from lib/parse/__tests__/parseIgnore.js)10
-rw-r--r--packages/gitbook/src/parse/__tests__/parsePageFromString.js (renamed from lib/parse/__tests__/parsePageFromString.js)20
-rw-r--r--packages/gitbook/src/parse/__tests__/parseReadme.js (renamed from lib/parse/__tests__/parseReadme.js)20
-rw-r--r--packages/gitbook/src/parse/__tests__/parseSummary.js (renamed from lib/parse/__tests__/parseSummary.js)22
-rw-r--r--packages/gitbook/src/parse/__tests__/parseURIIndexFromPages.js26
-rw-r--r--packages/gitbook/src/parse/findParsableFile.js (renamed from lib/parse/findParsableFile.js)18
-rw-r--r--packages/gitbook/src/parse/index.js (renamed from lib/parse/index.js)0
-rw-r--r--packages/gitbook/src/parse/listAssets.js43
-rw-r--r--packages/gitbook/src/parse/lookupStructureFile.js (renamed from lib/parse/lookupStructureFile.js)6
-rw-r--r--packages/gitbook/src/parse/parseBook.js (renamed from lib/parse/parseBook.js)58
-rw-r--r--packages/gitbook/src/parse/parseConfig.js (renamed from lib/parse/parseConfig.js)16
-rw-r--r--packages/gitbook/src/parse/parseGlossary.js (renamed from lib/parse/parseGlossary.js)8
-rw-r--r--packages/gitbook/src/parse/parseIgnore.js (renamed from lib/parse/parseIgnore.js)37
-rw-r--r--packages/gitbook/src/parse/parseLanguages.js (renamed from lib/parse/parseLanguages.js)8
-rw-r--r--packages/gitbook/src/parse/parsePage.js (renamed from lib/parse/parsePage.js)6
-rw-r--r--packages/gitbook/src/parse/parsePageFromString.js (renamed from lib/parse/parsePageFromString.js)9
-rw-r--r--packages/gitbook/src/parse/parsePagesList.js (renamed from lib/parse/parsePagesList.js)48
-rw-r--r--packages/gitbook/src/parse/parseReadme.js (renamed from lib/parse/parseReadme.js)10
-rw-r--r--packages/gitbook/src/parse/parseStructureFile.js (renamed from lib/parse/parseStructureFile.js)12
-rw-r--r--packages/gitbook/src/parse/parseSummary.js (renamed from lib/parse/parseSummary.js)16
-rw-r--r--packages/gitbook/src/parse/parseURIIndexFromPages.js44
-rw-r--r--packages/gitbook/src/parse/validateConfig.js (renamed from lib/parse/validateConfig.js)16
-rw-r--r--packages/gitbook/src/parse/walkSummary.js (renamed from lib/parse/walkSummary.js)4
-rw-r--r--packages/gitbook/src/parsers.js (renamed from lib/parsers.js)20
-rw-r--r--packages/gitbook/src/plugins/__tests__/findForBook.js19
-rw-r--r--packages/gitbook/src/plugins/__tests__/findInstalled.js (renamed from lib/plugins/__tests__/findInstalled.js)12
-rw-r--r--packages/gitbook/src/plugins/__tests__/installPlugin.js42
-rw-r--r--packages/gitbook/src/plugins/__tests__/installPlugins.js37
-rw-r--r--packages/gitbook/src/plugins/__tests__/listDependencies.js39
-rw-r--r--packages/gitbook/src/plugins/__tests__/locateRootFolder.js (renamed from lib/plugins/__tests__/locateRootFolder.js)4
-rw-r--r--packages/gitbook/src/plugins/__tests__/resolveVersion.js (renamed from lib/plugins/__tests__/resolveVersion.js)8
-rw-r--r--packages/gitbook/src/plugins/__tests__/sortDependencies.js (renamed from lib/plugins/__tests__/sortDependencies.js)20
-rw-r--r--packages/gitbook/src/plugins/__tests__/validatePlugin.js (renamed from lib/plugins/__tests__/validatePlugin.js)8
-rw-r--r--packages/gitbook/src/plugins/findForBook.js (renamed from lib/plugins/findForBook.js)15
-rw-r--r--packages/gitbook/src/plugins/findInstalled.js81
-rw-r--r--packages/gitbook/src/plugins/index.js8
-rw-r--r--packages/gitbook/src/plugins/installPlugin.js44
-rw-r--r--packages/gitbook/src/plugins/installPlugins.js (renamed from lib/plugins/installPlugins.js)30
-rw-r--r--packages/gitbook/src/plugins/listBlocks.js21
-rw-r--r--packages/gitbook/src/plugins/listDependencies.js (renamed from lib/plugins/listDependencies.js)6
-rw-r--r--packages/gitbook/src/plugins/listDepsForBook.js (renamed from lib/plugins/listDepsForBook.js)10
-rw-r--r--packages/gitbook/src/plugins/listFilters.js20
-rw-r--r--packages/gitbook/src/plugins/loadForBook.js (renamed from lib/plugins/loadForBook.js)24
-rw-r--r--packages/gitbook/src/plugins/loadPlugin.js (renamed from lib/plugins/loadPlugin.js)48
-rw-r--r--packages/gitbook/src/plugins/locateRootFolder.js22
-rw-r--r--packages/gitbook/src/plugins/resolveVersion.js (renamed from lib/plugins/resolveVersion.js)41
-rw-r--r--packages/gitbook/src/plugins/sortDependencies.js (renamed from lib/plugins/sortDependencies.js)14
-rw-r--r--packages/gitbook/src/plugins/toNames.js (renamed from lib/plugins/toNames.js)2
-rw-r--r--packages/gitbook/src/plugins/validateConfig.js71
-rw-r--r--packages/gitbook/src/plugins/validatePlugin.js (renamed from lib/plugins/validatePlugin.js)20
-rw-r--r--packages/gitbook/src/templating/__tests__/conrefsLoader.js111
-rw-r--r--packages/gitbook/src/templating/__tests__/include.md (renamed from lib/templating/__tests__/include.md)0
-rw-r--r--packages/gitbook/src/templating/__tests__/replaceShortcuts.js31
-rw-r--r--packages/gitbook/src/templating/conrefsLoader.js (renamed from lib/templating/conrefsLoader.js)32
-rw-r--r--packages/gitbook/src/templating/index.js7
-rw-r--r--packages/gitbook/src/templating/listShortcuts.js (renamed from lib/templating/listShortcuts.js)10
-rw-r--r--packages/gitbook/src/templating/render.js (renamed from lib/templating/render.js)16
-rw-r--r--packages/gitbook/src/templating/renderFile.js (renamed from lib/templating/renderFile.js)12
-rw-r--r--packages/gitbook/src/templating/replaceShortcuts.js (renamed from lib/templating/replaceShortcuts.js)16
-rw-r--r--packages/gitbook/src/utils/__tests__/git.js (renamed from lib/utils/__tests__/git.js)30
-rw-r--r--packages/gitbook/src/utils/__tests__/location.js (renamed from lib/utils/__tests__/location.js)3
-rw-r--r--packages/gitbook/src/utils/__tests__/path.js (renamed from lib/utils/__tests__/path.js)4
-rw-r--r--packages/gitbook/src/utils/command.js (renamed from lib/utils/command.js)82
-rw-r--r--packages/gitbook/src/utils/error.js (renamed from lib/utils/error.js)48
-rw-r--r--packages/gitbook/src/utils/fs.js (renamed from lib/utils/fs.js)62
-rw-r--r--packages/gitbook/src/utils/genKey.js (renamed from lib/utils/genKey.js)4
-rw-r--r--packages/gitbook/src/utils/git.js158
-rw-r--r--packages/gitbook/src/utils/images.js (renamed from lib/utils/images.js)24
-rw-r--r--packages/gitbook/src/utils/location.js (renamed from lib/utils/location.js)40
-rw-r--r--packages/gitbook/src/utils/logger.js (renamed from lib/utils/logger.js)48
-rw-r--r--packages/gitbook/src/utils/mergeDefaults.js (renamed from lib/utils/mergeDefaults.js)6
-rw-r--r--packages/gitbook/src/utils/path.js (renamed from lib/utils/path.js)25
-rw-r--r--packages/gitbook/src/utils/promise.js (renamed from lib/utils/promise.js)14
-rw-r--r--packages/gitbook/src/utils/reducedObject.js (renamed from lib/utils/reducedObject.js)8
-rw-r--r--packages/gitbook/src/utils/timing.js (renamed from lib/utils/timing.js)79
-rw-r--r--packages/gitbook/testing/setup.js (renamed from testing/setup.js)33
-rwxr-xr-xscripts/bump.js42
581 files changed, 13637 insertions, 6862 deletions
diff --git a/.eslintrc b/.eslintrc
index 33b9dfe..1891e69 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,15 +1,4 @@
{
- "rules": {
- "indent": [ 2, 4 ],
- "quotes": [ 2, "single" ],
- "linebreak-style": [ 2, "unix" ],
- "semi": [ 2, "always" ],
- "no-unused-vars": [ 2, {
- "vars": "all",
- "args": "none"
- } ],
- "spaced-comment": [ 2, "always" ]
- },
"env": {
"node": true,
"browser": true,
@@ -18,5 +7,5 @@
"globals": {
"expect": true
},
- "extends": "eslint:recommended"
-} \ No newline at end of file
+ "extends": "gitbook"
+}
diff --git a/.gitignore b/.gitignore
index add4c3c..eb6dba3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,14 +22,20 @@ build/Release
# Dependency directory
# 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
+node_modules
+
+# yarn
+yarn.lock
# vim swapfile
*.swp
+# GitBook with babel
+/packages/gitbook-core/lib
+/packages/gitbook-plugin/lib
+
# Output of documentation
_book
-
book.pdf
book.epub
book.mobi
diff --git a/CHANGES.md b/CHANGES.md
index a8b26d5..e84d00b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## 4.0.0
+- **Breaking Changes:** Most plugins and themes should be incompatible with this version
+- GitBook rendering is now done using React, which brings better APIs for plugins and a more interactive website.
+- *Performances*: Installation of plugins is much faster thanks to `ied`
+- *Performances*: Git conrefs are faster over large books
+- *Website Feature:* new default plugin `copy-code` to copy code blocks content in one click
+- *Website Feature:* new default plugin `heading-anchors` to have clickable headings
+- *Website Feature:* `fontsettings` is now only controlling the font size, and can work on multiple themes
+- *Plugin API*: hook `page` can no longer modify the content, only the page's attributes
+- *Plugin API*: plugins can no longer export resources, instead use the `_assets` folder
+
## 3.2.2
- Fix catching parsing errors leading to possible missing pages in generated book
- Rollback markdown parser to `kramed` until `markup-it` is stable enough
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d8c3fb0
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,51 @@
+
+# Contributing
+
+Want to contribute to GitBook? That would be awesome!
+
+- [Send feedback](#send-feedback)
+- [Reporting Bugs](#reporting-bugs)
+- [Asking Questions](#asking-questions)
+- [Submitting Pull Requests](#submitting-pull-requests)
+- [Running Tests](#running-tests)
+
+## Send feedback
+
+We’ve done our best to test but your feedback is really important. If you encounter problems or have feedback about GitBook products (GitBook.com, GitBook Editor, or the Toolchain), please log an issue in the [GitbookIO/feedback](https://github.com/GitbookIO/feedback/issues) repository.
+
+
+## Reporting Bugs
+
+If you run into any weird behavior while using GitBook, feel free to open a new issue in this repository! To be most helpful, please include the steps to reproduce the bug as best you can, including the output of command `gitbook -V`.
+
+## Asking Questions
+
+Questions are very welcome :smile:! Previous questions that folks have asked are tagged with a [`question`](https://github.com/GitBookIO/gitbook/issues?q=is%3Aissue+is%3Aclosed+label%3Aquestion) label, so before opening a new issue double-check that someone hasn't asked it before. But if you don't see anything, or if you're not sure if it's the same, err on the side of asking!
+
+We've also got a [GitBook Community Slack](https://slack.gitbook.com/) where you can ask questions and get answers from other people using GitBook.
+
+## Submitting Pull Requests
+
+All pull requests are super welcomed and greatly appreciated! Easy issues are marked with an [`easy-one`](https://github.com/GitBookIO/gitbook/issues?q=is%3Aopen+is%3Aissue+label%3Aeasy-one) label if you're looking for a simple place to get familiar with the code base.
+
+Please include tests and docs with every pull request!
+
+## Running Tests
+
+To run the examples, you need to have the GitBook repository cloned to your computer. After that, you need to `cd` into the directory where you cloned it, and install the dependencies from `npm`.
+
+```
+npm install
+```
+
+Then you'll need to bootstrap it:
+
+```
+npm run bootstrap
+```
+
+Which will also compile the source files. Then run the tests with:
+
+```
+npm test
+```
diff --git a/book.js b/book.js
index 31d751b..21a0c8a 100644
--- a/book.js
+++ b/book.js
@@ -5,11 +5,8 @@ module.exports = {
root: './docs',
title: 'GitBook Toolchain Documentation',
- // Enforce use of GitBook v3
- gitbook: '3.1.1',
-
// Use the "official" theme
- plugins: ['theme-official@2.1.1', '-sharing', '-fontsettings', 'sitemap'],
+ plugins: ['sitemap'],
variables: {
version: pkg.version
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 3548605..e3d8d67 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -42,6 +42,13 @@
* [Test your plugin](plugins/testing.md)
* [Theming](themes/README.md)
+### Plugin Development
+
+* [Introduction](./api/README.md)
+* [Node](./api/node.md)
+* [Connect to the context](./api/connect.md)
+* [Components](./api/components.md)
+
--
* [FAQ](faq.md)
diff --git a/docs/_layouts/website/page.html b/docs/_layouts/website/page.html
deleted file mode 100644
index 47954f7..0000000
--- a/docs/_layouts/website/page.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{% extends template.self %}
-
-{% block header_nav %}
-<a href="https://github.com/GitbookIO/gitbook/blob/master/docs/{{ file.path }}" target="_blank" class="btn btn-link pull-right hidden-xs">
- <i class="octicon octicon-mark-github"></i> Edit on GitHub
-</a>
-<a href="{{ "faq.md"|resolveFile }}" class="btn btn-link pull-right hidden-xs">
- F.A.Q
-</a>
-<a href="https://github.com/GitbookIO/gitbook/blob/master/CHANGES.md" target="_blank" class="btn btn-link pull-right hidden-xs">
- {{ book.version }}
-</a>
-{% endblock %}
-
-{% block page %}
-{{ super() }}
-<hr>
-<div class="btn-group btn-group-justified">
- {% if page.previous and page.previous.path %}
- <a class="btn" href="{{ page.previous.path|resolveFile }}"><b>Previous:</b> {{ page.previous.title }}</a>
- {% endif %}
- {% if page.next and page.next.path %}
- <a class="btn" href="{{ page.next.path|resolveFile }}"><b>Next:</b> {{ page.next.title }}</a>
- {% endif %}
-</div>
-{% endblock %}
diff --git a/docs/api/README.md b/docs/api/README.md
new file mode 100644
index 0000000..66b05b6
--- /dev/null
+++ b/docs/api/README.md
@@ -0,0 +1,24 @@
+# Plugin Architecture
+
+A GitBook plugin is a NPM package that follows a defined convention.
+
+`gitbook-plugin` is a command line utility to help you create, test and release plugins.
+
+### Bootstrap your first plugin
+
+Install `gitbook-plugin` from NPM:
+
+```
+$ npm install gitbook-plugin -g
+```
+
+Then create your plugin:
+
+```
+$ gitbook-plugin create
+```
+
+You will be asked for a plugin name, and a few other things to complete the creation process.
+
+
+### Publish your plugin
diff --git a/docs/api/components.md b/docs/api/components.md
new file mode 100644
index 0000000..e323abc
--- /dev/null
+++ b/docs/api/components.md
@@ -0,0 +1,86 @@
+# Components
+
+
+## Injection
+
+Plugins can inject components by registering React components to some roles.
+
+#### Register a components
+
+During the initialization phase of your plugin, dispatch the `GitBook.registerComponent` action:
+
+```js
+dispatch(GitBook.registerComponent(MyCustomButton, { role: 'toolbar:buttons:left' }));
+```
+
+#### Roles
+
+Custom roles can be use for interopability with other plugins, but GitBook and the default theme set a convention for common roles:
+
+| Role | Description | Props |
+| ---- | ----------- | ----- |
+| `page:container` | DIV container for the page's content | `{ page: Page }` |
+| `summary:container` | DIV container for the whole summary | `{ summary: Summary }` |
+| `summary:parts` | DIV container for summary's parts | `{ parts: List<SummaryPart> }` |
+| `summary:part` | DIV for a specific part | `{ part: SummaryPart }` |
+| `summary:articles` | UL container for a part's articles | `{ articles: List<SummaryArticle> }` |
+| `summary:article` | LI for a specific article | `{ article: SummaryArticle }` |
+
+## Default Components
+
+#### `GitBook.Head`
+
+Extends the meta tags of the page. This is an alias for [react-helmet](https://github.com/nfl/react-helmet).
+
+```js
+<GitBook.Head
+ title="My page"
+ />
+```
+
+#### `GitBook.ImportCSS`
+
+Import a CSS file by resolving the path correctly according to the current page:
+
+```js
+<GitBook.ImportCSS href="myfile.css" />
+```
+
+#### `GitBook.ImportJS`
+
+Import a JS file by resolving the path correctly according to the current page:
+
+```js
+<GitBook.ImportJS src="mylib.js" />
+```
+
+#### `GitBook.InjectedComponent`
+
+Inject a component matching a specific role:
+
+```js
+<GitBook.InjectedComponent matching={{ role: 'mycustomrole' }} props={{ someProp: 1 }}>
+ <b>Inner content</b>
+</GitBook.InjectedComponent>
+```
+
+#### `GitBook.InjectedComponentSet`
+
+Same API as `InjectedComponentSet` but render the matching components in chain instead of composed:
+
+```js
+<GitBook.InjectedComponentSet matching={{ role: 'mytoolbar' }} />
+```
+
+**Warning:** Children are discarded.
+
+#### `GitBook.FlexLayout` and `GitBook.FlexBox`
+
+A simple wrapper that provides a Flexbox layout with the given direction and style. Any additional props you set on the Flexbox are rendered.
+
+```js
+<GitBook.FlexLayout column>
+ <GitBook.FlexBox>First column</GitBook.FlexBox>
+ <GitBook.FlexBox>Second column</GitBook.FlexBox>
+</GitBook.FlexLayout>
+```
diff --git a/docs/api/connect.md b/docs/api/connect.md
new file mode 100644
index 0000000..740deca
--- /dev/null
+++ b/docs/api/connect.md
@@ -0,0 +1,33 @@
+# Connect to the context
+
+`GitBook.connect(Component, [mapStateToProps], [mapActionsToProps])` connects a react component to the GitBook context.
+
+It does not modify the component class passed to it.
+Instead, it returns a new, connected component class, for you to use.
+
+### `mapStateToProps(state, [ownProps]): stateProps`
+
+If specified, the component will subscribe to GitBook store updates. Any time it updates, `mapStateToProps` will be called. Its result must be a plain object, and it will be merged into the component’s props.
+
+If you omit it, the component will not be subscribed to the GitBook store. If `ownProps` is specified as a second argument, its value will be the props passed to your component, and `mapStateToProps` will be additionally re-invoked whenever the component receives new props (e.g. if props received from a parent component have shallowly changed, and you use the `ownProps` argument, `mapStateToProps` is re-evaluated).
+
+For example to render the title of the current page:
+
+```js
+const GitBook = require('gitbook-core');
+
+let PageTitle = React.createClass({
+ render() {
+ const { page } = this.props;
+ return <h1>{page.title}</h1>;
+ }
+});
+
+function mapStateToProps(state) {
+ return { page: state.page };
+}
+
+PageTitle = GitBook.connect(PageTitle, mapStateToProps);
+```
+
+### `mapActionsToProps(actions, [dispatch])`
diff --git a/docs/api/i18n.md b/docs/api/i18n.md
new file mode 100644
index 0000000..b58f1e8
--- /dev/null
+++ b/docs/api/i18n.md
@@ -0,0 +1,44 @@
+# Internationalize your plugin
+
+GitBook has built-in support for internationalization. Plugins can register new languages and provide the right messages for different languages.
+
+### Register locale and messages
+
+The first step is to register messages for a language:
+
+```js
+module.exports = GitBook.createPlugin({
+ init: (dispatch, getState, { I18n }) => {
+ dispatch(I18n.registerLocale('en-US', {
+ MY_PLUGIN_MESSAGE: 'Hello World'
+ }));
+ }
+});
+```
+
+### Render a message in a component
+
+`GitBook.connect` adds a `i18n` prop to access the internationalization:
+
+```js
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const MyButton = React.createClass({
+ propTypes: {
+ i18n: GitBook.Shapes.i18n
+ },
+
+ render() {
+ const { i18n } = this.props;
+
+ return (
+ <GitBook.Button>
+ {i18.t('MY_PLUGIN_MESSAGE')}
+ </GitBook.Button>
+ );
+ }
+});
+
+module.exports = GitBook.connect(MyButton);
+```
diff --git a/docs/api/navigation.md b/docs/api/navigation.md
new file mode 100644
index 0000000..b0a1d2d
--- /dev/null
+++ b/docs/api/navigation.md
@@ -0,0 +1,23 @@
+# Navigation
+
+### Listen to url change
+
+Listen for changes to the current location:
+
+```js
+const onLocationChanged = (location) => {
+ console.log(location.pathname);
+ console.log(location.query);
+ console.log(location.hash);
+};
+
+module.exports = GitBook.createPlugin({
+ init: (dispatch, getState, { Navigation }) => {
+ dispatch(Navigation.listen(onLocationChanged));
+ }
+});
+```
+
+The `onLocationChanged` will be triggered for initial state.
+
+### Changing the url
diff --git a/docs/api/node.md b/docs/api/node.md
new file mode 100644
index 0000000..be0cce2
--- /dev/null
+++ b/docs/api/node.md
@@ -0,0 +1,97 @@
+# Node APIs
+
+GitBooks provides different Node APIs and contexts to plugins. These APIs can vary according to the GitBook version being used, your plugin should specify the `engines.gitbook` field in `package.json` accordingly.
+
+#### Book instance
+
+The `Book` interface is the central point of GitBook, it centralize all access read methods.
+
+```js
+// Read configuration from book.json
+var value = book.config.get('title', 'Default Value');
+
+// Resolve a filename to an absolute path
+var filepath = book.resolve('README.md');
+
+// Render an inline markup string
+book.renderInline('markdown', 'This is **Markdown**')
+ .then(function(str) { ... })
+
+// Render a markup string (block mode)
+book.renderBlock('markdown', '* This is **Markdown**')
+ .then(function(str) { ... })
+```
+
+#### Output instance
+
+The `Output` class represent the output/write process.
+
+```js
+// Return root folder for the output
+var root = output.root();
+
+// Resolve a file in the output folder
+var filepath = output.resolve('myimage.png');
+
+// Convert a filename to an URL (returns a path to an html file)
+var fileurl = output.toURL('mychapter/README.md');
+
+// Write a file in the output folder
+output.writeFile('hello.txt', 'Hello World')
+ .then(function() { ... });
+
+// Copy a file to the output folder
+output.copyFile('./myfile.jpg', 'cover.jpg')
+ .then(function() { ... });
+
+// Verify that a file exists
+output.hasFile('hello.txt')
+ .then(function(exists) { ... });
+```
+
+#### Page instance
+
+A page instance represent the current parsed page.
+
+```js
+// Title of the page (from SUMMARY)
+page.title
+
+// Content of the page (Markdown/Asciidoc/HTML according to the stage)
+page.content
+
+// Relative path in the book
+page.path
+
+// Absolute path to the file
+page.rawPath
+
+// Type of parser used for this file
+page.type ('markdown' or 'asciidoc')
+```
+
+#### Context for Blocks and Filters
+
+Blocks and filters have access to the same context, this context is bind to the template engine execution:
+
+```js
+{
+ // Current templating syntax
+ "ctx": {
+ // For example, after a {% set message = "hello" %}
+ "message": "hello"
+ },
+
+ // Book instance
+ "book" <Book>,
+
+ // Output instance
+ "output": <Output>
+}
+```
+
+For example a filter or block function can access the current book using: `this.book`.
+
+#### Context for Hooks
+
+Hooks only have access to the `<Book>` instance using `this.book`.
diff --git a/lerna.json b/lerna.json
new file mode 100644
index 0000000..5444b1e
--- /dev/null
+++ b/lerna.json
@@ -0,0 +1,4 @@
+{
+ "lerna": "2.0.0-beta.29",
+ "version": "4.0.0"
+}
diff --git a/lib/api/decodeGlobal.js b/lib/api/decodeGlobal.js
deleted file mode 100644
index 118afb2..0000000
--- a/lib/api/decodeGlobal.js
+++ /dev/null
@@ -1,22 +0,0 @@
-var decodeConfig = require('./decodeConfig');
-
-/**
- Decode changes from a JS API to a output object.
- Only the configuration can be edited by plugin's hooks
-
- @param {Output} output
- @param {Object} result: result from API
- @return {Output}
-*/
-function decodeGlobal(output, result) {
- var book = output.getBook();
- var config = book.getConfig();
-
- // Update config
- config = decodeConfig(config, result.config);
- book = book.set('config', config);
-
- return output.set('book', book);
-}
-
-module.exports = decodeGlobal;
diff --git a/lib/api/decodePage.js b/lib/api/decodePage.js
deleted file mode 100644
index c85dd1b..0000000
--- a/lib/api/decodePage.js
+++ /dev/null
@@ -1,44 +0,0 @@
-var deprecate = require('./deprecate');
-
-/**
- Decode changes from a JS API to a page object.
- Only the content can be edited by plugin's hooks.
-
- @param {Output} output
- @param {Page} page: page instance to edit
- @param {Object} result: result from API
- @return {Page}
-*/
-function decodePage(output, page, result) {
- var originalContent = page.getContent();
-
- // No returned value
- // Existing content will be used
- if (!result) {
- return page;
- }
-
- deprecate.disable('page.sections');
-
- // GitBook 3
- // Use returned page.content if different from original content
- if (result.content != originalContent) {
- page = page.set('content', result.content);
- }
-
- // GitBook 2 compatibility
- // Finally, use page.sections
- else if (result.sections) {
- page = page.set('content',
- result.sections.map(function(section) {
- return section.content;
- }).join('\n')
- );
- }
-
- deprecate.enable('page.sections');
-
- return page;
-}
-
-module.exports = decodePage;
diff --git a/lib/api/deprecate.js b/lib/api/deprecate.js
deleted file mode 100644
index 7a93a91..0000000
--- a/lib/api/deprecate.js
+++ /dev/null
@@ -1,122 +0,0 @@
-var is = require('is');
-var objectPath = require('object-path');
-
-var logged = {};
-var disabled = {};
-
-/**
- Log a deprecated notice
-
- @param {Book|Output} book
- @param {String} key
- @param {String} message
-*/
-function logNotice(book, key, message) {
- if (logged[key] || disabled[key]) return;
-
- logged[key] = true;
-
- var logger = book.getLogger();
- logger.warn.ln(message);
-}
-
-/**
- Deprecate a function
-
- @param {Book|Output} book
- @param {String} key: unique identitifer for the deprecated
- @param {Function} fn
- @param {String} msg: message to print when called
- @return {Function}
-*/
-function deprecateMethod(book, key, fn, msg) {
- return function() {
- logNotice(book, key, msg);
-
- return fn.apply(this, arguments);
- };
-}
-
-/**
- Deprecate a property of an object
-
- @param {Book|Output} book
- @param {String} key: unique identitifer for the deprecated
- @param {Object} instance
- @param {String|Function} property
- @param {String} msg: message to print when called
- @return {Function}
-*/
-function deprecateField(book, key, instance, property, value, msg) {
- var store = undefined;
-
- var prepare = function() {
- if (!is.undefined(store)) return;
-
- if (is.fn(value)) store = value();
- else store = value;
- };
-
- var getter = function(){
- prepare();
-
- logNotice(book, key, msg);
- return store;
- };
- var setter = function(v) {
- prepare();
-
- logNotice(book, key, msg);
- store = v;
- return store;
- };
-
- Object.defineProperty(instance, property, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
-}
-
-/**
- Enable a deprecation
-
- @param {String} key: unique identitifer
-*/
-function enableDeprecation(key) {
- disabled[key] = false;
-}
-
-/**
- Disable a deprecation
-
- @param {String} key: unique identitifer
-*/
-function disableDeprecation(key) {
- disabled[key] = true;
-}
-
-/**
- Deprecate a method in favor of another one
-
- @param {Book} book
- @param {String} key
- @param {Object} instance
- @param {String} oldName
- @param {String} newName
-*/
-function deprecateRenamedMethod(book, key, instance, oldName, newName, msg) {
- msg = msg || ('"' + oldName + '" is deprecated, use "' + newName + '()" instead');
- var fn = objectPath.get(instance, newName);
-
- instance[oldName] = deprecateMethod(book, key, fn, msg);
-}
-
-module.exports = {
- method: deprecateMethod,
- renamedMethod: deprecateRenamedMethod,
- field: deprecateField,
- enable: enableDeprecation,
- disable: disableDeprecation
-};
diff --git a/lib/api/encodeGlobal.js b/lib/api/encodeGlobal.js
deleted file mode 100644
index a366526..0000000
--- a/lib/api/encodeGlobal.js
+++ /dev/null
@@ -1,257 +0,0 @@
-var path = require('path');
-var Promise = require('../utils/promise');
-var PathUtils = require('../utils/path');
-var fs = require('../utils/fs');
-
-var Plugins = require('../plugins');
-var deprecate = require('./deprecate');
-var fileToURL = require('../output/helper/fileToURL');
-var defaultBlocks = require('../constants/defaultBlocks');
-var gitbook = require('../gitbook');
-var parsers = require('../parsers');
-
-var encodeConfig = require('./encodeConfig');
-var encodeSummary = require('./encodeSummary');
-var encodeNavigation = require('./encodeNavigation');
-var encodePage = require('./encodePage');
-
-/**
- Encode a global context into a JS object
- It's the context for page's hook, etc
-
- @param {Output} output
- @return {Object}
-*/
-function encodeGlobal(output) {
- var book = output.getBook();
- var bookFS = book.getContentFS();
- var logger = output.getLogger();
- var outputFolder = output.getRoot();
- var plugins = output.getPlugins();
- var blocks = Plugins.listBlocks(plugins);
-
- var result = {
- log: logger,
- config: encodeConfig(output, book.getConfig()),
- summary: encodeSummary(output, book.getSummary()),
-
- /**
- Check if the book is a multilingual book
-
- @return {Boolean}
- */
- isMultilingual: function() {
- return book.isMultilingual();
- },
-
- /**
- Check if the book is a language book for a multilingual book
-
- @return {Boolean}
- */
- isLanguageBook: function() {
- return book.isLanguageBook();
- },
-
- /**
- Read a file from the book
-
- @param {String} fileName
- @return {Promise<Buffer>}
- */
- readFile: function(fileName) {
- return bookFS.read(fileName);
- },
-
- /**
- Read a file from the book as a string
-
- @param {String} fileName
- @return {Promise<String>}
- */
- readFileAsString: function(fileName) {
- return bookFS.readAsString(fileName);
- },
-
- /**
- Resolve a file from the book root
-
- @param {String} fileName
- @return {String}
- */
- resolve: function(fileName) {
- return path.resolve(book.getContentRoot(), fileName);
- },
-
- /**
- Resolve a page by it path
-
- @param {String} filePath
- @return {String}
- */
- getPageByPath: function(filePath) {
- var page = output.getPage(filePath);
- if (!page) return undefined;
-
- return encodePage(output, page);
- },
-
- /**
- Render a block of text (markdown/asciidoc)
-
- @param {String} type
- @param {String} text
- @return {Promise<String>}
- */
- renderBlock: function(type, text) {
- var parser = parsers.get(type);
-
- return parser.parsePage(text)
- .get('content');
- },
-
- /**
- Render an inline text (markdown/asciidoc)
-
- @param {String} type
- @param {String} text
- @return {Promise<String>}
- */
- renderInline: function(type, text) {
- var parser = parsers.get(type);
-
- return parser.parseInline(text)
- .get('content');
- },
-
- template: {
- /**
- Apply a templating block and returns its result
-
- @param {String} name
- @param {Object} blockData
- @return {Promise|Object}
- */
- applyBlock: function(name, blockData) {
- var block = blocks.get(name) || defaultBlocks.get(name);
- return Promise(block.applyBlock(blockData, result));
- }
- },
-
- output: {
- /**
- Name of the generator being used
- {String}
- */
- name: output.getGenerator(),
-
- /**
- Return absolute path to the root folder of output
- @return {String}
- */
- root: function() {
- return outputFolder;
- },
-
- /**
- Resolve a file from the output root
-
- @param {String} fileName
- @return {String}
- */
- resolve: function(fileName) {
- return path.resolve(outputFolder, fileName);
- },
-
- /**
- Convert a filepath into an url
- @return {String}
- */
- toURL: function(filePath) {
- return fileToURL(output, filePath);
- },
-
- /**
- Check that a file exists.
-
- @param {String} fileName
- @return {Promise}
- */
- hasFile: function(fileName, content) {
- return Promise()
- .then(function() {
- var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
-
- return fs.exists(filePath);
- });
- },
-
- /**
- Write a file to the output folder,
- It creates the required folder
-
- @param {String} fileName
- @param {Buffer} content
- @return {Promise}
- */
- writeFile: function(fileName, content) {
- return Promise()
- .then(function() {
- var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
-
- return fs.ensureFile(filePath)
- .then(function() {
- return fs.writeFile(filePath, content);
- });
- });
- },
-
- /**
- Copy a file to the output folder
- It creates the required folder.
-
- @param {String} inputFile
- @param {String} outputFile
- @param {Buffer} content
- @return {Promise}
- */
- copyFile: function(inputFile, outputFile, content) {
- return Promise()
- .then(function() {
- var outputFilePath = PathUtils.resolveInRoot(outputFolder, outputFile);
-
- return fs.ensureFile(outputFilePath)
- .then(function() {
- return fs.copy(inputFile, outputFilePath);
- });
- });
- }
- },
-
- gitbook: {
- version: gitbook.version
- }
- };
-
- // Deprecated properties
-
- deprecate.renamedMethod(output, 'this.isSubBook', result, 'isSubBook', 'isLanguageBook');
- deprecate.renamedMethod(output, 'this.contentLink', result, 'contentLink', 'output.toURL');
-
- deprecate.field(output, 'this.generator', result, 'generator',
- output.getGenerator(), '"this.generator" property is deprecated, use "this.output.name" instead');
-
- deprecate.field(output, 'this.navigation', result, 'navigation', function() {
- return encodeNavigation(output);
- }, '"navigation" property is deprecated');
-
- deprecate.field(output, 'this.book', result, 'book',
- result, '"book" property is deprecated, use "this" directly instead');
-
- deprecate.field(output, 'this.options', result, 'options',
- result.config.values, '"options" property is deprecated, use config.get(key) instead');
-
- return result;
-}
-
-module.exports = encodeGlobal;
diff --git a/lib/api/index.js b/lib/api/index.js
deleted file mode 100644
index 5e67525..0000000
--- a/lib/api/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-
-module.exports = {
- encodePage: require('./encodePage'),
- decodePage: require('./decodePage'),
-
- encodeGlobal: require('./encodeGlobal'),
- decodeGlobal: require('./decodeGlobal')
-};
diff --git a/lib/browser.js b/lib/browser.js
deleted file mode 100644
index 87a4dc4..0000000
--- a/lib/browser.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var Modifiers = require('./modifiers');
-
-module.exports = {
- Parse: require('./parse'),
-
- // Models
- Book: require('./models/book'),
- FS: require('./models/fs'),
- File: require('./models/file'),
- Summary: require('./models/summary'),
- Glossary: require('./models/glossary'),
- Config: require('./models/config'),
- Page: require('./models/page'),
- PluginDependency: require('./models/pluginDependency'),
-
- // Modifiers
- SummaryModifier: Modifiers.Summary,
- ConfigModifier: Modifiers.Config,
-
- // Constants
- CONFIG_FILES: require('./constants/configFiles.js'),
- IGNORE_FILES: require('./constants/ignoreFiles.js'),
- DEFAULT_PLUGINS: require('./constants/defaultPlugins'),
- EXTENSIONS_MARKDOWN: require('./constants/extsMarkdown'),
- EXTENSIONS_ASCIIDOC: require('./constants/extsAsciidoc')
-};
diff --git a/lib/cli/getBook.js b/lib/cli/getBook.js
deleted file mode 100644
index ac82187..0000000
--- a/lib/cli/getBook.js
+++ /dev/null
@@ -1,23 +0,0 @@
-var path = require('path');
-var Book = require('../models/book');
-var createNodeFS = require('../fs/node');
-
-/**
- Return a book instance to work on from
- command line args/kwargs
-
- @param {Array} args
- @param {Object} kwargs
- @return {Book}
-*/
-function getBook(args, kwargs) {
- var input = path.resolve(args[0] || process.cwd());
- var logLevel = kwargs.log;
-
- var fs = createNodeFS(input);
- var book = Book.createForFS(fs);
-
- return book.setLogLevel(logLevel);
-}
-
-module.exports = getBook;
diff --git a/lib/cli/getOutputFolder.js b/lib/cli/getOutputFolder.js
deleted file mode 100644
index 272dff9..0000000
--- a/lib/cli/getOutputFolder.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var path = require('path');
-
-/**
- Return path to output folder
-
- @param {Array} args
- @return {String}
-*/
-function getOutputFolder(args) {
- var bookRoot = path.resolve(args[0] || process.cwd());
- var defaultOutputRoot = path.join(bookRoot, '_book');
- var outputFolder = args[1]? path.resolve(process.cwd(), args[1]) : defaultOutputRoot;
-
- return outputFolder;
-}
-
-module.exports = getOutputFolder;
diff --git a/lib/cli/init.js b/lib/cli/init.js
deleted file mode 100644
index 55f1b15..0000000
--- a/lib/cli/init.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var path = require('path');
-
-var options = require('./options');
-var initBook = require('../init');
-
-module.exports = {
- name: 'init [book]',
- description: 'setup and create files for chapters',
- options: [
- options.log
- ],
- exec: function(args, kwargs) {
- var bookRoot = path.resolve(process.cwd(), args[0] || './');
-
- return initBook(bookRoot);
- }
-};
diff --git a/lib/cli/server.js b/lib/cli/server.js
deleted file mode 100644
index 752f867..0000000
--- a/lib/cli/server.js
+++ /dev/null
@@ -1,128 +0,0 @@
-var events = require('events');
-var http = require('http');
-var send = require('send');
-var util = require('util');
-var url = require('url');
-
-var Promise = require('../utils/promise');
-
-function Server() {
- this.running = null;
- this.dir = null;
- this.port = 0;
- this.sockets = [];
-}
-util.inherits(Server, events.EventEmitter);
-
-/**
- Return true if the server is running
-
- @return {Boolean}
-*/
-Server.prototype.isRunning = function() {
- return !!this.running;
-};
-
-/**
- Stop the server
-
- @return {Promise}
-*/
-Server.prototype.stop = function() {
- var that = this;
- if (!this.isRunning()) return Promise();
-
- var d = Promise.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;
-};
-
-/**
- Start the server
-
- @return {Promise}
-*/
-Server.prototype.start = function(dir, port) {
- var that = this, pre = Promise();
- port = port || 8004;
-
- if (that.isRunning()) pre = this.stop();
- return pre
- .then(function() {
- var d = Promise.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() {
- var resultURL = urlTransform(req.url, function(parsed) {
- parsed.pathname += '/';
- return parsed;
- });
-
- res.statusCode = 301;
- res.setHeader('Location', resultURL);
- res.end('Redirecting to ' + resultURL);
- }
-
- res.setHeader('X-Current-Location', 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;
- });
-};
-
-/**
- urlTransform is a helper function that allows a function to transform
- a url string in it's parsed form and returns the new url as a string
-
- @param {String} uri
- @param {Function} fn
- @return {String}
-*/
-function urlTransform(uri, fn) {
- return url.format(fn(url.parse(uri)));
-}
-
-module.exports = Server;
diff --git a/lib/constants/configDefault.js b/lib/constants/configDefault.js
deleted file mode 100644
index 0d95883..0000000
--- a/lib/constants/configDefault.js
+++ /dev/null
@@ -1,6 +0,0 @@
-var Immutable = require('immutable');
-var jsonSchemaDefaults = require('json-schema-defaults');
-
-var schema = require('./configSchema');
-
-module.exports = Immutable.fromJS(jsonSchemaDefaults(schema));
diff --git a/lib/constants/defaultBlocks.js b/lib/constants/defaultBlocks.js
deleted file mode 100644
index 74d1f1f..0000000
--- a/lib/constants/defaultBlocks.js
+++ /dev/null
@@ -1,51 +0,0 @@
-var Immutable = require('immutable');
-var TemplateBlock = require('../models/templateBlock');
-
-module.exports = Immutable.Map({
- html: TemplateBlock({
- name: 'html',
- process: function(blk) {
- return blk;
- }
- }),
-
- code: TemplateBlock({
- name: 'code',
- process: function(blk) {
- return {
- html: false,
- body: blk.body
- };
- }
- }),
-
- markdown: TemplateBlock({
- name: 'markdown',
- process: function(blk) {
- return this.book.renderInline('markdown', blk.body)
- .then(function(out) {
- return { body: out };
- });
- }
- }),
-
- asciidoc: TemplateBlock({
- name: 'asciidoc',
- process: function(blk) {
- return this.book.renderInline('asciidoc', blk.body)
- .then(function(out) {
- return { body: out };
- });
- }
- }),
-
- markup: TemplateBlock({
- name: 'markup',
- process: function(blk) {
- return this.book.renderInline(this.ctx.file.type, blk.body)
- .then(function(out) {
- return { body: out };
- });
- }
- })
-});
diff --git a/lib/index.js b/lib/index.js
deleted file mode 100644
index 1f683e2..0000000
--- a/lib/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-var extend = require('extend');
-
-var common = require('./browser');
-
-module.exports = extend({
- initBook: require('./init'),
- createNodeFS: require('./fs/node'),
- Output: require('./output'),
- commands: require('./cli')
-}, common);
diff --git a/lib/json/encodeBook.js b/lib/json/encodeBook.js
deleted file mode 100644
index 9d7ec77..0000000
--- a/lib/json/encodeBook.js
+++ /dev/null
@@ -1,39 +0,0 @@
-var extend = require('extend');
-
-var gitbook = require('../gitbook');
-var encodeSummary = require('./encodeSummary');
-var encodeGlossary = require('./encodeGlossary');
-var encodeReadme = require('./encodeReadme');
-var encodeLanguages = require('./encodeLanguages');
-
-/**
- Encode a book to JSON
-
- @param {Book}
- @return {Object}
-*/
-function encodeBookToJson(book) {
- var config = book.getConfig();
- var language = book.getLanguage();
-
- var variables = config.getValue('variables', {});
-
- return {
- summary: encodeSummary(book.getSummary()),
- glossary: encodeGlossary(book.getGlossary()),
- readme: encodeReadme(book.getReadme()),
- config: book.getConfig().getValues().toJS(),
-
- languages: book.isMultilingual()? encodeLanguages(book.getLanguages()) : undefined,
-
- gitbook: {
- version: gitbook.version,
- time: gitbook.START_TIME
- },
- book: extend({
- language: language? language : undefined
- }, variables.toJS())
- };
-}
-
-module.exports = encodeBookToJson;
diff --git a/lib/json/encodeBookWithPage.js b/lib/json/encodeBookWithPage.js
deleted file mode 100644
index 1c5c7a3..0000000
--- a/lib/json/encodeBookWithPage.js
+++ /dev/null
@@ -1,22 +0,0 @@
-var encodeBook = require('./encodeBook');
-var encodePage = require('./encodePage');
-var encodeFile = require('./encodeFile');
-
-/**
- * Return a JSON representation of a book with a specific file
- *
- * @param {Book} output
- * @param {Page} page
- * @return {Object}
- */
-function encodeBookWithPage(book, page) {
- var file = page.getFile();
-
- var result = encodeBook(book);
- result.page = encodePage(page, book.getSummary());
- result.file = encodeFile(file);
-
- return result;
-}
-
-module.exports = encodeBookWithPage;
diff --git a/lib/json/encodeFile.js b/lib/json/encodeFile.js
deleted file mode 100644
index d2c9e8a..0000000
--- a/lib/json/encodeFile.js
+++ /dev/null
@@ -1,21 +0,0 @@
-
-/**
- Return a JSON representation of a file
-
- @param {File} file
- @return {Object}
-*/
-function encodeFileToJson(file) {
- var filePath = file.getPath();
- if (!filePath) {
- return undefined;
- }
-
- return {
- path: filePath,
- mtime: file.getMTime(),
- type: file.getType()
- };
-}
-
-module.exports = encodeFileToJson;
diff --git a/lib/json/encodeGlossary.js b/lib/json/encodeGlossary.js
deleted file mode 100644
index e9bcfc9..0000000
--- a/lib/json/encodeGlossary.js
+++ /dev/null
@@ -1,21 +0,0 @@
-var encodeFile = require('./encodeFile');
-var encodeGlossaryEntry = require('./encodeGlossaryEntry');
-
-/**
- Encode a glossary to JSON
-
- @param {Glossary}
- @return {Object}
-*/
-function encodeGlossary(glossary) {
- var file = glossary.getFile();
- var entries = glossary.getEntries();
-
- return {
- file: encodeFile(file),
- entries: entries
- .map(encodeGlossaryEntry).toJS()
- };
-}
-
-module.exports = encodeGlossary;
diff --git a/lib/json/encodeLanguages.js b/lib/json/encodeLanguages.js
deleted file mode 100644
index 8447e80..0000000
--- a/lib/json/encodeLanguages.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var encodeFile = require('./encodeFile');
-
-/**
- Encode a languages listing to JSON
-
- @param {Languages}
- @return {Object}
-*/
-function encodeLanguages(languages) {
- var file = languages.getFile();
- var list = languages.getList();
-
- return {
- file: encodeFile(file),
- list: list
- .valueSeq()
- .map(function(lang) {
- return {
- id: lang.getID(),
- title: lang.getTitle()
- };
- }).toJS()
- };
-}
-
-module.exports = encodeLanguages;
diff --git a/lib/json/encodeOutput.js b/lib/json/encodeOutput.js
deleted file mode 100644
index 7347e57..0000000
--- a/lib/json/encodeOutput.js
+++ /dev/null
@@ -1,25 +0,0 @@
-var encodeBook = require('./encodeBook');
-
-/**
- * Encode an output to JSON
- *
- * @param {Output}
- * @return {Object}
- */
-function encodeOutputToJson(output) {
- var book = output.getBook();
- var generator = output.getGenerator();
- var options = output.getOptions();
-
- var result = encodeBook(book);
-
- result.output = {
- name: generator
- };
-
- result.options = options.toJS();
-
- return result;
-}
-
-module.exports = encodeOutputToJson;
diff --git a/lib/json/encodeOutputWithPage.js b/lib/json/encodeOutputWithPage.js
deleted file mode 100644
index 8b21e3d..0000000
--- a/lib/json/encodeOutputWithPage.js
+++ /dev/null
@@ -1,23 +0,0 @@
-var encodeOutput = require('./encodeOutput');
-var encodePage = require('./encodePage');
-var encodeFile = require('./encodeFile');
-
-/**
- * Return a JSON representation of a book with a specific file
- *
- * @param {Book} output
- * @param {Page} page
- * @return {Object}
- */
-function encodeOutputWithPage(output, page) {
- var file = page.getFile();
- var book = output.getBook();
-
- var result = encodeOutput(output);
- result.page = encodePage(page, book.getSummary());
- result.file = encodeFile(file);
-
- return result;
-}
-
-module.exports = encodeOutputWithPage;
diff --git a/lib/json/encodePage.js b/lib/json/encodePage.js
deleted file mode 100644
index be92117..0000000
--- a/lib/json/encodePage.js
+++ /dev/null
@@ -1,39 +0,0 @@
-var encodeSummaryArticle = require('./encodeSummaryArticle');
-
-/**
- Return a JSON representation of a page
-
- @param {Page} page
- @param {Summary} summary
- @return {Object}
-*/
-function encodePage(page, summary) {
- var file = page.getFile();
- var attributes = page.getAttributes();
- var article = summary.getByPath(file.getPath());
-
- var result = attributes.toJS();
-
- if (article) {
- result.title = article.getTitle();
- result.level = article.getLevel();
- result.depth = article.getDepth();
-
- var nextArticle = summary.getNextArticle(article);
- if (nextArticle) {
- result.next = encodeSummaryArticle(nextArticle);
- }
-
- var prevArticle = summary.getPrevArticle(article);
- if (prevArticle) {
- result.previous = encodeSummaryArticle(prevArticle);
- }
- }
-
- result.content = page.getContent();
- result.dir = page.getDir();
-
- return result;
-}
-
-module.exports = encodePage;
diff --git a/lib/json/encodeReadme.js b/lib/json/encodeReadme.js
deleted file mode 100644
index 96176a3..0000000
--- a/lib/json/encodeReadme.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var encodeFile = require('./encodeFile');
-
-/**
- Encode a readme to JSON
-
- @param {Readme}
- @return {Object}
-*/
-function encodeReadme(readme) {
- var file = readme.getFile();
-
- return {
- file: encodeFile(file)
- };
-}
-
-module.exports = encodeReadme;
diff --git a/lib/json/encodeSummary.js b/lib/json/encodeSummary.js
deleted file mode 100644
index 97db910..0000000
--- a/lib/json/encodeSummary.js
+++ /dev/null
@@ -1,20 +0,0 @@
-var encodeFile = require('./encodeFile');
-var encodeSummaryPart = require('./encodeSummaryPart');
-
-/**
- Encode a summary to JSON
-
- @param {Summary}
- @return {Object}
-*/
-function encodeSummary(summary) {
- var file = summary.getFile();
- var parts = summary.getParts();
-
- return {
- file: encodeFile(file),
- parts: parts.map(encodeSummaryPart).toJS()
- };
-}
-
-module.exports = encodeSummary;
diff --git a/lib/json/encodeSummaryArticle.js b/lib/json/encodeSummaryArticle.js
deleted file mode 100644
index 2fc5144..0000000
--- a/lib/json/encodeSummaryArticle.js
+++ /dev/null
@@ -1,28 +0,0 @@
-
-/**
- Encode a SummaryArticle to JSON
-
- @param {SummaryArticle}
- @return {Object}
-*/
-function encodeSummaryArticle(article, recursive) {
- var articles = undefined;
- if (recursive !== false) {
- articles = article.getArticles()
- .map(encodeSummaryArticle)
- .toJS();
- }
-
- return {
- title: article.getTitle(),
- level: article.getLevel(),
- depth: article.getDepth(),
- anchor: article.getAnchor(),
- url: article.getUrl(),
- path: article.getPath(),
- ref: article.getRef(),
- articles: articles
- };
-}
-
-module.exports = encodeSummaryArticle;
diff --git a/lib/json/encodeSummaryPart.js b/lib/json/encodeSummaryPart.js
deleted file mode 100644
index a5e7218..0000000
--- a/lib/json/encodeSummaryPart.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var encodeSummaryArticle = require('./encodeSummaryArticle');
-
-/**
- Encode a SummaryPart to JSON
-
- @param {SummaryPart}
- @return {Object}
-*/
-function encodeSummaryPart(part) {
- return {
- title: part.getTitle(),
- articles: part.getArticles()
- .map(encodeSummaryArticle).toJS()
- };
-}
-
-module.exports = encodeSummaryPart;
diff --git a/lib/json/index.js b/lib/json/index.js
deleted file mode 100644
index 3b68f5e..0000000
--- a/lib/json/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-
-module.exports = {
- encodeOutput: require('./encodeOutput'),
- encodeBookWithPage: require('./encodeBookWithPage'),
- encodeOutputWithPage: require('./encodeOutputWithPage'),
- encodeBook: require('./encodeBook'),
- encodeFile: require('./encodeFile'),
- encodePage: require('./encodePage'),
- encodeSummary: require('./encodeSummary'),
- encodeSummaryArticle: require('./encodeSummaryArticle'),
- encodeReadme: require('./encodeReadme'),
- encodeLanguages: require('./encodeLanguages')
-};
diff --git a/lib/models/__tests__/templateBlock.js b/lib/models/__tests__/templateBlock.js
deleted file mode 100644
index e5f7666..0000000
--- a/lib/models/__tests__/templateBlock.js
+++ /dev/null
@@ -1,205 +0,0 @@
-var nunjucks = require('nunjucks');
-var Immutable = require('immutable');
-var Promise = require('../../utils/promise');
-
-describe('TemplateBlock', function() {
- var TemplateBlock = require('../templateBlock');
-
- describe('create', function() {
- it('must initialize a simple TemplateBlock from a function', function() {
- var templateBlock = TemplateBlock.create('sayhello', function(block) {
- return {
- body: '<p>Hello, World!</p>',
- parse: true
- };
- });
-
- // Check basic templateBlock properties
- expect(templateBlock.getName()).toBe('sayhello');
- expect(templateBlock.getEndTag()).toBe('endsayhello');
- expect(templateBlock.getBlocks().size).toBe(0);
- expect(templateBlock.getExtensionName()).toBe('BlocksayhelloExtension');
-
- // Check result of applying block
- return Promise()
- .then(function() {
- return templateBlock.applyBlock();
- })
- .then(function(result) {
- expect(result.name).toBe('sayhello');
- expect(result.body).toBe('<p>Hello, World!</p>');
- });
- });
- });
-
- describe('getShortcuts', function() {
- it('must return undefined if no shortcuts', function() {
- var templateBlock = TemplateBlock.create('sayhello', function(block) {
- return {
- body: '<p>Hello, World!</p>',
- parse: true
- };
- });
-
- expect(templateBlock.getShortcuts()).toNotExist();
- });
-
- it('must return complete shortcut', function() {
- var templateBlock = TemplateBlock.create('sayhello', {
- process: function(block) {
- return '<p>Hello, World!</p>';
- },
- shortcuts: {
- parsers: ['markdown'],
- start: '$',
- end: '-'
- }
- });
-
- var shortcut = templateBlock.getShortcuts();
-
- expect(shortcut).toBeDefined();
- expect(shortcut.getStart()).toEqual('$');
- expect(shortcut.getEnd()).toEqual('-');
- expect(shortcut.getStartTag()).toEqual('sayhello');
- expect(shortcut.getEndTag()).toEqual('endsayhello');
- });
- });
-
- describe('toNunjucksExt()', function() {
- it('should replace by block anchor', function() {
- var templateBlock = TemplateBlock.create('sayhello', function(block) {
- return 'Hello';
- });
-
- var blocks = {};
-
- // Create a fresh Nunjucks environment
- var env = new nunjucks.Environment(null, { autoescape: false });
-
- // Add template block to environement
- var Ext = templateBlock.toNunjucksExt({}, blocks);
- env.addExtension(templateBlock.getExtensionName(), new Ext());
-
- // Render a template using the block
- var src = '{% sayhello %}{% endsayhello %}';
- return Promise.nfcall(env.renderString.bind(env), src)
- .then(function(res) {
- blocks = Immutable.fromJS(blocks);
- expect(blocks.size).toBe(1);
-
- var blockId = blocks.keySeq().get(0);
- var block = blocks.get(blockId);
-
- expect(res).toBe('{{-%' + blockId + '%-}}');
- expect(block.get('body')).toBe('Hello');
- expect(block.get('name')).toBe('sayhello');
- });
- });
-
- it('must create a valid nunjucks extension', function() {
- var templateBlock = TemplateBlock.create('sayhello', function(block) {
- return {
- body: '<p>Hello, World!</p>',
- parse: true
- };
- });
-
- // Create a fresh Nunjucks environment
- var env = new nunjucks.Environment(null, { autoescape: false });
-
- // Add template block to environement
- var Ext = templateBlock.toNunjucksExt();
- env.addExtension(templateBlock.getExtensionName(), new Ext());
-
- // Render a template using the block
- var src = '{% sayhello %}{% endsayhello %}';
- return Promise.nfcall(env.renderString.bind(env), src)
- .then(function(res) {
- expect(res).toBe('<p>Hello, World!</p>');
- });
- });
-
- it('must apply block arguments correctly', function() {
- var templateBlock = TemplateBlock.create('sayhello', function(block) {
- return {
- body: '<'+block.kwargs.tag+'>Hello, '+block.kwargs.name+'!</'+block.kwargs.tag+'>',
- parse: true
- };
- });
-
- // Create a fresh Nunjucks environment
- var env = new nunjucks.Environment(null, { autoescape: false });
-
- // Add template block to environement
- var Ext = templateBlock.toNunjucksExt();
- env.addExtension(templateBlock.getExtensionName(), new Ext());
-
- // Render a template using the block
- var src = '{% sayhello name="Samy", tag="p" %}{% endsayhello %}';
- return Promise.nfcall(env.renderString.bind(env), src)
- .then(function(res) {
- expect(res).toBe('<p>Hello, Samy!</p>');
- });
- });
-
- it('must accept an async function', function() {
- var templateBlock = TemplateBlock.create('sayhello', function(block) {
- return Promise()
- .then(function() {
- return {
- body: 'Hello ' + block.body,
- parse: true
- };
- });
- });
-
- // Create a fresh Nunjucks environment
- var env = new nunjucks.Environment(null, { autoescape: false });
-
- // Add template block to environement
- var Ext = templateBlock.toNunjucksExt();
- env.addExtension(templateBlock.getExtensionName(), new Ext());
-
- // Render a template using the block
- var src = '{% sayhello %}Samy{% endsayhello %}';
- return Promise.nfcall(env.renderString.bind(env), src)
- .then(function(res) {
- expect(res).toBe('Hello Samy');
- });
- });
-
- it('must handle nested blocks', function() {
- var templateBlock = new TemplateBlock({
- name: 'yoda',
- blocks: Immutable.List(['start', 'end']),
- process: function(block) {
- var nested = {};
-
- block.blocks.forEach(function(blk) {
- nested[blk.name] = blk.body.trim();
- });
-
- return {
- body: '<p class="yoda">'+nested.end+' '+nested.start+'</p>',
- parse: true
- };
- }
- });
-
- // Create a fresh Nunjucks environment
- var env = new nunjucks.Environment(null, { autoescape: false });
-
- // Add template block to environement
- var Ext = templateBlock.toNunjucksExt();
- env.addExtension(templateBlock.getExtensionName(), new Ext());
-
- // Render a template using the block
- var src = '{% yoda %}{% start %}this sentence should be{% end %}inverted{% endyoda %}';
- return Promise.nfcall(env.renderString.bind(env), src)
- .then(function(res) {
- expect(res).toBe('<p class="yoda">inverted this sentence should be</p>');
- });
- });
- });
-}); \ No newline at end of file
diff --git a/lib/models/book.js b/lib/models/book.js
deleted file mode 100644
index f774ee8..0000000
--- a/lib/models/book.js
+++ /dev/null
@@ -1,364 +0,0 @@
-var path = require('path');
-var Immutable = require('immutable');
-
-var Logger = require('../utils/logger');
-
-var FS = require('./fs');
-var Config = require('./config');
-var Readme = require('./readme');
-var Summary = require('./summary');
-var Glossary = require('./glossary');
-var Languages = require('./languages');
-var Ignore = require('./ignore');
-
-var Book = Immutable.Record({
- // Logger for outptu message
- logger: Logger(),
-
- // Filesystem binded to the book scope to read files/directories
- fs: FS(),
-
- // Ignore files parser
- ignore: Ignore(),
-
- // Structure files
- config: Config(),
- readme: Readme(),
- summary: Summary(),
- glossary: Glossary(),
- languages: Languages(),
-
- // ID of the language for language books
- language: String(),
-
- // List of children, if multilingual (String -> Book)
- books: Immutable.OrderedMap()
-});
-
-Book.prototype.getLogger = function() {
- return this.get('logger');
-};
-
-Book.prototype.getFS = function() {
- return this.get('fs');
-};
-
-Book.prototype.getIgnore = function() {
- return this.get('ignore');
-};
-
-Book.prototype.getConfig = function() {
- return this.get('config');
-};
-
-Book.prototype.getReadme = function() {
- return this.get('readme');
-};
-
-Book.prototype.getSummary = function() {
- return this.get('summary');
-};
-
-Book.prototype.getGlossary = function() {
- return this.get('glossary');
-};
-
-Book.prototype.getLanguages = function() {
- return this.get('languages');
-};
-
-Book.prototype.getBooks = function() {
- return this.get('books');
-};
-
-Book.prototype.getLanguage = function() {
- return this.get('language');
-};
-
-/**
- Return FS instance to access the content
-
- @return {FS}
-*/
-Book.prototype.getContentFS = function() {
- var fs = this.getFS();
- var config = this.getConfig();
- var rootFolder = config.getValue('root');
-
- if (rootFolder) {
- return FS.reduceScope(fs, rootFolder);
- }
-
- return fs;
-};
-
-/**
- Return root of the book
-
- @return {String}
-*/
-Book.prototype.getRoot = function() {
- var fs = this.getFS();
- return fs.getRoot();
-};
-
-/**
- Return root for content of the book
-
- @return {String}
-*/
-Book.prototype.getContentRoot = function() {
- var fs = this.getContentFS();
- return fs.getRoot();
-};
-
-/**
- Check if a file is ignore (should not being parsed, etc)
-
- @param {String} ref
- @return {Page|undefined}
-*/
-Book.prototype.isFileIgnored = function(filename) {
- var ignore = this.getIgnore();
- var language = this.getLanguage();
-
- // Ignore is always relative to the root of the main book
- if (language) {
- filename = path.join(language, filename);
- }
-
- return ignore.isFileIgnored(filename);
-};
-
-/**
- Check if a content file is ignore (should not being parsed, etc)
-
- @param {String} ref
- @return {Page|undefined}
-*/
-Book.prototype.isContentFileIgnored = function(filename) {
- var config = this.getConfig();
- var rootFolder = config.getValue('root');
-
- if (rootFolder) {
- filename = path.join(rootFolder, filename);
- }
-
- return this.isFileIgnored(filename);
-};
-
-/**
- Return a page from a book by its path
-
- @param {String} ref
- @return {Page|undefined}
-*/
-Book.prototype.getPage = function(ref) {
- return this.getPages().get(ref);
-};
-
-/**
- Is this book the parent of language's books
-
- @return {Boolean}
-*/
-Book.prototype.isMultilingual = function() {
- return (this.getLanguages().getCount() > 0);
-};
-
-/**
- Return true if book is associated to a language
-
- @return {Boolean}
-*/
-Book.prototype.isLanguageBook = function() {
- return Boolean(this.getLanguage());
-};
-
-/**
- Return a languages book
-
- @param {String} language
- @return {Book}
-*/
-Book.prototype.getLanguageBook = function(language) {
- var books = this.getBooks();
- return books.get(language);
-};
-
-/**
- Add a new language book
-
- @param {String} language
- @param {Book} book
- @return {Book}
-*/
-Book.prototype.addLanguageBook = function(language, book) {
- var books = this.getBooks();
- books = books.set(language, book);
-
- return this.set('books', books);
-};
-
-/**
- Set the summary for this book
-
- @param {Summary}
- @return {Book}
-*/
-Book.prototype.setSummary = function(summary) {
- return this.set('summary', summary);
-};
-
-/**
- Set the readme for this book
-
- @param {Readme}
- @return {Book}
-*/
-Book.prototype.setReadme = function(readme) {
- return this.set('readme', readme);
-};
-
-/**
- Set the configuration for this book
-
- @param {Config}
- @return {Book}
-*/
-Book.prototype.setConfig = function(config) {
- return this.set('config', config);
-};
-
-/**
- Set the ignore instance for this book
-
- @param {Ignore}
- @return {Book}
-*/
-Book.prototype.setIgnore = function(ignore) {
- return this.set('ignore', ignore);
-};
-
-/**
- Change log level
-
- @param {String} level
- @return {Book}
-*/
-Book.prototype.setLogLevel = function(level) {
- this.getLogger().setLevel(level);
- return this;
-};
-
-/**
- Create a book using a filesystem
-
- @param {FS} fs
- @return {Book}
-*/
-Book.createForFS = function createForFS(fs) {
- return new Book({
- fs: fs
- });
-};
-
-/**
- Infers the default extension for files
- @return {String}
-*/
-Book.prototype.getDefaultExt = function() {
- // Inferring sources
- var clues = [
- this.getReadme(),
- this.getSummary(),
- this.getGlossary()
- ];
-
- // List their extensions
- var exts = clues.map(function (clue) {
- var file = clue.getFile();
- if (file.exists()) {
- return file.getParser().getExtensions().first();
- } else {
- return null;
- }
- });
- // Adds the general default extension
- exts.push('.md');
-
- // Choose the first non null
- return exts.find(function (e) { return e !== null; });
-};
-
-/**
- Infer the default path for a Readme.
- @param {Boolean} [absolute=false] False for a path relative to
- this book's content root
- @return {String}
-*/
-Book.prototype.getDefaultReadmePath = function(absolute) {
- var defaultPath = 'README'+this.getDefaultExt();
- if (absolute) {
- return path.join(this.getContentRoot(), defaultPath);
- } else {
- return defaultPath;
- }
-};
-
-/**
- Infer the default path for a Summary.
- @param {Boolean} [absolute=false] False for a path relative to
- this book's content root
- @return {String}
-*/
-Book.prototype.getDefaultSummaryPath = function(absolute) {
- var defaultPath = 'SUMMARY'+this.getDefaultExt();
- if (absolute) {
- return path.join(this.getContentRoot(), defaultPath);
- } else {
- return defaultPath;
- }
-};
-
-/**
- Infer the default path for a Glossary.
- @param {Boolean} [absolute=false] False for a path relative to
- this book's content root
- @return {String}
-*/
-Book.prototype.getDefaultGlossaryPath = function(absolute) {
- var defaultPath = 'GLOSSARY'+this.getDefaultExt();
- if (absolute) {
- return path.join(this.getContentRoot(), defaultPath);
- } else {
- return defaultPath;
- }
-};
-
-/**
- Create a language book from a parent
-
- @param {Book} parent
- @param {String} language
- @return {Book}
-*/
-Book.createFromParent = function createFromParent(parent, language) {
- var ignore = parent.getIgnore();
- var config = parent.getConfig();
-
- // Set language in configuration
- config = config.setValue('language', language);
-
- return new Book({
- // Inherits config. logegr and list of ignored files
- logger: parent.getLogger(),
- config: config,
- ignore: ignore,
-
- language: language,
- fs: FS.reduceScope(parent.getContentFS(), language)
- });
-};
-
-module.exports = Book;
diff --git a/lib/models/ignore.js b/lib/models/ignore.js
deleted file mode 100644
index 499195e..0000000
--- a/lib/models/ignore.js
+++ /dev/null
@@ -1,42 +0,0 @@
-var Immutable = require('immutable');
-var IgnoreMutable = require('ignore');
-
-/*
- Immutable version of node-ignore
-*/
-var Ignore = Immutable.Record({
- ignore: new IgnoreMutable()
-}, 'Ignore');
-
-Ignore.prototype.getIgnore = function() {
- return this.get('ignore');
-};
-
-/**
- Test if a file is ignored by these rules
-
- @param {String} filePath
- @return {Boolean}
-*/
-Ignore.prototype.isFileIgnored = function(filename) {
- var ignore = this.getIgnore();
- return ignore.filter([filename]).length == 0;
-};
-
-/**
- Add rules
-
- @param {String}
- @return {Ignore}
-*/
-Ignore.prototype.add = function(rule) {
- var ignore = this.getIgnore();
- var newIgnore = new IgnoreMutable();
-
- newIgnore.add(ignore);
- newIgnore.add(rule);
-
- return this.set('ignore', newIgnore);
-};
-
-module.exports = Ignore;
diff --git a/lib/models/output.js b/lib/models/output.js
deleted file mode 100644
index 0f008ec..0000000
--- a/lib/models/output.js
+++ /dev/null
@@ -1,107 +0,0 @@
-var Immutable = require('immutable');
-
-var Book = require('./book');
-var LocationUtils = require('../utils/location');
-
-var Output = Immutable.Record({
- book: Book(),
-
- // Name of the generator being used
- generator: String(),
-
- // Map of plugins to use (String -> Plugin)
- plugins: Immutable.OrderedMap(),
-
- // Map pages to generation (String -> Page)
- pages: Immutable.OrderedMap(),
-
- // List assets (String)
- assets: Immutable.List(),
-
- // Option for the generation
- options: Immutable.Map(),
-
- // Internal state for the generation
- state: Immutable.Map()
-});
-
-Output.prototype.getBook = function() {
- return this.get('book');
-};
-
-Output.prototype.getGenerator = function() {
- return this.get('generator');
-};
-
-Output.prototype.getPlugins = function() {
- return this.get('plugins');
-};
-
-Output.prototype.getPages = function() {
- return this.get('pages');
-};
-
-Output.prototype.getOptions = function() {
- return this.get('options');
-};
-
-Output.prototype.getAssets = function() {
- return this.get('assets');
-};
-
-Output.prototype.getState = function() {
- return this.get('state');
-};
-
-/**
- Return a page byt its file path
-
- @param {String} filePath
- @return {Page|undefined}
-*/
-Output.prototype.getPage = function(filePath) {
- filePath = LocationUtils.normalize(filePath);
-
- var pages = this.getPages();
- return pages.get(filePath);
-};
-
-/**
- Get root folder for output
-
- @return {String}
-*/
-Output.prototype.getRoot = function() {
- return this.getOptions().get('root');
-};
-
-/**
- Update state of output
-
- @param {Map} newState
- @return {Output}
-*/
-Output.prototype.setState = function(newState) {
- return this.set('state', newState);
-};
-
-/**
- Update options
-
- @param {Map} newOptions
- @return {Output}
-*/
-Output.prototype.setOptions = function(newOptions) {
- return this.set('options', newOptions);
-};
-
-/**
- Return logegr for this output (same as book)
-
- @return {Logger}
-*/
-Output.prototype.getLogger = function() {
- return this.getBook().getLogger();
-};
-
-module.exports = Output;
diff --git a/lib/models/page.js b/lib/models/page.js
deleted file mode 100644
index 275a034..0000000
--- a/lib/models/page.js
+++ /dev/null
@@ -1,70 +0,0 @@
-var Immutable = require('immutable');
-var yaml = require('js-yaml');
-
-var File = require('./file');
-
-var Page = Immutable.Record({
- file: File(),
-
- // Attributes extracted from the YAML header
- attributes: Immutable.Map(),
-
- // Content of the page
- content: String(),
-
- // Direction of the text
- dir: String('ltr')
-});
-
-Page.prototype.getFile = function() {
- return this.get('file');
-};
-
-Page.prototype.getAttributes = function() {
- return this.get('attributes');
-};
-
-Page.prototype.getContent = function() {
- return this.get('content');
-};
-
-Page.prototype.getDir = function() {
- return this.get('dir');
-};
-
-/**
- * Return page as text
- * @return {String}
-*/
-Page.prototype.toText = function() {
- var attrs = this.getAttributes();
- var content = this.getContent();
-
- if (attrs.size === 0) {
- return content;
- }
-
- var frontMatter = '---\n' + yaml.safeDump(attrs.toJS(), { skipInvalid: true }) + '---\n\n';
- return (frontMatter + content);
-};
-
-/**
- * Return path of the page
- * @return {String}
-*/
-Page.prototype.getPath = function() {
- return this.getFile().getPath();
-};
-
-/**
- * Create a page for a file
- * @param {File} file
- * @return {Page}
-*/
-Page.createForFile = function(file) {
- return new Page({
- file: file
- });
-};
-
-module.exports = Page;
diff --git a/lib/models/plugin.js b/lib/models/plugin.js
deleted file mode 100644
index acabba9..0000000
--- a/lib/models/plugin.js
+++ /dev/null
@@ -1,169 +0,0 @@
-var Immutable = require('immutable');
-
-var TemplateBlock = require('./templateBlock');
-var PluginDependency = require('./pluginDependency');
-var THEME_PREFIX = require('../constants/themePrefix');
-
-var DEFAULT_VERSION = '*';
-
-var Plugin = Immutable.Record({
- name: String(),
-
- // Requirement version (ex: ">1.0.0")
- version: String(DEFAULT_VERSION),
-
- // Path to load this plugin
- path: String(),
-
- // Depth of this plugin in the dependency tree
- depth: Number(0),
-
- // Parent depending on this plugin
- parent: String(),
-
- // Content of the "package.json"
- package: Immutable.Map(),
-
- // Content of the package itself
- content: Immutable.Map()
-}, 'Plugin');
-
-Plugin.prototype.getName = function() {
- return this.get('name');
-};
-
-Plugin.prototype.getPath = function() {
- return this.get('path');
-};
-
-Plugin.prototype.getVersion = function() {
- return this.get('version');
-};
-
-Plugin.prototype.getPackage = function() {
- return this.get('package');
-};
-
-Plugin.prototype.getContent = function() {
- return this.get('content');
-};
-
-Plugin.prototype.getDepth = function() {
- return this.get('depth');
-};
-
-Plugin.prototype.getParent = function() {
- return this.get('parent');
-};
-
-/**
- * Return the ID on NPM for this plugin
- * @return {String}
- */
-Plugin.prototype.getNpmID = function() {
- return PluginDependency.nameToNpmID(this.getName());
-};
-
-/**
- * Check if a plugin is loaded
- * @return {Boolean}
- */
-Plugin.prototype.isLoaded = function() {
- return Boolean(this.getPackage().size > 0);
-};
-
-/**
- * Check if a plugin is a theme given its name
- * @return {Boolean}
- */
-Plugin.prototype.isTheme = function() {
- var name = this.getName();
- return (name && name.indexOf(THEME_PREFIX) === 0);
-};
-
-/**
- * Return map of hooks
- * @return {Map<String:Function>}
- */
-Plugin.prototype.getHooks = function() {
- return this.getContent().get('hooks') || Immutable.Map();
-};
-
-/**
- * Return infos about resources for a specific type
- * @param {String} type
- * @return {Map<String:Mixed>}
- */
-Plugin.prototype.getResources = function(type) {
- if (type != 'website' && type != 'ebook') {
- throw new Error('Invalid assets type ' + type);
- }
-
- var content = this.getContent();
- return (content.get(type)
- || (type == 'website'? content.get('book') : null)
- || Immutable.Map());
-};
-
-/**
- * Return map of filters
- * @return {Map<String:Function>}
- */
-Plugin.prototype.getFilters = function() {
- return this.getContent().get('filters');
-};
-
-/**
- * Return map of blocks
- * @return {Map<String:TemplateBlock>}
- */
-Plugin.prototype.getBlocks = function() {
- var blocks = this.getContent().get('blocks');
- blocks = blocks || Immutable.Map();
-
- return blocks
- .map(function(block, blockName) {
- return TemplateBlock.create(blockName, block);
- });
-};
-
-/**
- * Return a specific hook
- * @param {String} name
- * @return {Function|undefined}
- */
-Plugin.prototype.getHook = function(name) {
- return this.getHooks().get(name);
-};
-
-/**
- * Create a plugin from a string
- * @param {String}
- * @return {Plugin}
- */
-Plugin.createFromString = function(s) {
- var parts = s.split('@');
- var name = parts[0];
- var version = parts.slice(1).join('@');
-
- return new Plugin({
- name: name,
- version: version || DEFAULT_VERSION
- });
-};
-
-/**
- * Create a plugin from a dependency
- * @param {PluginDependency}
- * @return {Plugin}
- */
-Plugin.createFromDep = function(dep) {
- return new Plugin({
- name: dep.getName(),
- version: dep.getVersion()
- });
-};
-
-Plugin.nameToNpmID = PluginDependency.nameToNpmID;
-
-module.exports = Plugin;
diff --git a/lib/models/templateBlock.js b/lib/models/templateBlock.js
deleted file mode 100644
index 458f084..0000000
--- a/lib/models/templateBlock.js
+++ /dev/null
@@ -1,281 +0,0 @@
-var is = require('is');
-var extend = require('extend');
-var Immutable = require('immutable');
-
-var Promise = require('../utils/promise');
-var genKey = require('../utils/genKey');
-var TemplateShortcut = require('./templateShortcut');
-
-var NODE_ENDARGS = '%%endargs%%';
-
-var TemplateBlock = Immutable.Record({
- // Name of block, also the start tag
- name: String(),
-
- // End tag, default to "end<name>"
- end: String(),
-
- // Function to process the block content
- process: Function(),
-
- // List of String, for inner block tags
- blocks: Immutable.List(),
-
- // List of shortcuts to replace with this block
- shortcuts: Immutable.Map()
-}, 'TemplateBlock');
-
-TemplateBlock.prototype.getName = function() {
- return this.get('name');
-};
-
-TemplateBlock.prototype.getEndTag = function() {
- return this.get('end') || ('end' + this.getName());
-};
-
-TemplateBlock.prototype.getProcess = function() {
- return this.get('process');
-};
-
-TemplateBlock.prototype.getBlocks = function() {
- return this.get('blocks');
-};
-
-
-/**
- * Return shortcuts associated with this block or undefined
- * @return {TemplateShortcut|undefined}
- */
-TemplateBlock.prototype.getShortcuts = function() {
- var shortcuts = this.get('shortcuts');
- if (shortcuts.size === 0) {
- return undefined;
- }
-
- return TemplateShortcut.createForBlock(this, shortcuts);
-};
-
-/**
- * Return name for the nunjucks extension
- * @return {String}
- */
-TemplateBlock.prototype.getExtensionName = function() {
- return 'Block' + this.getName() + 'Extension';
-};
-
-/**
- * Return a nunjucks extension to represents this block
- * @return {Nunjucks.Extension}
- */
-TemplateBlock.prototype.toNunjucksExt = function(mainContext, blocksOutput) {
- blocksOutput = blocksOutput || {};
-
- var that = this;
- var name = this.getName();
- var endTag = this.getEndTag();
- var blocks = this.getBlocks().toJS();
-
- function Ext() {
- this.tags = [name];
-
- this.parse = function(parser, nodes) {
- var lastBlockName = null;
- var lastBlockArgs = null;
- var allBlocks = blocks.concat([endTag]);
-
- // Parse first block
- var tok = parser.nextToken();
- lastBlockArgs = parser.parseSignature(null, true);
- parser.advanceAfterBlockEnd(tok.value);
-
- var args = new nodes.NodeList();
- var bodies = [];
- var blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
- var blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
-
- // Parse while we found "end<block>"
- do {
- // Read body
- var currentBody = parser.parseUntilBlocks.apply(parser, allBlocks);
-
- // Handle body with previous block name and args
- blockNamesNode.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockName));
- blockArgCounts.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockArgs.children.length));
- bodies.push(currentBody);
-
- // Append arguments of this block as arguments of the run function
- lastBlockArgs.children.forEach(function(child) {
- args.addChild(child);
- });
-
- // Read new block
- lastBlockName = parser.nextToken().value;
-
- // Parse signature and move to the end of the block
- if (lastBlockName != endTag) {
- lastBlockArgs = parser.parseSignature(null, true);
- }
-
- parser.advanceAfterBlockEnd(lastBlockName);
- } while (lastBlockName != endTag);
-
- args.addChild(blockNamesNode);
- args.addChild(blockArgCounts);
- args.addChild(new nodes.Literal(args.lineno, args.colno, NODE_ENDARGS));
-
- return new nodes.CallExtensionAsync(this, 'run', args, bodies);
- };
-
- this.run = function(context) {
- var fnArgs = Array.prototype.slice.call(arguments, 1);
-
- var args;
- var blocks = [];
- var bodies = [];
- var blockNames;
- var blockArgCounts;
- var callback;
-
- // Extract callback
- callback = fnArgs.pop();
-
- // Detect end of arguments
- var endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
-
- // Extract arguments and bodies
- args = fnArgs.slice(0, endArgIndex);
- bodies = fnArgs.slice(endArgIndex + 1);
-
- // Extract block counts
- blockArgCounts = args.pop();
- blockNames = args.pop();
-
- // Recreate list of blocks
- blockNames.forEach(function(name, i) {
- var countArgs = blockArgCounts[i];
- var blockBody = bodies.shift();
-
- var blockArgs = countArgs > 0? args.slice(0, countArgs) : [];
- args = args.slice(countArgs);
- var blockKwargs = extractKwargs(blockArgs);
-
- blocks.push({
- name: name,
- body: blockBody(),
- args: blockArgs,
- kwargs: blockKwargs
- });
- });
-
- var mainBlock = blocks.shift();
- mainBlock.blocks = blocks;
-
- Promise()
- .then(function() {
- var ctx = extend({
- ctx: context
- }, mainContext || {});
-
- return that.applyBlock(mainBlock, ctx);
- })
- .then(function(result) {
- return that.blockResultToHtml(result, blocksOutput);
- })
- .nodeify(callback);
- };
- }
-
- return Ext;
-};
-
-/**
- * Apply a block to a content
- * @param {Object} inner
- * @param {Object} context
- * @return {Promise<String>|String}
- */
-TemplateBlock.prototype.applyBlock = function(inner, context) {
- var processFn = this.getProcess();
-
- inner = inner || {};
- inner.args = inner.args || [];
- inner.kwargs = inner.kwargs || {};
- inner.blocks = inner.blocks || [];
-
- var r = processFn.call(context, inner);
-
- if (Promise.isPromiseAlike(r)) {
- return r.then(this.normalizeBlockResult.bind(this));
- } else {
- return this.normalizeBlockResult(r);
- }
-};
-
-/**
- * Normalize result from a block process function
- * @param {Object|String} result
- * @return {Object}
- */
-TemplateBlock.prototype.normalizeBlockResult = function(result) {
- if (is.string(result)) {
- result = { body: result };
- }
- result.name = this.getName();
-
- return result;
-};
-
-/**
- * Convert a block result to HTML
- * @param {Object} result
- * @param {Object} blocksOutput: stored post processing blocks in this object
- * @return {String}
- */
-TemplateBlock.prototype.blockResultToHtml = function(result, blocksOutput) {
- var indexedKey;
- var toIndex = (!result.parse) || (result.post !== undefined);
-
- if (toIndex) {
- indexedKey = genKey();
- blocksOutput[indexedKey] = result;
- }
-
- // Parsable block, just return it
- if (result.parse) {
- return result.body;
- }
-
- // Return it as a position marker
- return '{{-%' + indexedKey + '%-}}';
-
-};
-
-/**
- * Create a template block from a function or an object
- * @param {String} blockName
- * @param {Object} block
- * @return {TemplateBlock}
- */
-TemplateBlock.create = function(blockName, block) {
- if (is.fn(block)) {
- block = new Immutable.Map({
- process: block
- });
- }
-
- block = new TemplateBlock(block);
- block = block.set('name', blockName);
- return block;
-};
-
-/**
- * Extract kwargs from an arguments array
- * @param {Array} args
- * @return {Object}
- */
-function extractKwargs(args) {
- var last = args[args.length - 1];
- return (is.object(last) && last.__keywords)? args.pop() : {};
-}
-
-module.exports = TemplateBlock;
diff --git a/lib/models/templateEngine.js b/lib/models/templateEngine.js
deleted file mode 100644
index 5724d55..0000000
--- a/lib/models/templateEngine.js
+++ /dev/null
@@ -1,139 +0,0 @@
-var nunjucks = require('nunjucks');
-var Immutable = require('immutable');
-
-var TemplateEngine = Immutable.Record({
- // Map of {TemplateBlock}
- blocks: Immutable.Map(),
-
- // Map of Extension
- extensions: Immutable.Map(),
-
- // Map of filters: {String} name -> {Function} fn
- filters: Immutable.Map(),
-
- // Map of globals: {String} name -> {Mixed}
- globals: Immutable.Map(),
-
- // Context for filters / blocks
- context: Object(),
-
- // Nunjucks loader
- loader: nunjucks.FileSystemLoader('views')
-}, 'TemplateEngine');
-
-TemplateEngine.prototype.getBlocks = function() {
- return this.get('blocks');
-};
-
-TemplateEngine.prototype.getGlobals = function() {
- return this.get('globals');
-};
-
-TemplateEngine.prototype.getFilters = function() {
- return this.get('filters');
-};
-
-TemplateEngine.prototype.getShortcuts = function() {
- return this.get('shortcuts');
-};
-
-TemplateEngine.prototype.getLoader = function() {
- return this.get('loader');
-};
-
-TemplateEngine.prototype.getContext = function() {
- return this.get('context');
-};
-
-TemplateEngine.prototype.getExtensions = function() {
- return this.get('extensions');
-};
-
-/**
- Return a block by its name (or undefined)
-
- @param {String} name
- @return {TemplateBlock}
-*/
-TemplateEngine.prototype.getBlock = function(name) {
- var blocks = this.getBlocks();
- return blocks.find(function(block) {
- return block.getName() === name;
- });
-};
-
-/**
- Return a nunjucks environment from this configuration
-
- @return {Nunjucks.Environment}
-*/
-TemplateEngine.prototype.toNunjucks = function(blocksOutput) {
- var loader = this.getLoader();
- var blocks = this.getBlocks();
- var filters = this.getFilters();
- var globals = this.getGlobals();
- var extensions = this.getExtensions();
- var context = this.getContext();
-
- var env = new nunjucks.Environment(
- loader,
- {
- // Escaping is done after by the asciidoc/markdown parser
- autoescape: false,
-
- // Syntax
- tags: {
- blockStart: '{%',
- blockEnd: '%}',
- variableStart: '{{',
- variableEnd: '}}',
- commentStart: '{###',
- commentEnd: '###}'
- }
- }
- );
-
- // Add filters
- filters.forEach(function(filterFn, filterName) {
- env.addFilter(filterName, filterFn.bind(context));
- });
-
- // Add blocks
- blocks.forEach(function(block) {
- var extName = block.getExtensionName();
- var Ext = block.toNunjucksExt(context, blocksOutput);
-
- env.addExtension(extName, new Ext());
- });
-
- // Add globals
- globals.forEach(function(globalValue, globalName) {
- env.addGlobal(globalName, globalValue);
- });
-
- // Add other extensions
- extensions.forEach(function(ext, extName) {
- env.addExtension(extName, ext);
- });
-
- return env;
-};
-
-/**
- Create a template engine
-
- @param {Object} def
- @return {TemplateEngine}
-*/
-TemplateEngine.create = function(def) {
- return new TemplateEngine({
- blocks: Immutable.List(def.blocks || []),
- extensions: Immutable.Map(def.extensions || {}),
- filters: Immutable.Map(def.filters || {}),
- globals: Immutable.Map(def.globals || {}),
- context: def.context,
- loader: def.loader
- });
-};
-
-module.exports = TemplateEngine;
diff --git a/lib/models/templateOutput.js b/lib/models/templateOutput.js
deleted file mode 100644
index ae63c06..0000000
--- a/lib/models/templateOutput.js
+++ /dev/null
@@ -1,42 +0,0 @@
-var Immutable = require('immutable');
-
-var TemplateOutput = Immutable.Record({
- // Text content of the template
- content: String(),
-
- // Map of blocks to replace / post process
- blocks: Immutable.Map()
-}, 'TemplateOutput');
-
-TemplateOutput.prototype.getContent = function() {
- return this.get('content');
-};
-
-TemplateOutput.prototype.getBlocks = function() {
- return this.get('blocks');
-};
-
-/**
- * Update content of this output
- * @param {String} content
- * @return {TemplateContent}
- */
-TemplateOutput.prototype.setContent = function(content) {
- return this.set('content', content);
-};
-
-/**
- * Create a TemplateOutput from a text content
- * and an object containing block definition
- * @param {String} content
- * @param {Object} blocks
- * @return {TemplateOutput}
- */
-TemplateOutput.create = function(content, blocks) {
- return new TemplateOutput({
- content: content,
- blocks: Immutable.fromJS(blocks)
- });
-};
-
-module.exports = TemplateOutput;
diff --git a/lib/output/createTemplateEngine.js b/lib/output/createTemplateEngine.js
deleted file mode 100644
index 8cf320e..0000000
--- a/lib/output/createTemplateEngine.js
+++ /dev/null
@@ -1,45 +0,0 @@
-var Templating = require('../templating');
-var TemplateEngine = require('../models/templateEngine');
-
-var Api = require('../api');
-var Plugins = require('../plugins');
-
-var defaultBlocks = require('../constants/defaultBlocks');
-var defaultFilters = require('../constants/defaultFilters');
-
-/**
- Create template engine for an output.
- It adds default filters/blocks, then add the ones from plugins
-
- @param {Output} output
- @return {TemplateEngine}
-*/
-function createTemplateEngine(output) {
- var plugins = output.getPlugins();
- var book = output.getBook();
- var rootFolder = book.getContentRoot();
- var logger = book.getLogger();
-
- var filters = Plugins.listFilters(plugins);
- var blocks = Plugins.listBlocks(plugins);
-
- // Extend with default
- blocks = defaultBlocks.merge(blocks);
- filters = defaultFilters.merge(filters);
-
- // Create loader
- var transformFn = Templating.replaceShortcuts.bind(null, blocks);
- var loader = new Templating.ConrefsLoader(rootFolder, transformFn, logger);
-
- // Create API context
- var context = Api.encodeGlobal(output);
-
- return new TemplateEngine({
- filters: filters,
- blocks: blocks,
- loader: loader,
- context: context
- });
-}
-
-module.exports = createTemplateEngine;
diff --git a/lib/output/ebook/getPDFTemplate.js b/lib/output/ebook/getPDFTemplate.js
deleted file mode 100644
index b767daf..0000000
--- a/lib/output/ebook/getPDFTemplate.js
+++ /dev/null
@@ -1,41 +0,0 @@
-var juice = require('juice');
-
-var WebsiteGenerator = require('../website');
-var JSONUtils = require('../../json');
-var Templating = require('../../templating');
-var Promise = require('../../utils/promise');
-
-
-/**
- Generate PDF header/footer templates
-
- @param {Output} output
- @param {String} type
- @return {String}
-*/
-function getPDFTemplate(output, type) {
- var filePath = 'pdf_' + type + '.html';
- var outputRoot = output.getRoot();
- var engine = WebsiteGenerator.createTemplateEngine(output, filePath);
-
- // Generate context
- var context = JSONUtils.encodeOutput(output);
- context.page = {
- num: '_PAGENUM_',
- title: '_SECTION_'
- };
-
- // Render the theme
- return Templating.renderFile(engine, 'ebook/' + filePath, context)
-
- // Inline css and assets
- .then(function(tplOut) {
- return Promise.nfcall(juice.juiceResources, tplOut.getContent(), {
- webResources: {
- relativeTo: outputRoot
- }
- });
- });
-}
-
-module.exports = getPDFTemplate;
diff --git a/lib/output/ebook/onFinish.js b/lib/output/ebook/onFinish.js
deleted file mode 100644
index 7f21548..0000000
--- a/lib/output/ebook/onFinish.js
+++ /dev/null
@@ -1,91 +0,0 @@
-var path = require('path');
-
-var WebsiteGenerator = require('../website');
-var JSONUtils = require('../../json');
-var Templating = require('../../templating');
-var Promise = require('../../utils/promise');
-var error = require('../../utils/error');
-var command = require('../../utils/command');
-var writeFile = require('../helper/writeFile');
-
-var getConvertOptions = require('./getConvertOptions');
-var SUMMARY_FILE = 'SUMMARY.html';
-
-/**
- Write the SUMMARY.html
-
- @param {Output}
- @return {Output}
-*/
-function writeSummary(output) {
- var options = output.getOptions();
- var prefix = options.get('prefix');
-
- var filePath = SUMMARY_FILE;
- var engine = WebsiteGenerator.createTemplateEngine(output, filePath);
- var context = JSONUtils.encodeOutput(output);
-
- // Render the theme
- return Templating.renderFile(engine, prefix + '/summary.html', context)
-
- // Write it to the disk
- .then(function(tplOut) {
- return writeFile(output, filePath, tplOut.getContent());
- });
-}
-
-/**
- Generate the ebook file as "index.pdf"
-
- @param {Output}
- @return {Output}
-*/
-function runEbookConvert(output) {
- var logger = output.getLogger();
- var options = output.getOptions();
- var format = options.get('format');
- var outputFolder = output.getRoot();
-
- if (!format) {
- return Promise(output);
- }
-
- return getConvertOptions(output)
- .then(function(options) {
- var cmd = [
- 'ebook-convert',
- path.resolve(outputFolder, SUMMARY_FILE),
- path.resolve(outputFolder, 'index.' + format),
- command.optionsToShellArgs(options)
- ].join(' ');
-
- return command.exec(cmd)
- .progress(function(data) {
- logger.debug(data);
- })
- .fail(function(err) {
- if (err.code == 127) {
- throw error.RequireInstallError({
- cmd: 'ebook-convert',
- install: 'Install it from Calibre: https://calibre-ebook.com'
- });
- }
-
- throw error.EbookError(err);
- });
- })
- .thenResolve(output);
-}
-
-/**
- Finish the generation, generates the SUMMARY.html
-
- @param {Output}
- @return {Output}
-*/
-function onFinish(output) {
- return writeSummary(output)
- .then(runEbookConvert);
-}
-
-module.exports = onFinish;
diff --git a/lib/output/ebook/options.js b/lib/output/ebook/options.js
deleted file mode 100644
index ea7b8b4..0000000
--- a/lib/output/ebook/options.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var Immutable = require('immutable');
-
-var Options = Immutable.Record({
- // Root folder for the output
- root: String(),
-
- // Prefix for generation
- prefix: String('ebook'),
-
- // Format to generate using ebook-convert
- format: String(),
-
- // Force use of absolute urls ("index.html" instead of "/")
- directoryIndex: Boolean(false)
-});
-
-module.exports = Options;
diff --git a/lib/output/generatePage.js b/lib/output/generatePage.js
deleted file mode 100644
index 090a870..0000000
--- a/lib/output/generatePage.js
+++ /dev/null
@@ -1,79 +0,0 @@
-var path = require('path');
-
-var Promise = require('../utils/promise');
-var error = require('../utils/error');
-var timing = require('../utils/timing');
-
-var Templating = require('../templating');
-var JSONUtils = require('../json');
-var createTemplateEngine = require('./createTemplateEngine');
-var callPageHook = require('./callPageHook');
-
-/**
- * Prepare and generate HTML for a page
- *
- * @param {Output} output
- * @param {Page} page
- * @return {Promise<Page>}
- */
-function generatePage(output, page) {
- var book = output.getBook();
- var engine = createTemplateEngine(output);
-
- return timing.measure(
- 'page.generate',
- Promise(page)
- .then(function(resultPage) {
- var file = resultPage.getFile();
- var filePath = file.getPath();
- var parser = file.getParser();
- var context = JSONUtils.encodeOutputWithPage(output, resultPage);
-
- if (!parser) {
- return Promise.reject(error.FileNotParsableError({
- filename: filePath
- }));
- }
-
- // Call hook "page:before"
- return callPageHook('page:before', output, resultPage)
-
- // Escape code blocks with raw tags
- .then(function(currentPage) {
- return parser.preparePage(currentPage.getContent());
- })
-
- // Render templating syntax
- .then(function(content) {
- var absoluteFilePath = path.join(book.getContentRoot(), filePath);
- return Templating.render(engine, absoluteFilePath, content, context);
- })
-
- .then(function(output) {
- var content = output.getContent();
-
- return parser.parsePage(content)
- .then(function(result) {
- return output.setContent(result.content);
- });
- })
-
- // Post processing for templating syntax
- .then(function(output) {
- return Templating.postRender(engine, output);
- })
-
- // Return new page
- .then(function(content) {
- return resultPage.set('content', content);
- })
-
- // Call final hook
- .then(function(currentPage) {
- return callPageHook('page', output, currentPage);
- });
- })
- );
-}
-
-module.exports = generatePage;
diff --git a/lib/output/getModifiers.js b/lib/output/getModifiers.js
deleted file mode 100644
index bb44e80..0000000
--- a/lib/output/getModifiers.js
+++ /dev/null
@@ -1,73 +0,0 @@
-var Modifiers = require('./modifiers');
-var resolveFileToURL = require('./helper/resolveFileToURL');
-var Api = require('../api');
-var Plugins = require('../plugins');
-var Promise = require('../utils/promise');
-var defaultBlocks = require('../constants/defaultBlocks');
-var fileToOutput = require('./helper/fileToOutput');
-
-var CODEBLOCK = 'code';
-
-/**
- * Return default modifier to prepare a page for
- * rendering.
- *
- * @return {Array<Modifier>}
- */
-function getModifiers(output, page) {
- var book = output.getBook();
- var plugins = output.getPlugins();
- var glossary = book.getGlossary();
- var file = page.getFile();
-
- // Glossary entries
- var entries = glossary.getEntries();
- var glossaryFile = glossary.getFile();
- var glossaryFilename = fileToOutput(output, glossaryFile.getPath());
-
- // Current file path
- var currentFilePath = file.getPath();
-
- // Get TemplateBlock for highlighting
- var blocks = Plugins.listBlocks(plugins);
- var code = blocks.get(CODEBLOCK) || defaultBlocks.get(CODEBLOCK);
-
- // Current context
- var context = Api.encodeGlobal(output);
-
- return [
- // Normalize IDs on headings
- Modifiers.addHeadingId,
-
- // Annotate text with glossary entries
- Modifiers.annotateText.bind(null, entries, glossaryFilename),
-
- // Resolve images
- Modifiers.resolveImages.bind(null, currentFilePath),
-
- // Resolve links (.md -> .html)
- Modifiers.resolveLinks.bind(null,
- currentFilePath,
- resolveFileToURL.bind(null, output)
- ),
-
- // Highlight code blocks using "code" block
- Modifiers.highlightCode.bind(null, function(lang, source) {
- return Promise(code.applyBlock({
- body: source,
- kwargs: {
- language: lang
- }
- }, context))
- .then(function(result) {
- if (result.html === false) {
- return { text: result.body };
- } else {
- return { html: result.body };
- }
- });
- })
- ];
-}
-
-module.exports = getModifiers;
diff --git a/lib/output/helper/fileToOutput.js b/lib/output/helper/fileToOutput.js
deleted file mode 100644
index 361c6eb..0000000
--- a/lib/output/helper/fileToOutput.js
+++ /dev/null
@@ -1,32 +0,0 @@
-var path = require('path');
-
-var PathUtils = require('../../utils/path');
-var LocationUtils = require('../../utils/location');
-
-var OUTPUT_EXTENSION = '.html';
-
-/**
- * Convert a filePath (absolute) to a filename for output
- *
- * @param {Output} output
- * @param {String} filePath
- * @return {String}
- */
-function fileToOutput(output, filePath) {
- var book = output.getBook();
- var readme = book.getReadme();
- var fileReadme = readme.getFile();
-
- if (
- path.basename(filePath, path.extname(filePath)) == 'README' ||
- (fileReadme.exists() && filePath == fileReadme.getPath())
- ) {
- filePath = path.join(path.dirname(filePath), 'index' + OUTPUT_EXTENSION);
- } else {
- filePath = PathUtils.setExtension(filePath, OUTPUT_EXTENSION);
- }
-
- return LocationUtils.normalize(filePath);
-}
-
-module.exports = fileToOutput;
diff --git a/lib/output/helper/fileToURL.js b/lib/output/helper/fileToURL.js
deleted file mode 100644
index 44ad2d8..0000000
--- a/lib/output/helper/fileToURL.js
+++ /dev/null
@@ -1,31 +0,0 @@
-var path = require('path');
-var LocationUtils = require('../../utils/location');
-
-var fileToOutput = require('./fileToOutput');
-
-/**
- Convert a filePath (absolute) to an url (without hostname).
- It returns an absolute path.
-
- "README.md" -> "/"
- "test/hello.md" -> "test/hello.html"
- "test/README.md" -> "test/"
-
- @param {Output} output
- @param {String} filePath
- @return {String}
-*/
-function fileToURL(output, filePath) {
- var options = output.getOptions();
- var directoryIndex = options.get('directoryIndex');
-
- filePath = fileToOutput(output, filePath);
-
- if (directoryIndex && path.basename(filePath) == 'index.html') {
- filePath = path.dirname(filePath) + '/';
- }
-
- return LocationUtils.normalize(filePath);
-}
-
-module.exports = fileToURL;
diff --git a/lib/output/helper/resolveFileToURL.js b/lib/output/helper/resolveFileToURL.js
deleted file mode 100644
index 3f52713..0000000
--- a/lib/output/helper/resolveFileToURL.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var LocationUtils = require('../../utils/location');
-
-var fileToURL = require('./fileToURL');
-
-/**
- * Resolve an absolute path (extracted from a link)
- *
- * @param {Output} output
- * @param {String} filePath
- * @return {String}
- */
-function resolveFileToURL(output, filePath) {
- // Convert /test.png -> test.png
- filePath = LocationUtils.toAbsolute(filePath, '', '');
-
- var page = output.getPage(filePath);
-
- // if file is a page, return correct .html url
- if (page) {
- filePath = fileToURL(output, filePath);
- }
-
- return LocationUtils.normalize(filePath);
-}
-
-module.exports = resolveFileToURL;
diff --git a/lib/output/modifiers/__tests__/fetchRemoteImages.js b/lib/output/modifiers/__tests__/fetchRemoteImages.js
deleted file mode 100644
index bc1704d..0000000
--- a/lib/output/modifiers/__tests__/fetchRemoteImages.js
+++ /dev/null
@@ -1,40 +0,0 @@
-var cheerio = require('cheerio');
-var tmp = require('tmp');
-var path = require('path');
-
-var URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png';
-
-describe('fetchRemoteImages', function() {
- var dir;
- var fetchRemoteImages = require('../fetchRemoteImages');
-
- beforeEach(function() {
- dir = tmp.dirSync();
- });
-
- it('should download image file', function() {
- var $ = cheerio.load('<img src="' + URL + '" />');
-
- return fetchRemoteImages(dir.name, 'index.html', $)
- .then(function() {
- var $img = $('img');
- var src = $img.attr('src');
-
- expect(dir.name).toHaveFile(src);
- });
- });
-
- it('should download image file and replace with relative path', function() {
- var $ = cheerio.load('<img src="' + URL + '" />');
-
- return fetchRemoteImages(dir.name, 'test/index.html', $)
- .then(function() {
- var $img = $('img');
- var src = $img.attr('src');
-
- expect(dir.name).toHaveFile(path.join('test', src));
- });
- });
-});
-
-
diff --git a/lib/output/modifiers/__tests__/highlightCode.js b/lib/output/modifiers/__tests__/highlightCode.js
deleted file mode 100644
index 75d9902..0000000
--- a/lib/output/modifiers/__tests__/highlightCode.js
+++ /dev/null
@@ -1,60 +0,0 @@
-var cheerio = require('cheerio');
-var Promise = require('../../../utils/promise');
-var highlightCode = require('../highlightCode');
-
-describe('highlightCode', function() {
- function doHighlight(lang, code) {
- return {
- text: '' + (lang || '') + '$' + code
- };
- }
-
- function doHighlightAsync(lang, code) {
- return Promise()
- .then(function() {
- return doHighlight(lang, code);
- });
- }
-
- it('should call it for normal code element', function() {
- var $ = cheerio.load('<p>This is a <code>test</code></p>');
-
- return highlightCode(doHighlight, $)
- .then(function() {
- var $code = $('code');
- expect($code.text()).toBe('$test');
- });
- });
-
- it('should call it for markdown code block', function() {
- var $ = cheerio.load('<pre><code class="lang-js">test</code></pre>');
-
- return highlightCode(doHighlight, $)
- .then(function() {
- var $code = $('code');
- expect($code.text()).toBe('js$test');
- });
- });
-
- it('should call it for asciidoc code block', function() {
- var $ = cheerio.load('<pre><code class="language-python">test</code></pre>');
-
- return highlightCode(doHighlight, $)
- .then(function() {
- var $code = $('code');
- expect($code.text()).toBe('python$test');
- });
- });
-
- it('should accept async highlighter', function() {
- var $ = cheerio.load('<pre><code class="language-python">test</code></pre>');
-
- return highlightCode(doHighlightAsync, $)
- .then(function() {
- var $code = $('code');
- expect($code.text()).toBe('python$test');
- });
- });
-});
-
-
diff --git a/lib/output/modifiers/__tests__/inlinePng.js b/lib/output/modifiers/__tests__/inlinePng.js
deleted file mode 100644
index 0073cff..0000000
--- a/lib/output/modifiers/__tests__/inlinePng.js
+++ /dev/null
@@ -1,25 +0,0 @@
-var cheerio = require('cheerio');
-var tmp = require('tmp');
-var inlinePng = require('../inlinePng');
-
-describe('inlinePng', function() {
- var dir;
-
- beforeEach(function() {
- dir = tmp.dirSync();
- });
-
- it('should write an inline PNG using data URI as a file', function() {
- var $ = cheerio.load('<img alt="GitBook Logo 20x20" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUEAYAAADdGcFOAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAF+klEQVRIDY3Wf5CVVR3H8c9z791fyI9dQwdQ4TTI7wEWnQZZAa/mJE4Z0OaKUuN1KoaykZxUGGHay+iIVFMoEYrUPhDCKEKW2ChT8dA0RCSxWi6EW3sYYpcfxq5C+4O9957O+7m7O/qHQ9/XzH1+nHuec57z8wkWTsKw0y6N/LxXN6KzTnEUHi8eP/l3YStSU/MdsYvBbGh8six2YXcbcgc++QkfTQkWz/81KtqDA0hlUoWnsX+5uxe5X365BB9my2bjrHNHccLk16BpS9CExjcmXMDbD6wehdyEjxbjz1uK1zn9qga6dcfnMLXeXY/qjuQqTF4W1MKke8ZgeNhjMCxMPIWSd4OF78C55CFI/1kF6WwXpMqjkAZ/CKniNDrCsmU4lE1YbPlgR2x7R39FF23D4mq3A1+Z35PGTNs1E1XhxcGQOh6HNPwXkK56BVJhOaRg/pvoHXNxHFw410B25EYE2RMvI0i/twFJvXcrFObykEa+DmnQGLwYqR0l2a6JqItaj8C/4E2QxtZCofkC8tF1t8HZc/fAZaLnIF2xEsoEtW1w7vBSSFtfhDTnCki9cSi81Ain1uko2Ld+Dmf2rkUq0/5t+PYbFtPQdkjzNiAXTWtDEF49FgkzJInAVPwNyhzcDOmrdZCm/Rn+ebWtcPs+/U24hmg2XL0rRkPPELh9R8fDtXR2oC/VuZbGaci79Ajkb6lZgfyYtyzy/X9s6T/pO/ZfN/RdNxxIwTWM2wbX8KVmuIaEqmKm6zEondwGpd0SyOy5DrJ//TFkX9kMhd3XQHbEVCSsm4OECV5HIv2p15CwfWPSntoHRbv2Q1HzSvSlSqZwATIuBxk/zZBOBbdB+u9hSKU3Q7pwAjInZkFm6U8hu7MSMqe/Dqn8fUj5GVCmpxK+4N/F1LMa0p5eSOPqIPP7NGSunAI/+R6GnzQzIBt8A1LC/QZ+6HwLst1rITv0n5CtXgSZ78yFTNkR+FdeDZneJkip3fAtsQ5Scilkek7CH9dAmjIWvkK7IXXOh6/IzZDNPQdZXR1TQmdjKv0ZfEu0YKDpNflpyG5aDtnRv8VAuu3dBV+huyBbvgdS97tQNLQc0mfugKy5Cb4BipPIXvsUpK5N8Mvao/Bd3QDZRH9Rrtj3Cl6FHwPFMLmNkKrj8BnHoT+XX6f2wl+XxFS4Ab7C72Dgf7bi+5DpTkNm8kQMpCs/BzIlz8LfPxnzLdh3EjwMX4GX4Ju4GNb9A1L7k/D3J8b6kv2LFCtmCmcgUzoJsr2z4MfwFsh87xikZefg188fYaAhpPUxm3ge/vFnYkoED0HqeQiyJYcwkNGWnoNv6s9C1p1Bf/389VYoCjohW7UfMms3wXdpBv7+FEiPLIHs4DIMNERUNhbSpY3wk6QOsqlCDVx2xCrInMpBmfNPQOnzKxBkkrugdOl9GKigSZZCUWIm/GqwDtLUI5D+WAOlb9wKP0YvQLbjZSjsaYaL/n0/FA3fDtnCGihK5UYjCK+ZDr+TDIKLdm2Fs1UOzo76F5wO74XSZj0S6d7RCMLkCshcXALZxaWQRjXDZQ62oRAdCeG/Ju5HELX2QFH3C0hkRy6GovyfwF58AoVbguOxyB2H7/I34Gf11yANnQSp7Vr4MbQH0vg7kbNNp5AM3UrIVDchnz56B1Jm573wW9gZSFVPwO/hefg5FsIvN09CchtQCIOFw/F5U8ii3CZn4cqo7C8YlXEPYkx9cacZl00+iwnprrtwVdj1Q/gXmAs/pu6LZc9XQOGgSvh19n2cDZN341g2EcfxTEGwH/RewqlMsUfbbWIGLjUG+j/j9nokD1beiOvLS5dhjr30Gu6ZnivgdtM/6VJvY1+6pBHbH+h9CX84vfMxNJtisYVFlys+WNCIZJNmIsjohlhNSQC3f8R55H+y/hjkN8GPR9ndCLJxT4/3n0Px51ay8XQnNrYfDJHf//Fc0oMrEZSeeQGJ7+Z+gKCgLbHNWgXnB9FlYt5JaN38JIINC95EakjtAqQeuUx21c5B6tEFf0fSfbEFQf28Z6D6y+X/H0jf40QQJhYwAAAAAElFTkSuQmCC"/>');
-
- return inlinePng(dir.name, 'index.html', $)
- .then(function() {
- var $img = $('img');
- var src = $img.attr('src');
-
- expect(dir.name).toHaveFile(src);
- });
- });
-});
-
-
diff --git a/lib/output/modifiers/__tests__/resolveLinks.js b/lib/output/modifiers/__tests__/resolveLinks.js
deleted file mode 100644
index 8904c11..0000000
--- a/lib/output/modifiers/__tests__/resolveLinks.js
+++ /dev/null
@@ -1,104 +0,0 @@
-var path = require('path');
-var cheerio = require('cheerio');
-var resolveLinks = require('../resolveLinks');
-
-describe('resolveLinks', function() {
- function resolveFileBasic(href) {
- return 'fakeDir/' + href;
- }
-
- function resolveFileCustom(href) {
- if (path.extname(href) == '.md') {
- return href.slice(0, -3) + '.html';
- }
-
- return href;
- }
-
- describe('Absolute path', function() {
- var TEST = '<p>This is a <a href="/test/cool.md"></a></p>';
-
- it('should resolve path starting by "/" in root directory', function() {
- var $ = cheerio.load(TEST);
-
- return resolveLinks('hello.md', resolveFileBasic, $)
- .then(function() {
- var link = $('a');
- expect(link.attr('href')).toBe('fakeDir/test/cool.md');
- });
- });
-
- it('should resolve path starting by "/" in child directory', function() {
- var $ = cheerio.load(TEST);
-
- return resolveLinks('afolder/hello.md', resolveFileBasic, $)
- .then(function() {
- var link = $('a');
- expect(link.attr('href')).toBe('../fakeDir/test/cool.md');
- });
- });
- });
-
- describe('Anchor', function() {
- it('should prevent anchors in resolution', function() {
- var TEST = '<p>This is a <a href="test/cool.md#an-anchor"></a></p>';
- var $ = cheerio.load(TEST);
-
- return resolveLinks('hello.md', resolveFileCustom, $)
- .then(function() {
- var link = $('a');
- expect(link.attr('href')).toBe('test/cool.html#an-anchor');
- });
- });
-
- it('should ignore pure anchor links', function() {
- var TEST = '<p>This is a <a href="#an-anchor"></a></p>';
- var $ = cheerio.load(TEST);
-
- return resolveLinks('hello.md', resolveFileCustom, $)
- .then(function() {
- var link = $('a');
- expect(link.attr('href')).toBe('#an-anchor');
- });
- });
- });
-
- describe('Custom Resolver', function() {
- var TEST = '<p>This is a <a href="/test/cool.md"></a> <a href="afile.png"></a></p>';
-
- it('should resolve path correctly for absolute path', function() {
- var $ = cheerio.load(TEST);
-
- return resolveLinks('hello.md', resolveFileCustom, $)
- .then(function() {
- var link = $('a').first();
- expect(link.attr('href')).toBe('test/cool.html');
- });
- });
-
- it('should resolve path correctly for absolute path (2)', function() {
- var $ = cheerio.load(TEST);
-
- return resolveLinks('afodler/hello.md', resolveFileCustom, $)
- .then(function() {
- var link = $('a').first();
- expect(link.attr('href')).toBe('../test/cool.html');
- });
- });
- });
-
- describe('External link', function() {
- var TEST = '<p>This is a <a href="http://www.github.com">external link</a></p>';
-
- it('should have target="_blank" attribute', function() {
- var $ = cheerio.load(TEST);
-
- return resolveLinks('hello.md', resolveFileBasic, $)
- .then(function() {
- var link = $('a');
- expect(link.attr('target')).toBe('_blank');
- });
- });
- });
-
-});
diff --git a/lib/output/modifiers/__tests__/svgToImg.js b/lib/output/modifiers/__tests__/svgToImg.js
deleted file mode 100644
index 5fe9796..0000000
--- a/lib/output/modifiers/__tests__/svgToImg.js
+++ /dev/null
@@ -1,25 +0,0 @@
-var cheerio = require('cheerio');
-var tmp = require('tmp');
-
-describe('svgToImg', function() {
- var dir;
- var svgToImg = require('../svgToImg');
-
- beforeEach(function() {
- dir = tmp.dirSync();
- });
-
- it('should write svg as a file', function() {
- var $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>');
-
- return svgToImg(dir.name, 'index.html', $)
- .then(function() {
- var $img = $('img');
- var src = $img.attr('src');
-
- expect(dir.name).toHaveFile(src);
- });
- });
-});
-
-
diff --git a/lib/output/modifiers/__tests__/svgToPng.js b/lib/output/modifiers/__tests__/svgToPng.js
deleted file mode 100644
index dbb3502..0000000
--- a/lib/output/modifiers/__tests__/svgToPng.js
+++ /dev/null
@@ -1,33 +0,0 @@
-var cheerio = require('cheerio');
-var tmp = require('tmp');
-var path = require('path');
-
-var svgToImg = require('../svgToImg');
-var svgToPng = require('../svgToPng');
-
-describe('svgToPng', function() {
- var dir;
-
- beforeEach(function() {
- dir = tmp.dirSync();
- });
-
- it('should write svg as png file', function() {
- var $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>');
- var fileName = 'index.html';
-
- return svgToImg(dir.name, fileName, $)
- .then(function() {
- return svgToPng(dir.name, fileName, $);
- })
- .then(function() {
- var $img = $('img');
- var src = $img.attr('src');
-
- expect(dir.name).toHaveFile(src);
- expect(path.extname(src)).toBe('.png');
- });
- });
-});
-
-
diff --git a/lib/output/modifiers/fetchRemoteImages.js b/lib/output/modifiers/fetchRemoteImages.js
deleted file mode 100644
index ef868b9..0000000
--- a/lib/output/modifiers/fetchRemoteImages.js
+++ /dev/null
@@ -1,44 +0,0 @@
-var path = require('path');
-var crc = require('crc');
-
-var editHTMLElement = require('./editHTMLElement');
-var fs = require('../../utils/fs');
-var LocationUtils = require('../../utils/location');
-
-/**
- Fetch all remote images
-
- @param {String} rootFolder
- @param {String} currentFile
- @param {HTMLDom} $
- @return {Promise}
-*/
-function fetchRemoteImages(rootFolder, currentFile, $) {
- var currentDirectory = path.dirname(currentFile);
-
- return editHTMLElement($, 'img', function($img) {
- var src = $img.attr('src');
- var extension = path.extname(src);
-
- if (!LocationUtils.isExternal(src)) {
- return;
- }
-
- // We avoid generating twice the same PNG
- var hash = crc.crc32(src).toString(16);
- var fileName = hash + extension;
- var filePath = path.join(rootFolder, fileName);
-
- return fs.assertFile(filePath, function() {
- return fs.download(src, filePath);
- })
- .then(function() {
- // Convert to relative
- src = LocationUtils.relative(currentDirectory, fileName);
-
- $img.replaceWith('<img src="' + src + '" />');
- });
- });
-}
-
-module.exports = fetchRemoteImages;
diff --git a/lib/output/modifiers/highlightCode.js b/lib/output/modifiers/highlightCode.js
deleted file mode 100644
index 5d397bb..0000000
--- a/lib/output/modifiers/highlightCode.js
+++ /dev/null
@@ -1,58 +0,0 @@
-var is = require('is');
-var Immutable = require('immutable');
-
-var Promise = require('../../utils/promise');
-var editHTMLElement = require('./editHTMLElement');
-
-/**
- Return language for a code blocks from a list of class names
-
- @param {Array<String>}
- @return {String}
-*/
-function getLanguageForClass(classNames) {
- return Immutable.List(classNames)
- .map(function(cl) {
- // Markdown
- if (cl.search('lang-') === 0) {
- return cl.slice('lang-'.length);
- }
-
- // Asciidoc
- if (cl.search('language-') === 0) {
- return cl.slice('language-'.length);
- }
-
- return null;
- })
- .find(function(cl) {
- return Boolean(cl);
- });
-}
-
-
-/**
- Highlight all code elements
-
- @param {Function(lang, body) -> String} highlight
- @param {HTMLDom} $
- @return {Promise}
-*/
-function highlightCode(highlight, $) {
- return editHTMLElement($, 'code', function($code) {
- var classNames = ($code.attr('class') || '').split(' ');
- var lang = getLanguageForClass(classNames);
- var source = $code.text();
-
- return Promise(highlight(lang, source))
- .then(function(r) {
- if (is.string(r.html)) {
- $code.html(r.html);
- } else {
- $code.text(r.text);
- }
- });
- });
-}
-
-module.exports = highlightCode;
diff --git a/lib/output/modifiers/modifyHTML.js b/lib/output/modifiers/modifyHTML.js
deleted file mode 100644
index cd3d6e5..0000000
--- a/lib/output/modifiers/modifyHTML.js
+++ /dev/null
@@ -1,25 +0,0 @@
-var cheerio = require('cheerio');
-var Promise = require('../../utils/promise');
-
-/**
- Apply a list of operations to a page and
- output the new page.
-
- @param {Page}
- @param {List|Array<Transformation>}
- @return {Promise<Page>}
-*/
-function modifyHTML(page, operations) {
- var html = page.getContent();
- var $ = cheerio.load(html);
-
- return Promise.forEach(operations, function(op) {
- return op($);
- })
- .then(function() {
- var resultHTML = $.html();
- return page.set('content', resultHTML);
- });
-}
-
-module.exports = modifyHTML;
diff --git a/lib/output/modifiers/resolveLinks.js b/lib/output/modifiers/resolveLinks.js
deleted file mode 100644
index 9d15e5e..0000000
--- a/lib/output/modifiers/resolveLinks.js
+++ /dev/null
@@ -1,53 +0,0 @@
-var path = require('path');
-var url = require('url');
-
-var LocationUtils = require('../../utils/location');
-var editHTMLElement = require('./editHTMLElement');
-
-/**
- Resolve all HTML links:
- - /test.md in hello -> ../test.html
-
- @param {String} currentFile
- @param {Function(String) -> String} resolveFile
- @param {HTMLDom} $
-*/
-function resolveLinks(currentFile, resolveFile, $) {
- var currentDirectory = path.dirname(currentFile);
-
- return editHTMLElement($, 'a', function($a) {
- var href = $a.attr('href');
-
- // Don't change a tag without href
- if (!href) {
- return;
- }
-
- if (LocationUtils.isExternal(href)) {
- $a.attr('target', '_blank');
- return;
- }
-
- // Split anchor
- var parsed = url.parse(href);
- href = parsed.pathname || '';
-
- if (href) {
- // Calcul absolute path for this
- href = LocationUtils.toAbsolute(href, currentDirectory, '.');
-
- // Resolve file
- href = resolveFile(href);
-
- // Convert back to relative
- href = LocationUtils.relative(currentDirectory, href);
- }
-
- // Add back anchor
- href = href + (parsed.hash || '');
-
- $a.attr('href', href);
- });
-}
-
-module.exports = resolveLinks;
diff --git a/lib/output/preparePages.js b/lib/output/preparePages.js
deleted file mode 100644
index 83944ed..0000000
--- a/lib/output/preparePages.js
+++ /dev/null
@@ -1,26 +0,0 @@
-var Parse = require('../parse');
-var Promise = require('../utils/promise');
-
-/**
- List and prepare all pages
-
- @param {Output}
- @return {Promise<Output>}
-*/
-function preparePages(output) {
- var book = output.getBook();
- var logger = book.getLogger();
-
- if (book.isMultilingual()) {
- return Promise(output);
- }
-
- return Parse.parsePagesList(book)
- .then(function(pages) {
- logger.info.ln('found', pages.size, 'pages');
-
- return output.set('pages', pages);
- });
-}
-
-module.exports = preparePages;
diff --git a/lib/output/website/__tests__/i18n.js b/lib/output/website/__tests__/i18n.js
deleted file mode 100644
index fd610fb..0000000
--- a/lib/output/website/__tests__/i18n.js
+++ /dev/null
@@ -1,38 +0,0 @@
-var createMockOutput = require('../../__tests__/createMock');
-var prepareI18n = require('../prepareI18n');
-var createTemplateEngine = require('../createTemplateEngine');
-
-var WebsiteGenerator = require('../');
-
-describe('i18n', function() {
- it('should correctly use english as default language', function() {
- return createMockOutput(WebsiteGenerator, {
- 'README.md': 'Hello World'
- })
- .then(function(output) {
- return prepareI18n(output);
- })
- .then(function(output) {
- var engine = createTemplateEngine(output, 'README.md');
- var t = engine.getFilters().get('t');
-
- expect(t('SUMMARY_INTRODUCTION')).toEqual('Introduction');
- });
- });
-
- it('should correctly use language from book.json', function() {
- return createMockOutput(WebsiteGenerator, {
- 'README.md': 'Hello World',
- 'book.json': JSON.stringify({ language: 'fr' })
- })
- .then(function(output) {
- return prepareI18n(output);
- })
- .then(function(output) {
- var engine = createTemplateEngine(output, 'README.md');
- var t = engine.getFilters().get('t');
-
- expect(t('GITBOOK_LINK')).toEqual('Publié avec GitBook');
- });
- });
-});
diff --git a/lib/output/website/copyPluginAssets.js b/lib/output/website/copyPluginAssets.js
deleted file mode 100644
index 9150636..0000000
--- a/lib/output/website/copyPluginAssets.js
+++ /dev/null
@@ -1,117 +0,0 @@
-var path = require('path');
-
-var ASSET_FOLDER = require('../../constants/pluginAssetsFolder');
-var Promise = require('../../utils/promise');
-var fs = require('../../utils/fs');
-
-/**
- Copy all assets from plugins.
- Assets are files stored in "_assets"
- nd resources declared in the plugin itself.
-
- @param {Output}
- @return {Promise}
-*/
-function copyPluginAssets(output) {
- var book = output.getBook();
-
- // Don't copy plugins assets for language book
- // It'll be resolved to the parent folder
- if (book.isLanguageBook()) {
- return Promise(output);
- }
-
- var plugins = output.getPlugins()
-
- // We reverse the order of plugins to copy
- // so that first plugins can replace assets from other plugins.
- .reverse();
-
- return Promise.forEach(plugins, function(plugin) {
- return copyAssets(output, plugin)
- .then(function() {
- return copyResources(output, plugin);
- });
- })
- .thenResolve(output);
-}
-
-/**
- Copy assets from a plugin
-
- @param {Plugin}
- @return {Promise}
-*/
-function copyAssets(output, plugin) {
- var logger = output.getLogger();
- var pluginRoot = plugin.getPath();
- var options = output.getOptions();
-
- var outputRoot = options.get('root');
- var assetOutputFolder = path.join(outputRoot, 'gitbook');
- var prefix = options.get('prefix');
-
- var assetFolder = path.join(pluginRoot, ASSET_FOLDER, prefix);
-
- if (!fs.existsSync(assetFolder)) {
- return Promise();
- }
-
- logger.debug.ln('copy assets from theme', assetFolder);
- return fs.copyDir(
- assetFolder,
- assetOutputFolder,
- {
- deleteFirst: false,
- overwrite: true,
- confirm: true
- }
- );
-}
-
-/**
- Copy resources from a plugin
-
- @param {Plugin}
- @return {Promise}
-*/
-function copyResources(output, plugin) {
- var logger = output.getLogger();
-
- var options = output.getOptions();
- var outputRoot = options.get('root');
-
- var state = output.getState();
- var resources = state.getResources();
-
- var pluginRoot = plugin.getPath();
- var pluginResources = resources.get(plugin.getName());
-
- var assetsFolder = pluginResources.get('assets');
- var assetOutputFolder = path.join(outputRoot, 'gitbook', plugin.getNpmID());
-
- if (!assetsFolder) {
- return Promise();
- }
-
- // Resolve assets folder
- assetsFolder = path.resolve(pluginRoot, assetsFolder);
- if (!fs.existsSync(assetsFolder)) {
- logger.warn.ln('assets folder for plugin "' + plugin.getName() + '" doesn\'t exist');
- return Promise();
- }
-
- logger.debug.ln('copy resources from plugin', assetsFolder);
-
- return fs.copyDir(
- assetsFolder,
- assetOutputFolder,
- {
- deleteFirst: false,
- overwrite: true,
- confirm: true
- }
- );
-}
-
-module.exports = copyPluginAssets;
diff --git a/lib/output/website/createTemplateEngine.js b/lib/output/website/createTemplateEngine.js
deleted file mode 100644
index 02ec796..0000000
--- a/lib/output/website/createTemplateEngine.js
+++ /dev/null
@@ -1,151 +0,0 @@
-var path = require('path');
-var nunjucks = require('nunjucks');
-var DoExtension = require('nunjucks-do')(nunjucks);
-
-var Api = require('../../api');
-var deprecate = require('../../api/deprecate');
-var JSONUtils = require('../../json');
-var LocationUtils = require('../../utils/location');
-var fs = require('../../utils/fs');
-var PathUtils = require('../../utils/path');
-var TemplateEngine = require('../../models/templateEngine');
-var templatesFolder = require('../../constants/templatesFolder');
-var defaultFilters = require('../../constants/defaultFilters');
-var Templating = require('../../templating');
-var listSearchPaths = require('./listSearchPaths');
-
-var fileToURL = require('../helper/fileToURL');
-var resolveFileToURL = require('../helper/resolveFileToURL');
-
-/**
- * Directory for a theme with the templates
- */
-function templateFolder(dir) {
- return path.join(dir, templatesFolder);
-}
-
-/**
- * Create templating engine to render themes
- *
- * @param {Output} output
- * @param {String} currentFile
- * @return {TemplateEngine}
- */
-function createTemplateEngine(output, currentFile) {
- var book = output.getBook();
- var state = output.getState();
- var i18n = state.getI18n();
- var config = book.getConfig();
- var summary = book.getSummary();
- var outputFolder = output.getRoot();
-
- // Search paths for templates
- var searchPaths = listSearchPaths(output);
- var tplSearchPaths = searchPaths.map(templateFolder);
-
- // Create loader
- var loader = new Templating.ThemesLoader(tplSearchPaths);
-
- // Get languages
- var language = config.getValue('language');
-
- // Create API context
- var context = Api.encodeGlobal(output);
-
-
- /**
- * Check if a file exists
- * @param {String} fileName
- * @return {Boolean}
- */
- function fileExists(fileName) {
- if (!fileName) {
- return false;
- }
-
- var filePath = PathUtils.resolveInRoot(outputFolder, fileName);
- return fs.existsSync(filePath);
- }
-
- /**
- * Return an article by its path
- * @param {String} filePath
- * @return {Object|undefined}
- */
- function getArticleByPath(filePath) {
- var article = summary.getByPath(filePath);
- if (!article) return undefined;
-
- return JSONUtils.encodeSummaryArticle(article);
- }
-
- /**
- * Return a page by its path
- * @param {String} filePath
- * @return {Object|undefined}
- */
- function getPageByPath(filePath) {
- var page = output.getPage(filePath);
- if (!page) return undefined;
-
- return JSONUtils.encodePage(page, summary);
- }
-
- return TemplateEngine.create({
- loader: loader,
-
- context: context,
-
- globals: {
- getArticleByPath: getArticleByPath,
- getPageByPath: getPageByPath,
- fileExists: fileExists
- },
-
- filters: defaultFilters.merge({
- /**
- * Translate a sentence
- */
- t: function t(s) {
- return i18n.t(language, s);
- },
-
- /**
- * Resolve an absolute file path into a
- * relative path.
- * it also resolve pages
- */
- resolveFile: function(filePath) {
- filePath = resolveFileToURL(output, filePath);
- return LocationUtils.relativeForFile(currentFile, filePath);
- },
-
- resolveAsset: function(filePath) {
- filePath = LocationUtils.toAbsolute(filePath, '', '');
- filePath = path.join('gitbook', filePath);
- filePath = LocationUtils.relativeForFile(currentFile, filePath);
-
- // Use assets from parent if language book
- if (book.isLanguageBook()) {
- filePath = path.join('../', filePath);
- }
-
- return LocationUtils.normalize(filePath);
- },
-
-
- fileExists: deprecate.method(book, 'fileExists', fileExists, 'Filter "fileExists" is deprecated, use "fileExists(filename)" '),
- getArticleByPath: deprecate.method(book, 'getArticleByPath', fileExists, 'Filter "getArticleByPath" is deprecated, use "getArticleByPath(filename)" '),
-
- contentURL: function(filePath) {
- return fileToURL(output, filePath);
- }
- }),
-
- extensions: {
- 'DoExtension': new DoExtension()
- }
- });
-}
-
-module.exports = createTemplateEngine;
diff --git a/lib/output/website/index.js b/lib/output/website/index.js
deleted file mode 100644
index 7818a28..0000000
--- a/lib/output/website/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-
-module.exports = {
- name: 'website',
- State: require('./state'),
- Options: require('./options'),
- onInit: require('./onInit'),
- onFinish: require('./onFinish'),
- onPage: require('./onPage'),
- onAsset: require('./onAsset'),
- createTemplateEngine: require('./createTemplateEngine')
-};
diff --git a/lib/output/website/listSearchPaths.js b/lib/output/website/listSearchPaths.js
deleted file mode 100644
index c45f39c..0000000
--- a/lib/output/website/listSearchPaths.js
+++ /dev/null
@@ -1,23 +0,0 @@
-
-/**
- List search paths for templates / i18n, etc
-
- @param {Output} output
- @return {List<String>}
-*/
-function listSearchPaths(output) {
- var book = output.getBook();
- var plugins = output.getPlugins();
-
- var searchPaths = plugins
- .valueSeq()
- .map(function(plugin) {
- return plugin.getPath();
- })
- .toList();
-
- return searchPaths.unshift(book.getContentRoot());
-}
-
-
-module.exports = listSearchPaths;
diff --git a/lib/output/website/onAsset.js b/lib/output/website/onAsset.js
deleted file mode 100644
index 69dfc4f..0000000
--- a/lib/output/website/onAsset.js
+++ /dev/null
@@ -1,28 +0,0 @@
-var path = require('path');
-var fs = require('../../utils/fs');
-
-/**
- Copy an asset to the output folder
-
- @param {Output} output
- @param {Page} page
-*/
-function onAsset(output, asset) {
- var book = output.getBook();
- var options = output.getOptions();
- var bookFS = book.getContentFS();
-
- var outputFolder = options.get('root');
- var outputPath = path.resolve(outputFolder, asset);
-
- return fs.ensureFile(outputPath)
- .then(function() {
- return bookFS.readAsStream(asset)
- .then(function(stream) {
- return fs.writeStream(outputPath, stream);
- });
- })
- .thenResolve(output);
-}
-
-module.exports = onAsset;
diff --git a/lib/output/website/onFinish.js b/lib/output/website/onFinish.js
deleted file mode 100644
index 5267458..0000000
--- a/lib/output/website/onFinish.js
+++ /dev/null
@@ -1,35 +0,0 @@
-var Promise = require('../../utils/promise');
-var JSONUtils = require('../../json');
-var Templating = require('../../templating');
-var writeFile = require('../helper/writeFile');
-var createTemplateEngine = require('./createTemplateEngine');
-
-/**
- Finish the generation, write the languages index
-
- @param {Output}
- @return {Output}
-*/
-function onFinish(output) {
- var book = output.getBook();
- var options = output.getOptions();
- var prefix = options.get('prefix');
-
- if (!book.isMultilingual()) {
- return Promise(output);
- }
-
- var filePath = 'index.html';
- var engine = createTemplateEngine(output, filePath);
- var context = JSONUtils.encodeOutput(output);
-
- // Render the theme
- return Templating.renderFile(engine, prefix + '/languages.html', context)
-
- // Write it to the disk
- .then(function(tplOut) {
- return writeFile(output, filePath, tplOut.getContent());
- });
-}
-
-module.exports = onFinish;
diff --git a/lib/output/website/onInit.js b/lib/output/website/onInit.js
deleted file mode 100644
index 3465eef..0000000
--- a/lib/output/website/onInit.js
+++ /dev/null
@@ -1,20 +0,0 @@
-var Promise = require('../../utils/promise');
-
-var copyPluginAssets = require('./copyPluginAssets');
-var prepareI18n = require('./prepareI18n');
-var prepareResources = require('./prepareResources');
-
-/**
- Initialize the generator
-
- @param {Output}
- @return {Output}
-*/
-function onInit(output) {
- return Promise(output)
- .then(prepareI18n)
- .then(prepareResources)
- .then(copyPluginAssets);
-}
-
-module.exports = onInit;
diff --git a/lib/output/website/onPage.js b/lib/output/website/onPage.js
deleted file mode 100644
index 5fb40a7..0000000
--- a/lib/output/website/onPage.js
+++ /dev/null
@@ -1,76 +0,0 @@
-var path = require('path');
-var omit = require('omit-keys');
-
-var Templating = require('../../templating');
-var Plugins = require('../../plugins');
-var JSONUtils = require('../../json');
-var LocationUtils = require('../../utils/location');
-var Modifiers = require('../modifiers');
-var writeFile = require('../helper/writeFile');
-var getModifiers = require('../getModifiers');
-var createTemplateEngine = require('./createTemplateEngine');
-var fileToOutput = require('../helper/fileToOutput');
-
-/**
- * Write a page as a json file
- *
- * @param {Output} output
- * @param {Page} page
- */
-function onPage(output, page) {
- var options = output.getOptions();
- var prefix = options.get('prefix');
-
- var file = page.getFile();
-
- var book = output.getBook();
- var plugins = output.getPlugins();
- var state = output.getState();
- var resources = state.getResources();
-
- var engine = createTemplateEngine(output, page.getPath());
-
- // Output file path
- var filePath = fileToOutput(output, file.getPath());
-
- // Calcul relative path to the root
- var outputDirName = path.dirname(filePath);
- var basePath = LocationUtils.normalize(path.relative(outputDirName, './'));
-
- return Modifiers.modifyHTML(page, getModifiers(output, page))
- .then(function(resultPage) {
- // Generate the context
- var context = JSONUtils.encodeOutputWithPage(output, resultPage);
- context.plugins = {
- resources: Plugins.listResources(plugins, resources).toJS()
- };
-
- context.template = {
- getJSContext: function() {
- return {
- page: omit(context.page, 'content'),
- config: context.config,
- file: context.file,
- gitbook: context.gitbook,
- basePath: basePath,
- book: {
- language: book.getLanguage()
- }
- };
- }
- };
-
- // We should probabbly move it to "template" or a "site" namespace
- context.basePath = basePath;
-
- // Render the theme
- return Templating.renderFile(engine, prefix + '/page.html', context)
-
- // Write it to the disk
- .then(function(tplOut) {
- return writeFile(output, filePath, tplOut.getContent());
- });
- });
-}
-
-module.exports = onPage;
diff --git a/lib/output/website/options.js b/lib/output/website/options.js
deleted file mode 100644
index ac9cdad..0000000
--- a/lib/output/website/options.js
+++ /dev/null
@@ -1,14 +0,0 @@
-var Immutable = require('immutable');
-
-var Options = Immutable.Record({
- // Root folder for the output
- root: String(),
-
- // Prefix for generation
- prefix: String('website'),
-
- // Use directory index url instead of "index.html"
- directoryIndex: Boolean(true)
-});
-
-module.exports = Options;
diff --git a/lib/output/website/prepareI18n.js b/lib/output/website/prepareI18n.js
deleted file mode 100644
index cedd3b9..0000000
--- a/lib/output/website/prepareI18n.js
+++ /dev/null
@@ -1,30 +0,0 @@
-var path = require('path');
-
-var fs = require('../../utils/fs');
-var Promise = require('../../utils/promise');
-var listSearchPaths = require('./listSearchPaths');
-
-/**
- * Prepare i18n, load translations from plugins and book
- *
- * @param {Output}
- * @return {Promise<Output>}
- */
-function prepareI18n(output) {
- var state = output.getState();
- var i18n = state.getI18n();
- var searchPaths = listSearchPaths(output);
-
- searchPaths
- .reverse()
- .forEach(function(searchPath) {
- var i18nRoot = path.resolve(searchPath, '_i18n');
-
- if (!fs.existsSync(i18nRoot)) return;
- i18n.load(i18nRoot);
- });
-
- return Promise(output);
-}
-
-module.exports = prepareI18n;
diff --git a/lib/output/website/prepareResources.js b/lib/output/website/prepareResources.js
deleted file mode 100644
index 4e6835d..0000000
--- a/lib/output/website/prepareResources.js
+++ /dev/null
@@ -1,54 +0,0 @@
-var is = require('is');
-var Immutable = require('immutable');
-var Promise = require('../../utils/promise');
-
-var Api = require('../../api');
-
-/**
- Prepare plugins resources, add all output corresponding type resources
-
- @param {Output}
- @return {Promise<Output>}
-*/
-function prepareResources(output) {
- var plugins = output.getPlugins();
- var options = output.getOptions();
- var type = options.get('prefix');
- var state = output.getState();
- var context = Api.encodeGlobal(output);
-
- var result = Immutable.Map();
-
- return Promise.forEach(plugins, function(plugin) {
- var pluginResources = plugin.getResources(type);
-
- return Promise()
- .then(function() {
- // Apply resources if is a function
- if (is.fn(pluginResources)) {
- return Promise()
- .then(pluginResources.bind(context));
- }
- else {
- return pluginResources;
- }
- })
- .then(function(resources) {
- result = result.set(plugin.getName(), Immutable.Map(resources));
- });
- })
- .then(function() {
- // Set output resources
- state = state.merge({
- resources: result
- });
-
- output = output.merge({
- state: state
- });
-
- return output;
- });
-}
-
-module.exports = prepareResources; \ No newline at end of file
diff --git a/lib/parse/listAssets.js b/lib/parse/listAssets.js
deleted file mode 100644
index d83d8fd..0000000
--- a/lib/parse/listAssets.js
+++ /dev/null
@@ -1,43 +0,0 @@
-var timing = require('../utils/timing');
-
-/**
- List all assets in a book
- Assets are file not ignored and not a page
-
- @param {Book} book
- @param {List<String>} pages
- @param
-*/
-function listAssets(book, pages) {
- var fs = book.getContentFS();
-
- var summary = book.getSummary();
- var summaryFile = summary.getFile().getPath();
-
- var glossary = book.getGlossary();
- var glossaryFile = glossary.getFile().getPath();
-
- var langs = book.getLanguages();
- var langsFile = langs.getFile().getPath();
-
- var config = book.getConfig();
- var configFile = config.getFile().getPath();
-
- function filterFile(file) {
- return !(
- file === summaryFile ||
- file === glossaryFile ||
- file === langsFile ||
- file === configFile ||
- book.isContentFileIgnored(file) ||
- pages.has(file)
- );
- }
-
- return timing.measure(
- 'parse.listAssets',
- fs.listAllFiles('.', filterFile)
- );
-}
-
-module.exports = listAssets;
diff --git a/lib/plugins/__tests__/findForBook.js b/lib/plugins/__tests__/findForBook.js
deleted file mode 100644
index d8af2e9..0000000
--- a/lib/plugins/__tests__/findForBook.js
+++ /dev/null
@@ -1,19 +0,0 @@
-var path = require('path');
-
-var Book = require('../../models/book');
-var createNodeFS = require('../../fs/node');
-var findForBook = require('../findForBook');
-
-describe('findForBook', function() {
- var fs = createNodeFS(
- path.resolve(__dirname, '../../..')
- );
- var book = Book.createForFS(fs);
-
- it('should list default plugins', function() {
- return findForBook(book)
- .then(function(plugins) {
- expect(plugins.has('fontsettings')).toBeTruthy();
- });
- });
-});
diff --git a/lib/plugins/__tests__/installPlugin.js b/lib/plugins/__tests__/installPlugin.js
deleted file mode 100644
index 0c1a346..0000000
--- a/lib/plugins/__tests__/installPlugin.js
+++ /dev/null
@@ -1,29 +0,0 @@
-var path = require('path');
-
-var PluginDependency = require('../../models/pluginDependency');
-var Book = require('../../models/book');
-var NodeFS = require('../../fs/node');
-var installPlugin = require('../installPlugin');
-
-var Parse = require('../../parse');
-
-describe('installPlugin', function() {
- var book;
-
- this.timeout(30000);
-
- before(function() {
- var fs = NodeFS(path.resolve(__dirname, '../../../'));
- var baseBook = Book.createForFS(fs);
-
- return Parse.parseConfig(baseBook)
- .then(function(_book) {
- book = _book;
- });
- });
-
- it('must install a plugin from NPM', function() {
- var dep = PluginDependency.createFromString('ga');
- return installPlugin(book, dep);
- });
-});
diff --git a/lib/plugins/__tests__/installPlugins.js b/lib/plugins/__tests__/installPlugins.js
deleted file mode 100644
index 1a66f90..0000000
--- a/lib/plugins/__tests__/installPlugins.js
+++ /dev/null
@@ -1,30 +0,0 @@
-var path = require('path');
-
-var Book = require('../../models/book');
-var NodeFS = require('../../fs/node');
-var installPlugins = require('../installPlugins');
-
-var Parse = require('../../parse');
-
-describe('installPlugins', function() {
- var book;
-
- this.timeout(30000);
-
- before(function() {
- var fs = NodeFS(path.resolve(__dirname, '../../../'));
- var baseBook = Book.createForFS(fs);
-
- return Parse.parseConfig(baseBook)
- .then(function(_book) {
- book = _book;
- });
- });
-
- it('must install all plugins from NPM', function() {
- return installPlugins(book)
- .then(function(n) {
- expect(n).toBe(2);
- });
- });
-});
diff --git a/lib/plugins/__tests__/listDependencies.js b/lib/plugins/__tests__/listDependencies.js
deleted file mode 100644
index 940faba..0000000
--- a/lib/plugins/__tests__/listDependencies.js
+++ /dev/null
@@ -1,38 +0,0 @@
-var PluginDependency = require('../../models/pluginDependency');
-var listDependencies = require('../listDependencies');
-var toNames = require('../toNames');
-
-describe('listDependencies', function() {
- it('must list default', function() {
- var deps = PluginDependency.listFromString('ga,great');
- var plugins = listDependencies(deps);
- var names = toNames(plugins);
-
- expect(names).toEqual([
- 'ga', 'great',
- 'highlight', 'search', 'lunr', 'sharing', 'fontsettings',
- 'theme-default' ]);
- });
-
- it('must list from array with -', function() {
- var deps = PluginDependency.listFromString('ga,-great');
- var plugins = listDependencies(deps);
- var names = toNames(plugins);
-
- expect(names).toEqual([
- 'ga',
- 'highlight', 'search', 'lunr', 'sharing', 'fontsettings',
- 'theme-default' ]);
- });
-
- it('must remove default plugins using -', function() {
- var deps = PluginDependency.listFromString('ga,-search');
- var plugins = listDependencies(deps);
- var names = toNames(plugins);
-
- expect(names).toEqual([
- 'ga',
- 'highlight', 'lunr', 'sharing', 'fontsettings',
- 'theme-default' ]);
- });
-});
diff --git a/lib/plugins/findInstalled.js b/lib/plugins/findInstalled.js
deleted file mode 100644
index 06cc6c4..0000000
--- a/lib/plugins/findInstalled.js
+++ /dev/null
@@ -1,91 +0,0 @@
-var readInstalled = require('read-installed');
-var Immutable = require('immutable');
-var path = require('path');
-
-var Promise = require('../utils/promise');
-var fs = require('../utils/fs');
-var Plugin = require('../models/plugin');
-var PREFIX = require('../constants/pluginPrefix');
-
-/**
- * Validate if a package name is a GitBook plugin
- *
- * @return {Boolean}
- */
-function validateId(name) {
- return name && name.indexOf(PREFIX) === 0;
-}
-
-
-/**
- * List all packages installed inside a folder
- *
- * @param {String} folder
- * @return {OrderedMap<String:Plugin>}
- */
-function findInstalled(folder) {
- var options = {
- dev: false,
- log: function() {},
- depth: 4
- };
- var results = Immutable.OrderedMap();
-
- function onPackage(pkg, parent) {
- if (!pkg.name) return;
-
- var name = pkg.name;
- var version = pkg.version;
- var pkgPath = pkg.realPath;
- var depth = pkg.depth;
- var dependencies = pkg.dependencies;
-
- var pluginName = name.slice(PREFIX.length);
-
- if (!validateId(name)){
- if (parent) return;
- } else {
- results = results.set(pluginName, Plugin({
- name: pluginName,
- version: version,
- path: pkgPath,
- depth: depth,
- parent: parent
- }));
- }
-
- Immutable.Map(dependencies).forEach(function(dep) {
- onPackage(dep, pluginName);
- });
- }
-
- // Search for gitbook-plugins in node_modules folder
- var node_modules = path.join(folder, 'node_modules');
-
- // List all folders in node_modules
- return fs.readdir(node_modules)
- .fail(function() {
- return Promise([]);
- })
- .then(function(modules) {
- return Promise.serie(modules, function(module) {
- // Not a gitbook-plugin
- if (!validateId(module)) {
- return Promise();
- }
-
- // Read gitbook-plugin package details
- var module_folder = path.join(node_modules, module);
- return Promise.nfcall(readInstalled, module_folder, options)
- .then(function(data) {
- onPackage(data);
- });
- });
- })
- .then(function() {
- // Return installed plugins
- return results;
- });
-}
-
-module.exports = findInstalled;
diff --git a/lib/plugins/index.js b/lib/plugins/index.js
deleted file mode 100644
index 607a7f1..0000000
--- a/lib/plugins/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-
-module.exports = {
- loadForBook: require('./loadForBook'),
- validateConfig: require('./validateConfig'),
- installPlugins: require('./installPlugins'),
- listResources: require('./listResources'),
- listBlocks: require('./listBlocks'),
- listFilters: require('./listFilters')
-};
-
diff --git a/lib/plugins/installPlugin.js b/lib/plugins/installPlugin.js
deleted file mode 100644
index 37852df..0000000
--- a/lib/plugins/installPlugin.js
+++ /dev/null
@@ -1,47 +0,0 @@
-var npmi = require('npmi');
-
-var Promise = require('../utils/promise');
-var resolveVersion = require('./resolveVersion');
-
-/**
- Install a plugin for a book
-
- @param {Book}
- @param {PluginDependency}
- @return {Promise}
-*/
-function installPlugin(book, plugin) {
- var logger = book.getLogger();
-
- var installFolder = book.getRoot();
- var name = plugin.getName();
- var requirement = plugin.getVersion();
-
- logger.info.ln('');
- logger.info.ln('installing plugin "' + name + '"');
-
- // Find a version to install
- return resolveVersion(plugin)
- .then(function(version) {
- if (!version) {
- throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
- }
-
- logger.info.ln('install plugin "' + name +'" (' + requirement + ') from NPM with version', version);
- return Promise.nfcall(npmi, {
- 'name': plugin.getNpmID(),
- 'version': version,
- 'path': installFolder,
- 'npmLoad': {
- 'loglevel': 'silent',
- 'loaded': true,
- 'prefix': installFolder
- }
- });
- })
- .then(function() {
- logger.info.ok('plugin "' + name + '" installed with success');
- });
-}
-
-module.exports = installPlugin;
diff --git a/lib/plugins/listBlocks.js b/lib/plugins/listBlocks.js
deleted file mode 100644
index 3ac28af..0000000
--- a/lib/plugins/listBlocks.js
+++ /dev/null
@@ -1,18 +0,0 @@
-var Immutable = require('immutable');
-
-/**
- List blocks from a list of plugins
-
- @param {OrderedMap<String:Plugin>}
- @return {Map<String:TemplateBlock>}
-*/
-function listBlocks(plugins) {
- return plugins
- .reverse()
- .reduce(function(result, plugin) {
- var blocks = plugin.getBlocks();
- return result.merge(blocks);
- }, Immutable.Map());
-}
-
-module.exports = listBlocks;
diff --git a/lib/plugins/listFilters.js b/lib/plugins/listFilters.js
deleted file mode 100644
index 4d8a471..0000000
--- a/lib/plugins/listFilters.js
+++ /dev/null
@@ -1,17 +0,0 @@
-var Immutable = require('immutable');
-
-/**
- List filters from a list of plugins
-
- @param {OrderedMap<String:Plugin>}
- @return {Map<String:Function>}
-*/
-function listFilters(plugins) {
- return plugins
- .reverse()
- .reduce(function(result, plugin) {
- return result.merge(plugin.getFilters());
- }, Immutable.Map());
-}
-
-module.exports = listFilters;
diff --git a/lib/plugins/listResources.js b/lib/plugins/listResources.js
deleted file mode 100644
index fe31b5a..0000000
--- a/lib/plugins/listResources.js
+++ /dev/null
@@ -1,45 +0,0 @@
-var Immutable = require('immutable');
-var path = require('path');
-
-var LocationUtils = require('../utils/location');
-var PLUGIN_RESOURCES = require('../constants/pluginResources');
-
-/**
- List all resources from a list of plugins
-
- @param {OrderedMap<String:Plugin>}
- @param {String} type
- @return {Map<String:List<{url, path}>}
-*/
-function listResources(plugins, resources) {
- return plugins.reduce(function(result, plugin) {
- var npmId = plugin.getNpmID();
- var pluginResources = resources.get(plugin.getName());
-
- PLUGIN_RESOURCES.forEach(function(resourceType) {
- var assets = pluginResources.get(resourceType);
- if (!assets) return;
-
- var list = result.get(resourceType) || Immutable.List();
-
- assets = assets.map(function(assetFile) {
- if (LocationUtils.isExternal(assetFile)) {
- return {
- url: assetFile
- };
- } else {
- return {
- path: LocationUtils.normalize(path.join(npmId, assetFile))
- };
- }
- });
-
- list = list.concat(assets);
- result = result.set(resourceType, list);
- });
-
- return result;
- }, Immutable.Map());
-}
-
-module.exports = listResources;
diff --git a/lib/plugins/locateRootFolder.js b/lib/plugins/locateRootFolder.js
deleted file mode 100644
index 1139510..0000000
--- a/lib/plugins/locateRootFolder.js
+++ /dev/null
@@ -1,22 +0,0 @@
-var path = require('path');
-var resolve = require('resolve');
-
-var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
-
-/**
- * Resolve the root folder containing for node_modules
- * since gitbook can be used as a library and dependency can be flattened.
- *
- * @return {String} folderPath
- */
-function locateRootFolder() {
- var firstDefaultPlugin = DEFAULT_PLUGINS.first();
- var pluginPath = resolve.sync(firstDefaultPlugin.getNpmID() + '/package.json', {
- basedir: __dirname
- });
- var nodeModules = path.resolve(pluginPath, '../../..');
-
- return nodeModules;
-}
-
-module.exports = locateRootFolder;
diff --git a/lib/plugins/validateConfig.js b/lib/plugins/validateConfig.js
deleted file mode 100644
index fab1fef..0000000
--- a/lib/plugins/validateConfig.js
+++ /dev/null
@@ -1,71 +0,0 @@
-var Immutable = require('immutable');
-var jsonschema = require('jsonschema');
-var jsonSchemaDefaults = require('json-schema-defaults');
-
-var Promise = require('../utils/promise');
-var error = require('../utils/error');
-var mergeDefaults = require('../utils/mergeDefaults');
-
-/**
- Validate one plugin for a book and update book's confiration
-
- @param {Book}
- @param {Plugin}
- @return {Book}
-*/
-function validatePluginConfig(book, plugin) {
- var config = book.getConfig();
- var packageInfos = plugin.getPackage();
-
- var configKey = [
- 'pluginsConfig',
- plugin.getName()
- ].join('.');
-
- var pluginConfig = config.getValue(configKey, {}).toJS();
-
- var schema = (packageInfos.get('gitbook') || Immutable.Map()).toJS();
- if (!schema) return book;
-
- // Normalize schema
- schema.id = '/' + configKey;
- schema.type = 'object';
-
- // Validate and throw if invalid
- var v = new jsonschema.Validator();
- var result = v.validate(pluginConfig, schema, {
- propertyName: configKey
- });
-
- // Throw error
- if (result.errors.length > 0) {
- throw new error.ConfigurationError(new Error(result.errors[0].stack));
- }
-
- // Insert default values
- var defaults = jsonSchemaDefaults(schema);
- pluginConfig = mergeDefaults(pluginConfig, defaults);
-
-
- // Update configuration
- config = config.setValue(configKey, pluginConfig);
-
- // Return new book
- return book.set('config', config);
-}
-
-/**
- Validate a book configuration for plugins and
- returns an update configuration with default values.
-
- @param {Book}
- @param {OrderedMap<String:Plugin>}
- @return {Promise<Book>}
-*/
-function validateConfig(book, plugins) {
- return Promise.reduce(plugins, function(newBook, plugin) {
- return validatePluginConfig(newBook, plugin);
- }, book);
-}
-
-module.exports = validateConfig;
diff --git a/lib/templating/__tests__/conrefsLoader.js b/lib/templating/__tests__/conrefsLoader.js
deleted file mode 100644
index 196b513..0000000
--- a/lib/templating/__tests__/conrefsLoader.js
+++ /dev/null
@@ -1,98 +0,0 @@
-var path = require('path');
-
-var TemplateEngine = require('../../models/templateEngine');
-var renderTemplate = require('../render');
-var ConrefsLoader = require('../conrefsLoader');
-
-describe('ConrefsLoader', function() {
- var dirName = __dirname + '/';
- var fileName = path.join(dirName, 'test.md');
-
- describe('Git', function() {
- var engine = TemplateEngine({
- loader: new ConrefsLoader(dirName)
- });
-
- it('should include content from git', function() {
- return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('Hello from git');
- });
- });
-
- it('should handle deep inclusion (1)', function() {
- return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('First Hello. Hello from git');
- });
- });
-
- it('should handle deep inclusion (2)', function() {
- return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test3.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('First Hello. Hello from git');
- });
- });
- });
-
- describe('Local', function() {
- var engine = TemplateEngine({
- loader: new ConrefsLoader(dirName)
- });
-
- describe('Relative', function() {
- it('should resolve basic relative filepath', function() {
- return renderTemplate(engine, fileName, '{% include "include.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('Hello World');
- });
- });
-
- it('should resolve basic parent filepath', function() {
- return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "../include.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('Hello World');
- });
- });
- });
-
- describe('Absolute', function() {
- it('should resolve absolute filepath', function() {
- return renderTemplate(engine, fileName, '{% include "/include.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('Hello World');
- });
- });
-
- it('should resolve absolute filepath when in a directory', function() {
- return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "/include.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('Hello World');
- });
- });
- });
- });
-
- describe('transform', function() {
- function transform(filePath, source) {
- expect(filePath).toBeA('string');
- expect(source).toBeA('string');
-
- expect(filePath).toBe(path.resolve(__dirname, 'include.md'));
- expect(source).toBe('Hello World');
-
- return 'test-' + source + '-endtest';
- }
- var engine = TemplateEngine({
- loader: new ConrefsLoader(dirName, transform)
- });
-
- it('should transform included content', function() {
- return renderTemplate(engine, fileName, '{% include "include.md" %}')
- .then(function(out) {
- expect(out.getContent()).toBe('test-Hello World-endtest');
- });
- });
- });
-});
-
diff --git a/lib/templating/__tests__/postRender.js b/lib/templating/__tests__/postRender.js
deleted file mode 100644
index 131e29b..0000000
--- a/lib/templating/__tests__/postRender.js
+++ /dev/null
@@ -1,51 +0,0 @@
-var TemplateEngine = require('../../models/templateEngine');
-var TemplateBlock = require('../../models/templateBlock');
-
-var renderTemplate = require('../render');
-var postRender = require('../postRender');
-
-describe('postRender', function() {
- var testPost;
- var engine = TemplateEngine.create({
- blocks: [
- TemplateBlock.create('lower', function(blk) {
- return blk.body.toLowerCase();
- }),
- TemplateBlock.create('prefix', function(blk) {
- return {
- body: '_' + blk.body + '_',
- post: function() {
- testPost = true;
- }
- };
- })
- ]
- });
-
- it('should correctly replace block', function() {
- return renderTemplate(engine, 'README.md', 'Hello {% lower %}Samy{% endlower %}')
- .then(function(output) {
- expect(output.getContent()).toMatch(/Hello \{\{\-([\S]+)\-\}\}/);
- expect(output.getBlocks().size).toBe(1);
-
- return postRender(engine, output);
- })
- .then(function(result) {
- expect(result).toBe('Hello samy');
- });
- });
-
- it('should correctly replace blocks', function() {
- return renderTemplate(engine, 'README.md', 'Hello {% lower %}Samy{% endlower %}{% prefix %}Pesse{% endprefix %}')
- .then(function(output) {
- expect(output.getContent()).toMatch(/Hello \{\{\-([\S]+)\-\}\}\{\{\-([\S]+)\-\}\}/);
- expect(output.getBlocks().size).toBe(2);
- return postRender(engine, output);
- })
- .then(function(result) {
- expect(result).toBe('Hello samy_Pesse_');
- expect(testPost).toBe(true);
- });
- });
-
-});
diff --git a/lib/templating/__tests__/replaceShortcuts.js b/lib/templating/__tests__/replaceShortcuts.js
deleted file mode 100644
index 216a1c8..0000000
--- a/lib/templating/__tests__/replaceShortcuts.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var Immutable = require('immutable');
-
-var TemplateBlock = require('../../models/templateBlock');
-var replaceShortcuts = require('../replaceShortcuts');
-
-describe('replaceShortcuts', function() {
- var blocks = Immutable.List([
- TemplateBlock.create('math', {
- shortcuts: {
- start: '$$',
- end: '$$',
- parsers: ['markdown']
- }
- })
- ]);
-
- it('should correctly replace inline matches by block', function() {
- var content = replaceShortcuts(blocks, 'test.md', 'Hello $$a = b$$');
- expect(content).toBe('Hello {% math %}a = b{% endmath %}');
- });
-
- it('should correctly replace block matches', function() {
- var content = replaceShortcuts(blocks, 'test.md', 'Hello\n$$\na = b\n$$\n');
- expect(content).toBe('Hello\n{% math %}\na = b\n{% endmath %}\n');
- });
-});
-
diff --git a/lib/templating/index.js b/lib/templating/index.js
deleted file mode 100644
index bd74aca..0000000
--- a/lib/templating/index.js
+++ /dev/null
@@ -1,10 +0,0 @@
-
-module.exports = {
- render: require('./render'),
- renderFile: require('./renderFile'),
- postRender: require('./postRender'),
- replaceShortcuts: require('./replaceShortcuts'),
-
- ConrefsLoader: require('./conrefsLoader'),
- ThemesLoader: require('./themesLoader')
-};
diff --git a/lib/templating/postRender.js b/lib/templating/postRender.js
deleted file mode 100644
index f464f86..0000000
--- a/lib/templating/postRender.js
+++ /dev/null
@@ -1,53 +0,0 @@
-var Promise = require('../utils/promise');
-
-
-/**
- * Replace position markers of blocks by body after processing
- * This is done to avoid that markdown/asciidoc processer parse the block content
- *
- * @param {String} content
- * @return {Object} {blocks: Set, content: String}
- */
-function replaceBlocks(content, blocks) {
- var newContent = content.replace(/\{\{\-\%([\s\S]+?)\%\-\}\}/g, function(match, key) {
- var replacedWith = match;
-
- var block = blocks.get(key);
- if (block) {
- replacedWith = replaceBlocks(block.get('body'), blocks);
- }
-
- return replacedWith;
- });
-
- return newContent;
-}
-
-/**
- * Post render a template:
- * - Execute "post" for blocks
- * - Replace block content
- *
- * @param {TemplateEngine} engine
- * @param {TemplateOutput} content
- * @return {Promise<String>}
- */
-function postRender(engine, output) {
- var content = output.getContent();
- var blocks = output.getBlocks();
-
- var result = replaceBlocks(content, blocks);
-
- return Promise.forEach(blocks, function(block) {
- var post = block.get('post');
-
- if (!post) {
- return;
- }
-
- return post();
- })
- .thenResolve(result);
-}
-
-module.exports = postRender;
diff --git a/lib/templating/themesLoader.js b/lib/templating/themesLoader.js
deleted file mode 100644
index bae4c12..0000000
--- a/lib/templating/themesLoader.js
+++ /dev/null
@@ -1,115 +0,0 @@
-var Immutable = require('immutable');
-var nunjucks = require('nunjucks');
-var fs = require('fs');
-var path = require('path');
-
-var PathUtils = require('../utils/path');
-
-
-var ThemesLoader = nunjucks.Loader.extend({
- init: function(searchPaths) {
- this.searchPaths = Immutable.List(searchPaths)
- .map(path.normalize);
- },
-
- /**
- * Read source of a resolved filepath
- * @param {String}
- * @return {Object}
- */
- getSource: function(fullpath) {
- if (!fullpath) return null;
-
- fullpath = this.resolve(null, fullpath);
- var templateName = this.getTemplateName(fullpath);
-
- if(!fullpath) {
- return null;
- }
-
- var src = fs.readFileSync(fullpath, 'utf-8');
-
- src = '{% do %}var template = template || {}; template.stack = template.stack || []; template.stack.push(template.self); template.self = ' + JSON.stringify(templateName) + '{% enddo %}\n' +
- src +
- '\n{% do %}template.self = template.stack.pop();{% enddo %}';
-
- return {
- src: src,
- path: fullpath,
- noCache: true
- };
- },
-
- /**
- * Nunjucks calls "isRelative" to determine when to call "resolve".
- * We handle absolute paths ourselves in ".resolve" so we always return true
- */
- isRelative: function() {
- return true;
- },
-
- /**
- * Get original search path containing a template
- * @param {String} filepath
- * @return {String} searchPath
- */
- getSearchPath: function(filepath) {
- return this.searchPaths
- .sortBy(function(s) {
- return -s.length;
- })
- .find(function(basePath) {
- return (filepath && filepath.indexOf(basePath) === 0);
- });
- },
-
- /**
- * Get template name from a filepath
- * @param {String} filepath
- * @return {String} name
- */
- getTemplateName: function(filepath) {
- var originalSearchPath = this.getSearchPath(filepath);
- return originalSearchPath? path.relative(originalSearchPath, filepath) : null;
- },
-
- /**
- * Resolve a template from a current template
- * @param {String|null} from
- * @param {String} to
- * @return {String|null}
- */
- resolve: function(from, to) {
- var searchPaths = this.searchPaths;
-
- // Relative template like "./test.html"
- if (PathUtils.isPureRelative(to) && from) {
- return path.resolve(path.dirname(from), to);
- }
-
- // Determine in which search folder we currently are
- var originalSearchPath = this.getSearchPath(from);
- var originalFilename = this.getTemplateName(from);
-
- // If we are including same file from a different search path
- // Slice the search paths to avoid including from previous ones
- if (originalFilename == to) {
- var currentIndex = searchPaths.indexOf(originalSearchPath);
- searchPaths = searchPaths.slice(currentIndex + 1);
- }
-
- // Absolute template to resolve in root folder
- var resultFolder = searchPaths.find(function(basePath) {
- var p = path.resolve(basePath, to);
-
- return (
- p.indexOf(basePath) === 0
- && fs.existsSync(p)
- );
- });
- if (!resultFolder) return null;
- return path.resolve(resultFolder, to);
- }
-});
-
-module.exports = ThemesLoader;
diff --git a/lib/utils/git.js b/lib/utils/git.js
deleted file mode 100644
index 6884b83..0000000
--- a/lib/utils/git.js
+++ /dev/null
@@ -1,133 +0,0 @@
-var is = require('is');
-var path = require('path');
-var crc = require('crc');
-var URI = require('urijs');
-
-var pathUtil = require('./path');
-var Promise = require('./promise');
-var command = require('./command');
-var fs = require('./fs');
-
-var GIT_PREFIX = 'git+';
-
-function Git() {
- this.tmpDir;
- this.cloned = {};
-}
-
-// Return an unique ID for a combinaison host/ref
-Git.prototype.repoID = function(host, ref) {
- return crc.crc32(host+'#'+(ref || '')).toString(16);
-};
-
-// Allocate a temporary folder for cloning repos in it
-Git.prototype.allocateDir = function() {
- var that = this;
-
- if (this.tmpDir) return Promise();
-
- return fs.tmpDir()
- .then(function(dir) {
- that.tmpDir = dir;
- });
-};
-
-// Clone a git repository if non existant
-Git.prototype.clone = function(host, ref) {
- var that = this;
-
- return this.allocateDir()
-
- // Return or clone the git repo
- .then(function() {
- // Unique ID for repo/ref combinaison
- var repoId = that.repoID(host, ref);
-
- // Absolute path to the folder
- var repoPath = path.join(that.tmpDir, repoId);
-
- if (that.cloned[repoId]) return repoPath;
-
- // Clone repo
- return command.exec('git clone '+host+' '+repoPath)
-
- // Checkout reference if specified
- .then(function() {
- that.cloned[repoId] = true;
-
- if (!ref) return;
- return command.exec('git checkout '+ref, { cwd: repoPath });
- })
- .thenResolve(repoPath);
- });
-};
-
-// Get file from a git repo
-Git.prototype.resolve = function(giturl) {
- // Path to a file in a git repo?
- if (!Git.isUrl(giturl)) {
- if (this.resolveRoot(giturl)) return Promise(giturl);
- return Promise(null);
- }
- if (is.string(giturl)) giturl = Git.parseUrl(giturl);
- if (!giturl) return Promise(null);
-
- // Clone or get from cache
- return this.clone(giturl.host, giturl.ref)
- .then(function(repo) {
- return path.resolve(repo, giturl.filepath);
- });
-};
-
-// Return root of git repo from a filepath
-Git.prototype.resolveRoot = function(filepath) {
- var relativeToGit, repoId;
-
- // No git repo cloned, or file is not in a git repository
- if (!this.tmpDir || !pathUtil.isInRoot(this.tmpDir, filepath)) return null;
-
- // Extract first directory (is the repo id)
- relativeToGit = path.relative(this.tmpDir, filepath);
- repoId = relativeToGit.split(path.sep)[0];
- if (!repoId) {
- return;
- }
-
- // Return an absolute file
- return path.resolve(this.tmpDir, repoId);
-};
-
-// Check if an url is a git dependency url
-Git.isUrl = function(giturl) {
- return (giturl.indexOf(GIT_PREFIX) === 0);
-};
-
-// Parse and extract infos
-Git.parseUrl = function(giturl) {
- var ref, uri, fileParts, filepath;
-
- if (!Git.isUrl(giturl)) return null;
- giturl = giturl.slice(GIT_PREFIX.length);
-
- uri = new URI(giturl);
- ref = uri.fragment() || null;
- 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(fileParts[0] + '.git');
-
- return {
- host: uri.toString(),
- ref: ref,
- filepath: filepath
- };
-};
-
-module.exports = Git;
diff --git a/package.json b/package.json
index 3c379e0..53932fa 100644
--- a/package.json
+++ b/package.json
@@ -1,100 +1,28 @@
{
- "name": "gitbook",
- "version": "3.2.2",
- "homepage": "https://www.gitbook.com",
- "description": "Library and cmd utility to generate GitBooks",
- "main": "lib/index.js",
- "browser": "./lib/browser.js",
- "dependencies": {
- "bash-color": "0.0.4",
- "cheerio": "0.20.0",
- "chokidar": "1.5.0",
- "cp": "0.2.0",
- "cpr": "1.1.1",
- "crc": "3.4.0",
- "destroy": "1.0.4",
- "direction": "0.1.5",
- "dom-serializer": "0.1.0",
- "error": "7.0.2",
- "escape-html": "^1.0.3",
- "escape-string-regexp": "1.0.5",
- "extend": "^3.0.0",
- "fresh-require": "1.0.3",
- "front-matter": "^2.1.0",
- "gitbook-asciidoc": "1.2.2",
- "gitbook-markdown": "1.3.2",
- "gitbook-plugin-fontsettings": "2.0.0",
- "gitbook-plugin-highlight": "2.0.2",
- "gitbook-plugin-livereload": "0.0.1",
- "gitbook-plugin-lunr": "1.2.0",
- "gitbook-plugin-search": "2.2.1",
- "gitbook-plugin-sharing": "1.0.2",
- "gitbook-plugin-theme-default": "1.0.6",
- "github-slugid": "1.0.1",
- "graceful-fs": "4.1.4",
- "i18n-t": "1.0.1",
- "ignore": "3.1.2",
- "immutable": "^3.8.1",
- "is": "^3.1.0",
- "js-yaml": "^3.6.1",
- "json-schema-defaults": "0.1.1",
- "jsonschema": "1.1.0",
- "juice": "2.0.0",
- "mkdirp": "0.5.1",
- "moment": "2.13.0",
- "npm": "3.9.2",
- "npmi": "2.0.1",
- "nunjucks": "2.5.2",
- "nunjucks-do": "1.0.0",
- "object-path": "^0.9.2",
- "omit-keys": "^0.1.0",
- "open": "0.0.5",
- "q": "1.4.1",
- "read-installed": "^4.0.3",
- "request": "2.72.0",
- "resolve": "1.1.7",
- "rmdir": "1.2.0",
- "semver": "5.1.0",
- "send": "0.13.2",
- "spawn-cmd": "0.0.2",
- "tiny-lr": "0.2.1",
- "tmp": "0.0.28",
- "urijs": "1.18.0"
- },
+ "private": true,
"devDependencies": {
- "eslint": "2.10.2",
+ "eslint": "3.4.0",
+ "eslint-config-gitbook": "1.3.1",
"expect": "^1.20.1",
+ "lerna": "2.0.0-beta.29",
"mocha": "^2.4.5"
},
"scripts": {
"lint": "eslint .",
- "test": "./node_modules/.bin/mocha ./testing/setup.js \"./lib/**/*/__tests__/*.js\" --bail --reporter=list --timeout=10000"
+ "test": "lerna run --concurrency 1 test",
+ "bootstrap": "npm run bump && lerna bootstrap",
+ "bump": "./scripts/bump.js",
+ "clean": "lerna clean",
+ "dist": "lerna run --concurrency 1 prepublish"
},
"repository": {
"type": "git",
- "url": "https://github.com/GitbookIO/gitbook.git"
- },
- "bin": {
- "gitbook": "./bin/gitbook.js"
+ "url": "git+https://github.com/GitbookIO/gitbook.git"
},
- "keywords": [
- "git",
- "book",
- "gitbook"
- ],
- "author": "FriendCode Inc. <contact@gitbook.com>",
+ "author": "GitBook Inc. <contact@gitbook.com>",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/GitbookIO/gitbook/issues"
},
- "contributors": [
- {
- "name": "Aaron O'Mullan",
- "email": "aaron@gitbook.com"
- },
- {
- "name": "Samy Pessé",
- "email": "samy@gitbook.com"
- }
- ]
+ "homepage": "https://github.com/GitbookIO/gitbook#readme"
}
diff --git a/packages/gitbook-core/.babelrc b/packages/gitbook-core/.babelrc
new file mode 100644
index 0000000..5f27bda
--- /dev/null
+++ b/packages/gitbook-core/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015", "react", "stage-2"]
+}
diff --git a/packages/gitbook-core/.gitignore b/packages/gitbook-core/.gitignore
new file mode 100644
index 0000000..eed58d7
--- /dev/null
+++ b/packages/gitbook-core/.gitignore
@@ -0,0 +1 @@
+gitbook.core.min.js
diff --git a/packages/gitbook-core/.npmignore b/packages/gitbook-core/.npmignore
new file mode 100644
index 0000000..b9cde8e
--- /dev/null
+++ b/packages/gitbook-core/.npmignore
@@ -0,0 +1,3 @@
+src
+!lib
+!dist
diff --git a/packages/gitbook-core/package.json b/packages/gitbook-core/package.json
new file mode 100644
index 0000000..182e6e3
--- /dev/null
+++ b/packages/gitbook-core/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "gitbook-core",
+ "version": "4.0.0",
+ "description": "Core for GitBook plugins API",
+ "main": "./lib/index.js",
+ "dependencies": {
+ "bluebird": "^3.4.6",
+ "classnames": "^2.2.5",
+ "entities": "^1.1.1",
+ "history": "^4.3.0",
+ "html-tags": "^1.1.1",
+ "immutable": "^3.8.1",
+ "mousetrap": "1.6.0",
+ "react": "15.4.1",
+ "react-addons-css-transition-group": "15.4.1",
+ "react-dom": "15.4.1",
+ "react-helmet": "^3.1.0",
+ "react-immutable-proptypes": "^2.1.0",
+ "react-intl": "^2.1.5",
+ "react-redux": "^4.4.5",
+ "react-safe-html": "0.4.0",
+ "redux": "^3.5.2",
+ "redux-thunk": "^2.1.0",
+ "reflexbox": "^2.2.2",
+ "whatwg-fetch": "^1.0.0"
+ },
+ "devDependencies": {
+ "babel-cli": "^6.14.0",
+ "babel-preset-es2015": "^6.14.0",
+ "babel-preset-react": "^6.11.1",
+ "babel-preset-stage-2": "^6.13.0",
+ "browserify": "^13.1.0",
+ "envify": "^3.4.1",
+ "uglify-js": "^2.7.3"
+ },
+ "scripts": {
+ "dist-lib": "rm -rf lib/ && babel -d lib/ src/",
+ "dist-standalone": "mkdir -p dist && browserify -r ./lib/index.js:gitbook-core -r react -r react-dom ./lib/index.js | uglifyjs -c > ./dist/gitbook.core.min.js",
+ "dist": "npm run dist-lib && npm run dist-standalone",
+ "prepublish": "npm run dist"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "author": "GitBook Inc. <contact@gitbook.com>",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-core/src/actions/TYPES.js b/packages/gitbook-core/src/actions/TYPES.js
new file mode 100644
index 0000000..9876057
--- /dev/null
+++ b/packages/gitbook-core/src/actions/TYPES.js
@@ -0,0 +1,16 @@
+
+module.exports = {
+ // Components
+ REGISTER_COMPONENT: 'components/register',
+ UNREGISTER_COMPONENT: 'components/unregister',
+ // Navigation
+ HISTORY_ACTIVATE: 'history/activate',
+ HISTORY_DEACTIVATE: 'history/deactivate',
+ HISTORY_LISTEN: 'history/listen',
+ HISTORY_UPDATE: 'history/update',
+ PAGE_FETCH_START: 'history/fetch:start',
+ PAGE_FETCH_END: 'history/fetch:end',
+ PAGE_FETCH_ERROR: 'history/fetch:error',
+ // i18n
+ I18N_REGISTER_LOCALE: 'i18n/register:locale'
+};
diff --git a/packages/gitbook-core/src/actions/components.js b/packages/gitbook-core/src/actions/components.js
new file mode 100644
index 0000000..f21c382
--- /dev/null
+++ b/packages/gitbook-core/src/actions/components.js
@@ -0,0 +1,37 @@
+const ACTION_TYPES = require('./TYPES');
+
+/**
+ * Find all components matching a descriptor
+ * @param {List<ComponentDescriptor>} state
+ * @param {String} matching.role
+ */
+function findMatchingComponents(state, matching) {
+ return state
+ .filter(({descriptor}) => {
+ if (matching.role && matching.role !== descriptor.role) {
+ return false;
+ }
+
+ return true;
+ })
+ .map(component => component.Component);
+}
+
+/**
+ * Register a new component
+ * @param {React.Class} Component
+ * @param {Descriptor} descriptor
+ * @return {Action}
+ */
+function registerComponent(Component, descriptor) {
+ return {
+ type: ACTION_TYPES.REGISTER_COMPONENT,
+ Component,
+ descriptor
+ };
+}
+
+module.exports = {
+ findMatchingComponents,
+ registerComponent
+};
diff --git a/packages/gitbook-core/src/actions/history.js b/packages/gitbook-core/src/actions/history.js
new file mode 100644
index 0000000..1c33f4a
--- /dev/null
+++ b/packages/gitbook-core/src/actions/history.js
@@ -0,0 +1,188 @@
+const ACTION_TYPES = require('./TYPES');
+const getPayload = require('../lib/getPayload');
+const Location = require('../models/Location');
+
+const SUPPORTED = (
+ typeof window !== 'undefined' &&
+ window.history && window.history.pushState && window.history.replaceState &&
+ // pushState isn't reliable on iOS until 5.
+ !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/)
+);
+
+/**
+ * Initialize the history
+ */
+function activate() {
+ return (dispatch, getState) => {
+ dispatch({
+ type: ACTION_TYPES.HISTORY_ACTIVATE,
+ listener: (location) => {
+ location = Location.fromNative(location);
+ const prevLocation = getState().history.location;
+
+ // Fetch page if required
+ if (!prevLocation || location.pathname !== prevLocation.pathname) {
+ dispatch(fetchPage(location.pathname));
+ }
+
+ // Signal location to listener
+ dispatch(emit());
+
+ // Update the location
+ dispatch({
+ type: ACTION_TYPES.HISTORY_UPDATE,
+ location
+ });
+ }
+ });
+
+ // Trigger for existing listeners
+ dispatch(emit());
+ };
+}
+
+/**
+ * Emit current location
+ * @param {List|Array<Function>} to?
+ */
+function emit(to) {
+ return (dispatch, getState) => {
+ const { listeners, client } = getState().history;
+
+ if (!client) {
+ return;
+ }
+
+ const location = Location.fromNative(client.location);
+
+ to = to || listeners;
+
+ to.forEach(handler => {
+ handler(location, dispatch, getState);
+ });
+ };
+}
+
+/**
+ * Cleanup the history
+ */
+function deactivate() {
+ return { type: ACTION_TYPES.HISTORY_DEACTIVATE };
+}
+
+/**
+ * Push a new url into the history
+ * @param {String|Location} location
+ * @return {Action} action
+ */
+function push(location) {
+ return (dispatch, getState) => {
+ const { client } = getState().history;
+ location = Location.fromNative(location);
+
+ if (SUPPORTED) {
+ client.push(location.toNative());
+ } else {
+ redirect(location.toString());
+ }
+ };
+}
+
+/**
+ * Replace current state in history
+ * @param {String|Location} location
+ * @return {Action} action
+ */
+function replace(location) {
+ return (dispatch, getState) => {
+ const { client } = getState().history;
+ location = Location.fromNative(location);
+
+ if (SUPPORTED) {
+ client.replace(location.toNative());
+ } else {
+ redirect(location.toString());
+ }
+ };
+}
+
+/**
+ * Hard redirection
+ * @param {String} uri
+ * @return {Action} action
+ */
+function redirect(uri) {
+ return () => {
+ window.location.href = uri;
+ };
+}
+
+/**
+ * Listen to url change
+ * @param {Function} listener
+ * @return {Action} action
+ */
+function listen(listener) {
+ return (dispatch, getState) => {
+ dispatch({ type: ACTION_TYPES.HISTORY_LISTEN, listener });
+
+ // Trigger for existing listeners
+ dispatch(emit([ listener ]));
+ };
+}
+
+/**
+ * Fetch a new page and update the store accordingly
+ * @param {String} pathname
+ * @return {Action} action
+ */
+function fetchPage(pathname) {
+ return (dispatch, getState) => {
+ dispatch({ type: ACTION_TYPES.PAGE_FETCH_START });
+
+ window.fetch(pathname, {
+ credentials: 'include'
+ })
+ .then(
+ response => {
+ return response.text();
+ }
+ )
+ .then(
+ html => {
+ const payload = getPayload(html);
+
+ if (!payload) {
+ throw new Error('No payload found in page');
+ }
+
+ dispatch({ type: ACTION_TYPES.PAGE_FETCH_END, payload });
+ }
+ )
+ .catch(
+ error => {
+ // dispatch(redirect(pathname));
+ dispatch({ type: ACTION_TYPES.PAGE_FETCH_ERROR, error });
+ }
+ );
+ };
+}
+
+/**
+ * Fetch a new article
+ * @param {SummaryArticle} article
+ * @return {Action} action
+ */
+function fetchArticle(article) {
+ return fetchPage(article.path);
+}
+
+module.exports = {
+ activate,
+ deactivate,
+ listen,
+ push,
+ replace,
+ fetchPage,
+ fetchArticle
+};
diff --git a/packages/gitbook-core/src/actions/i18n.js b/packages/gitbook-core/src/actions/i18n.js
new file mode 100644
index 0000000..115c5a1
--- /dev/null
+++ b/packages/gitbook-core/src/actions/i18n.js
@@ -0,0 +1,33 @@
+const ACTION_TYPES = require('./TYPES');
+
+/**
+ * Register messages for a locale
+ * @param {String} locale
+ * @param {Map<String:String>} messages
+ * @return {Action}
+ */
+function registerLocale(locale, messages) {
+ return { type: ACTION_TYPES.I18N_REGISTER_LOCALE, locale, messages };
+}
+
+/**
+ * Register multiple locales
+ * @param {Map<String:Object>} locales
+ * @return {Action}
+ */
+function registerLocales(locales) {
+ return (dispatch) => {
+ for (const locale in locales) {
+ if (!locales.hasOwnProperty(locale)) {
+ continue;
+ }
+
+ dispatch(registerLocale(locale, locales[locale]));
+ }
+ };
+}
+
+module.exports = {
+ registerLocale,
+ registerLocales
+};
diff --git a/packages/gitbook-core/src/components/Backdrop.js b/packages/gitbook-core/src/components/Backdrop.js
new file mode 100644
index 0000000..7b34b0d
--- /dev/null
+++ b/packages/gitbook-core/src/components/Backdrop.js
@@ -0,0 +1,56 @@
+const React = require('react');
+const HotKeys = require('./HotKeys');
+
+/**
+ * Backdrop for modals, dropdown, etc. that covers the whole screen
+ * and handles click and pressing escape.
+ *
+ * <Backdrop onClose={onCloseModal} />
+ */
+const Backdrop = React.createClass({
+ propTypes: {
+ // Callback when backdrop is closed
+ onClose: React.PropTypes.func.isRequired,
+ // Z-index for the backdrop
+ zIndex: React.PropTypes.number,
+ children: React.PropTypes.node
+ },
+
+ getDefaultProps() {
+ return {
+ zIndex: 200
+ };
+ },
+
+ onClick(event) {
+ const { onClose } = this.props;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ onClose();
+ },
+
+ render() {
+ const { zIndex, children, onClose } = this.props;
+ const style = {
+ zIndex,
+ position: 'fixed',
+ top: 0,
+ right: 0,
+ width: '100%',
+ height: '100%'
+ };
+ const keyMap = {
+ 'escape': onClose
+ };
+
+ return (
+ <HotKeys keyMap={keyMap}>
+ <div style={style} onClick={this.onClick}>{children}</div>
+ </HotKeys>
+ );
+ }
+});
+
+module.exports = Backdrop;
diff --git a/packages/gitbook-core/src/components/Button.js b/packages/gitbook-core/src/components/Button.js
new file mode 100644
index 0000000..4d929b8
--- /dev/null
+++ b/packages/gitbook-core/src/components/Button.js
@@ -0,0 +1,22 @@
+const React = require('react');
+const classNames = require('classnames');
+
+const Button = React.createClass({
+ propTypes: {
+ active: React.PropTypes.bool,
+ className: React.PropTypes.string,
+ children: React.PropTypes.node,
+ onClick: React.PropTypes.func
+ },
+
+ render() {
+ const { children, active, onClick } = this.props;
+ const className = classNames('GitBook-Button', this.props.className, {
+ active
+ });
+
+ return <button className={className} onClick={onClick}>{children}</button>;
+ }
+});
+
+module.exports = Button;
diff --git a/packages/gitbook-core/src/components/ButtonGroup.js b/packages/gitbook-core/src/components/ButtonGroup.js
new file mode 100644
index 0000000..4c20b68
--- /dev/null
+++ b/packages/gitbook-core/src/components/ButtonGroup.js
@@ -0,0 +1,23 @@
+const React = require('react');
+const classNames = require('classnames');
+
+const ButtonGroup = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ children: React.PropTypes.node,
+ onClick: React.PropTypes.func
+ },
+
+ render() {
+ let { className, children } = this.props;
+
+ className = classNames(
+ 'GitBook-ButtonGroup',
+ className
+ );
+
+ return <div className={className}>{children}</div>;
+ }
+});
+
+module.exports = ButtonGroup;
diff --git a/packages/gitbook-core/src/components/ContextProvider.js b/packages/gitbook-core/src/components/ContextProvider.js
new file mode 100644
index 0000000..96a44e3
--- /dev/null
+++ b/packages/gitbook-core/src/components/ContextProvider.js
@@ -0,0 +1,34 @@
+const React = require('react');
+const { Provider } = require('react-redux');
+
+const ContextShape = require('../propTypes/Context');
+
+/**
+ * React component to provide a GitBook context to children components.
+ */
+
+const ContextProvider = React.createClass({
+ propTypes: {
+ context: ContextShape.isRequired,
+ children: React.PropTypes.node
+ },
+
+ childContextTypes: {
+ gitbook: ContextShape
+ },
+
+ getChildContext() {
+ const { context } = this.props;
+
+ return {
+ gitbook: context
+ };
+ },
+
+ render() {
+ const { context, children } = this.props;
+ return <Provider store={context.store}>{children}</Provider>;
+ }
+});
+
+module.exports = ContextProvider;
diff --git a/packages/gitbook-core/src/components/Dropdown.js b/packages/gitbook-core/src/components/Dropdown.js
new file mode 100644
index 0000000..83a377f
--- /dev/null
+++ b/packages/gitbook-core/src/components/Dropdown.js
@@ -0,0 +1,126 @@
+const React = require('react');
+const classNames = require('classnames');
+
+/**
+ * Dropdown to display a menu
+ *
+ * <Dropdown.Container>
+ *
+ * <Button />
+ *
+ * <Dropdown.Menu>
+ * <Dropdown.Item href={...}> ... </Dropdown.Item>
+ * <Dropdown.Item onClick={...}> ... </Dropdown.Item>
+ * </Dropdown.Menu>
+ * </Dropdown.Container>
+ */
+
+const DropdownContainer = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ let { className, children } = this.props;
+
+ className = classNames(
+ 'GitBook-Dropdown',
+ className
+ );
+
+ return (
+ <div className={className}>
+ {children}
+ </div>
+ );
+ }
+});
+
+/**
+ * A dropdown item which can contains informations.
+ */
+const DropdownItem = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node
+ },
+
+ render() {
+ const { children } = this.props;
+
+ return (
+ <div className="GitBook-DropdownItem">
+ {children}
+ </div>
+ );
+ }
+});
+
+
+/**
+ * A dropdown item, which is always a link.
+ */
+const DropdownItemLink = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node,
+ onClick: React.PropTypes.func,
+ href: React.PropTypes.string
+ },
+
+ onClick(event) {
+ const { onClick, href } = this.props;
+
+ if (href) {
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ if (onClick) {
+ onClick();
+ }
+ },
+
+ render() {
+ const { children, href, ...otherProps } = this.props;
+
+ return (
+ <a {...otherProps} className="GitBook-DropdownItemLink" href={href || '#'} onClick={this.onClick} >
+ {children}
+ </a>
+ );
+ }
+});
+
+
+/**
+ * A DropdownMenu to display DropdownItems. Must be inside a
+ * DropdownContainer.
+ */
+const DropdownMenu = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ let { className, children } = this.props;
+ className = classNames('GitBook-DropdownMenu', className);
+
+ return (
+ <div className={className}>
+ {children}
+ </div>
+ );
+ }
+});
+
+const Dropdown = {
+ Item: DropdownItem,
+ ItemLink: DropdownItemLink,
+ Menu: DropdownMenu,
+ Container: DropdownContainer
+};
+
+module.exports = Dropdown;
diff --git a/packages/gitbook-core/src/components/HTMLContent.js b/packages/gitbook-core/src/components/HTMLContent.js
new file mode 100644
index 0000000..9d15398
--- /dev/null
+++ b/packages/gitbook-core/src/components/HTMLContent.js
@@ -0,0 +1,77 @@
+const React = require('react');
+const ReactSafeHtml = require('react-safe-html');
+const { DOMProperty } = require('react-dom/lib/ReactInjection');
+const htmlTags = require('html-tags');
+const entities = require('entities');
+
+const { InjectedComponent } = require('./InjectedComponent');
+
+DOMProperty.injectDOMPropertyConfig({
+ Properties: {
+ align: DOMProperty.MUST_USE_ATTRIBUTE
+ },
+ isCustomAttribute: (attributeName) => {
+ return attributeName === 'align';
+ }
+});
+
+/*
+ HTMLContent is a container for the page HTML that parse the content and
+ render the right block.
+ All html elements can be extended using the injected component.
+ */
+
+function inject(injectedProps, Component) {
+ return (props) => {
+ const cleanProps = {
+ ...props,
+ className: props['class']
+ };
+ delete cleanProps['class'];
+
+ return (
+ <InjectedComponent {...injectedProps(cleanProps)}>
+ <Component {...cleanProps} />
+ </InjectedComponent>
+ );
+ };
+}
+
+const COMPONENTS = {
+ // Templating blocks are exported as <xblock name="youtube" props="{}" />
+ 'xblock': inject(
+ ({name, props}) => {
+ props = entities.decodeHTML(props);
+ return {
+ matching: { role: `block:${name}` },
+ props: JSON.parse(props)
+ };
+ },
+ props => <div {...props} />
+ )
+};
+
+htmlTags.forEach(tag => {
+ COMPONENTS[tag] = inject(
+ props => {
+ return {
+ matching: { role: `html:${tag}` },
+ props
+ };
+ },
+ props => React.createElement(tag, props)
+ );
+});
+
+const HTMLContent = React.createClass({
+ propTypes: {
+ html: React.PropTypes.string.isRequired
+ },
+
+ render() {
+ const { html } = this.props;
+ return <ReactSafeHtml html={html} components={COMPONENTS} />;
+ }
+});
+
+module.exports = HTMLContent;
diff --git a/packages/gitbook-core/src/components/HotKeys.js b/packages/gitbook-core/src/components/HotKeys.js
new file mode 100644
index 0000000..e2a8154
--- /dev/null
+++ b/packages/gitbook-core/src/components/HotKeys.js
@@ -0,0 +1,59 @@
+const React = require('react');
+const Mousetrap = require('mousetrap');
+const { Map } = require('immutable');
+
+/**
+ * Defines hotkeys globally when this component is mounted.
+ *
+ * keyMap = {
+ * 'escape': (e) => quit()
+ * 'mod+s': (e) => save()
+ * }
+ *
+ * <HotKeys keyMap={keyMap}>
+ * < ... />
+ * </HotKeys>
+ */
+
+const HotKeys = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node.isRequired,
+ keyMap: React.PropTypes.objectOf(React.PropTypes.func)
+ },
+
+ getDefaultProps() {
+ return { keyMap: {} };
+ },
+
+ updateBindings(keyMap) {
+ Map(keyMap).forEach((handler, key) => {
+ Mousetrap.bind(key, handler);
+ });
+ },
+
+ clearBindings(keyMap) {
+ Map(keyMap).forEach((handler, key) => {
+ Mousetrap.unbind(key, handler);
+ });
+ },
+
+ componentDidMount() {
+ this.updateBindings(this.props.keyMap);
+ },
+
+ componentDidUpdate(prevProps) {
+ this.clearBindings(prevProps.keyMap);
+ this.updateBindings(this.props.keyMap);
+ },
+
+ componentWillUnmount() {
+ this.clearBindings(this.props.keyMap);
+ },
+
+ render() {
+ // Simply render the only child
+ return React.Children.only(this.props.children);
+ }
+});
+
+module.exports = HotKeys;
diff --git a/packages/gitbook-core/src/components/I18nProvider.js b/packages/gitbook-core/src/components/I18nProvider.js
new file mode 100644
index 0000000..b6b2d0f
--- /dev/null
+++ b/packages/gitbook-core/src/components/I18nProvider.js
@@ -0,0 +1,28 @@
+const { Map } = require('immutable');
+const React = require('react');
+const intl = require('react-intl');
+const ReactRedux = require('react-redux');
+
+const I18nProvider = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node,
+ messages: React.PropTypes.object
+ },
+
+ render() {
+ let { messages } = this.props;
+ messages = messages.get('en', Map()).toJS();
+
+ return (
+ <intl.IntlProvider locale={'en'} messages={messages}>
+ {this.props.children}
+ </intl.IntlProvider>
+ );
+ }
+});
+
+const mapStateToProps = state => {
+ return { messages: state.i18n.messages };
+};
+
+module.exports = ReactRedux.connect(mapStateToProps)(I18nProvider);
diff --git a/packages/gitbook-core/src/components/Icon.js b/packages/gitbook-core/src/components/Icon.js
new file mode 100644
index 0000000..5f2c751
--- /dev/null
+++ b/packages/gitbook-core/src/components/Icon.js
@@ -0,0 +1,28 @@
+const React = require('react');
+
+const Icon = React.createClass({
+ propTypes: {
+ id: React.PropTypes.string,
+ type: React.PropTypes.string,
+ className: React.PropTypes.string
+ },
+
+ getDefaultProps() {
+ return {
+ type: 'fa'
+ };
+ },
+
+ render() {
+ const { id, type } = this.props;
+ let { className } = this.props;
+
+ if (id) {
+ className = 'GitBook-Icon ' + type + ' ' + type + '-' + id;
+ }
+
+ return <i className={className}/>;
+ }
+});
+
+module.exports = Icon;
diff --git a/packages/gitbook-core/src/components/Import.js b/packages/gitbook-core/src/components/Import.js
new file mode 100644
index 0000000..68318b9
--- /dev/null
+++ b/packages/gitbook-core/src/components/Import.js
@@ -0,0 +1,48 @@
+const React = require('react');
+const Head = require('react-helmet');
+const ReactRedux = require('react-redux');
+
+/**
+ * Resolve a file url to a relative url in current state
+ * @param {String} href
+ * @param {State} state
+ * @return {String}
+ */
+function resolveForCurrentFile(href, state) {
+ const { file } = state;
+ return file.relative(href);
+}
+
+const ImportLink = ReactRedux.connect((state, {rel, href}) => {
+ href = resolveForCurrentFile(href, state);
+
+ return {
+ link: [
+ {
+ rel,
+ href
+ }
+ ]
+ };
+})(Head);
+
+const ImportScript = ReactRedux.connect((state, {type, src}) => {
+ src = resolveForCurrentFile(src, state);
+
+ return {
+ script: [
+ {
+ type,
+ src
+ }
+ ]
+ };
+})(Head);
+
+const ImportCSS = props => <ImportLink rel="stylesheet" {...props} />;
+
+module.exports = {
+ ImportLink,
+ ImportScript,
+ ImportCSS
+};
diff --git a/packages/gitbook-core/src/components/InjectedComponent.js b/packages/gitbook-core/src/components/InjectedComponent.js
new file mode 100644
index 0000000..097edaf
--- /dev/null
+++ b/packages/gitbook-core/src/components/InjectedComponent.js
@@ -0,0 +1,117 @@
+const React = require('react');
+const ReactRedux = require('react-redux');
+const { List } = require('immutable');
+
+const { findMatchingComponents } = require('../actions/components');
+
+/*
+ Public: InjectedComponent makes it easy to include a set of dynamically registered
+ components inside of your React render method. Rather than explicitly render
+ an array of buttons, for example, you can use InjectedComponentSet:
+
+ ```js
+ <InjectedComponentSet className="message-actions"
+ matching={{role: 'ThreadActionButton'}}
+ props={{ a: 1 }}>
+ ```
+
+ InjectedComponentSet will look up components registered for the location you provide,
+ render them inside a {Flexbox} and pass them `exposedProps`. By default, all injected
+ children are rendered inside {UnsafeComponent} wrappers to prevent third-party code
+ from throwing exceptions that break React renders.
+
+ InjectedComponentSet monitors the ComponentStore for changes. If a new component
+ is registered into the location you provide, InjectedComponentSet will re-render.
+ If no matching components is found, the InjectedComponent renders an empty span.
+ */
+
+const Injection = React.createClass({
+ propTypes: {
+ component: React.PropTypes.func,
+ props: React.PropTypes.object,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ const Comp = this.props.component;
+ const { props, children } = this.props;
+
+ // TODO: try to render with an error handling for unsafe component
+ return <Comp {...props}>{children}</Comp>;
+ }
+});
+
+const InjectedComponentSet = React.createClass({
+ propTypes: {
+ components: React.PropTypes.oneOfType([
+ React.PropTypes.arrayOf(React.PropTypes.func),
+ React.PropTypes.instanceOf(List)
+ ]).isRequired,
+ props: React.PropTypes.object,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ const { components, props, children, ...divProps } = this.props;
+
+ const inner = components.map((Comp, i) => <Injection key={i} component={Comp} props={props} />);
+
+ return (
+ <div {...divProps}>
+ {children}
+ {inner}
+ </div>
+ );
+ }
+});
+
+/**
+ * Render only the first component matching
+ */
+const InjectedComponent = React.createClass({
+ propTypes: {
+ components: React.PropTypes.oneOfType([
+ React.PropTypes.arrayOf(React.PropTypes.func),
+ React.PropTypes.instanceOf(List)
+ ]).isRequired,
+ props: React.PropTypes.object,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ let { components, props, children } = this.props;
+
+ if (!children) {
+ children = null;
+ } else {
+ children = React.Children.only(children);
+ }
+
+ return components.reduce((inner, Comp) => {
+ return (
+ <Injection component={Comp} props={props}>
+ {inner}
+ </Injection>
+ );
+ }, children);
+ }
+});
+
+/**
+ * Map Redux state to InjectedComponentSet's props
+ */
+function mapStateToProps(state, props) {
+ const { components } = state;
+ const { matching } = props;
+
+ return {
+ components: findMatchingComponents(components, matching)
+ };
+}
+
+const connect = ReactRedux.connect(mapStateToProps);
+
+module.exports = {
+ InjectedComponent: connect(InjectedComponent),
+ InjectedComponentSet: connect(InjectedComponentSet)
+};
diff --git a/packages/gitbook-core/src/components/Link.js b/packages/gitbook-core/src/components/Link.js
new file mode 100644
index 0000000..ab364bb
--- /dev/null
+++ b/packages/gitbook-core/src/components/Link.js
@@ -0,0 +1,37 @@
+const React = require('react');
+const ReactRedux = require('react-redux');
+
+const File = require('../models/File');
+const SummaryArticle = require('../models/SummaryArticle');
+const SummaryArticleShape = require('../propTypes/SummaryArticle');
+const FileShape = require('../propTypes/File');
+
+const Link = React.createClass({
+ propTypes: {
+ currentFile: FileShape,
+ children: React.PropTypes.node,
+
+ // Destination of the link
+ to: React.PropTypes.oneOfType([
+ React.PropTypes.string,
+ SummaryArticleShape,
+ FileShape
+ ])
+ },
+
+ render() {
+ const { currentFile, to, children, ...props } = this.props;
+ let href = to;
+
+ if (SummaryArticle.is(to) || File.is(to)) {
+ href = to.url;
+ }
+
+ href = currentFile.relative(href);
+ return <a href={href} {...props}>{children}</a>;
+ }
+});
+
+module.exports = ReactRedux.connect(state => {
+ return { currentFile: state.file };
+})(Link);
diff --git a/packages/gitbook-core/src/components/PJAXWrapper.js b/packages/gitbook-core/src/components/PJAXWrapper.js
new file mode 100644
index 0000000..6ed0697
--- /dev/null
+++ b/packages/gitbook-core/src/components/PJAXWrapper.js
@@ -0,0 +1,102 @@
+const React = require('react');
+const ReactRedux = require('react-redux');
+const History = require('../actions/history');
+
+/**
+ * Check if an element is inside a link
+ * @param {DOMElement} el
+ * @param {String} name
+ * @return {DOMElement|undefined
+ */
+function findParentByTagName(el, name) {
+ while (el && el !== document) {
+ if (el.tagName && el.tagName.toUpperCase() === name) {
+ return el;
+ }
+ el = el.parentNode;
+ }
+
+ return false;
+}
+
+/**
+ * Internal: Return the `href` component of given URL object with the hash
+ * portion removed.
+ *
+ * @param {Location|HTMLAnchorElement} location
+ * @return {String}
+ */
+function stripHash(location) {
+ return location.href.replace(/#.*/, '');
+}
+
+/**
+ * Test if a click event should be handled,
+ * return the new url if it's a normal lcick
+ */
+function getHrefForEvent(event) {
+ const link = findParentByTagName(event.target, 'A');
+
+ if (!link)
+ return;
+
+ // Middle click, cmd click, and ctrl click should open
+ // links in a new tab as normal.
+ if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
+ return;
+
+ // Ignore cross origin links
+ if (location.protocol !== link.protocol || location.hostname !== link.hostname)
+ return;
+
+ // Ignore case when a hash is being tacked on the current URL
+ if (link.href.indexOf('#') > -1 && stripHash(link) == stripHash(location))
+ return;
+
+ // Ignore event with default prevented
+ if (event.defaultPrevented)
+ return;
+
+ // Explicitly ignored
+ if (link.getAttribute('data-nopjax'))
+ return;
+
+ return link.pathname;
+}
+
+/*
+ Wrapper to bind all navigation events to fetch pages.
+ */
+
+const PJAXWrapper = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node,
+ dispatch: React.PropTypes.func
+ },
+
+ onClick(event) {
+ const { dispatch } = this.props;
+ const href = getHrefForEvent(event);
+
+ if (!href) {
+ return;
+ }
+
+ event.preventDefault();
+ dispatch(History.push(href));
+ },
+
+ componentDidMount() {
+ document.addEventListener('click', this.onClick, false);
+ },
+
+ componentWillUnmount() {
+ document.removeEventListener('click', this.onClick, false);
+ },
+
+ render() {
+ return React.Children.only(this.props.children);
+ }
+});
+
+module.exports = ReactRedux.connect()(PJAXWrapper);
diff --git a/packages/gitbook-core/src/components/Panel.js b/packages/gitbook-core/src/components/Panel.js
new file mode 100644
index 0000000..694cc29
--- /dev/null
+++ b/packages/gitbook-core/src/components/Panel.js
@@ -0,0 +1,22 @@
+const React = require('react');
+const classNames = require('classnames');
+
+const Panel = React.createClass({
+ propTypes: {
+ className: React.PropTypes.string,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ let { className, children } = this.props;
+ className = classNames('GitBook-Panel', className);
+
+ return (
+ <div className={className}>
+ {children}
+ </div>
+ );
+ }
+});
+
+module.exports = Panel;
diff --git a/packages/gitbook-core/src/components/Tooltipped.js b/packages/gitbook-core/src/components/Tooltipped.js
new file mode 100644
index 0000000..4d297fd
--- /dev/null
+++ b/packages/gitbook-core/src/components/Tooltipped.js
@@ -0,0 +1,44 @@
+const React = require('react');
+const classNames = require('classnames');
+
+const POSITIONS = {
+ BOTTOM_RIGHT: 'e',
+ BOTTOM_LEFT: 'w',
+ TOP_LEFT: 'nw',
+ TOP_RIGHT: 'ne',
+ BOTTOM: '',
+ TOP: 'n'
+};
+
+const Tooltipped = React.createClass({
+ propTypes: {
+ title: React.PropTypes.string.isRequired,
+ position: React.PropTypes.string,
+ open: React.PropTypes.bool,
+ children: React.PropTypes.node
+ },
+
+ statics: {
+ POSITIONS
+ },
+
+ render() {
+ const { title, position, open, children } = this.props;
+
+ const className = classNames(
+ 'GitBook-Tooltipped',
+ position ? 'Tooltipped-' + position : '',
+ {
+ 'Tooltipped-o': open
+ }
+ );
+
+ return (
+ <div className={className} aria-label={title}>
+ {children}
+ </div>
+ );
+ }
+});
+
+module.exports = Tooltipped;
diff --git a/packages/gitbook-core/src/index.js b/packages/gitbook-core/src/index.js
new file mode 100644
index 0000000..3f0120c
--- /dev/null
+++ b/packages/gitbook-core/src/index.js
@@ -0,0 +1,73 @@
+require('whatwg-fetch');
+
+const React = require('react');
+const ReactCSSTransitionGroup = require('react-addons-css-transition-group');
+const Immutable = require('immutable');
+const Head = require('react-helmet');
+const Promise = require('bluebird');
+const { Provider } = require('react-redux');
+const { Flex, Box } = require('reflexbox');
+
+const { InjectedComponent, InjectedComponentSet } = require('./components/InjectedComponent');
+const { ImportLink, ImportScript, ImportCSS } = require('./components/Import');
+const HTMLContent = require('./components/HTMLContent');
+const Link = require('./components/Link');
+const Icon = require('./components/Icon');
+const HotKeys = require('./components/HotKeys');
+const Button = require('./components/Button');
+const ButtonGroup = require('./components/ButtonGroup');
+const Dropdown = require('./components/Dropdown');
+const Panel = require('./components/Panel');
+const Backdrop = require('./components/Backdrop');
+const Tooltipped = require('./components/Tooltipped');
+const I18nProvider = require('./components/I18nProvider');
+
+const ACTIONS = require('./actions/TYPES');
+
+const PropTypes = require('./propTypes');
+const connect = require('./lib/connect');
+const createPlugin = require('./lib/createPlugin');
+const createReducer = require('./lib/createReducer');
+const createContext = require('./lib/createContext');
+const composeReducer = require('./lib/composeReducer');
+const bootstrap = require('./lib/bootstrap');
+const renderWithContext = require('./lib/renderWithContext');
+
+module.exports = {
+ ACTIONS,
+ bootstrap,
+ renderWithContext,
+ connect,
+ createPlugin,
+ createReducer,
+ createContext,
+ composeReducer,
+ // React Components
+ I18nProvider,
+ InjectedComponent,
+ InjectedComponentSet,
+ HTMLContent,
+ Head,
+ Panel,
+ Provider,
+ ImportLink,
+ ImportScript,
+ ImportCSS,
+ FlexLayout: Flex,
+ FlexBox: Box,
+ Link,
+ Icon,
+ HotKeys,
+ Button,
+ ButtonGroup,
+ Dropdown,
+ Backdrop,
+ Tooltipped,
+ // Utilities
+ PropTypes,
+ // Librairies
+ React,
+ ReactCSSTransitionGroup,
+ Immutable,
+ Promise
+};
diff --git a/packages/gitbook-core/src/lib/bootstrap.js b/packages/gitbook-core/src/lib/bootstrap.js
new file mode 100644
index 0000000..f3c99b7
--- /dev/null
+++ b/packages/gitbook-core/src/lib/bootstrap.js
@@ -0,0 +1,29 @@
+const ReactDOM = require('react-dom');
+
+const getPayload = require('./getPayload');
+const createContext = require('./createContext');
+const renderWithContext = require('./renderWithContext');
+
+/**
+ * Bootstrap GitBook on the browser (this function should not be called on the server side).
+ * @param {Object} matching
+ */
+function bootstrap(matching) {
+ const initialState = getPayload(window.document);
+ const plugins = window.gitbookPlugins;
+
+ const mountNode = document.getElementById('content');
+
+ // Create the redux store
+ const context = createContext(plugins, initialState);
+
+ window.gitbookContext = context;
+
+ // Render with the store
+ const el = renderWithContext(context, matching);
+
+ ReactDOM.render(el, mountNode);
+}
+
+
+module.exports = bootstrap;
diff --git a/packages/gitbook-core/src/lib/composeReducer.js b/packages/gitbook-core/src/lib/composeReducer.js
new file mode 100644
index 0000000..fa2a589
--- /dev/null
+++ b/packages/gitbook-core/src/lib/composeReducer.js
@@ -0,0 +1,16 @@
+
+/**
+ * Compose multiple reducers into one
+ * @param {Function} reducers
+ * @return {Function}
+ */
+function composeReducer(...reducers) {
+ return (state, action) => {
+ return reducers.reduce(
+ (newState, reducer) => reducer(newState, action),
+ state
+ );
+ };
+}
+
+module.exports = composeReducer;
diff --git a/packages/gitbook-core/src/lib/connect.js b/packages/gitbook-core/src/lib/connect.js
new file mode 100644
index 0000000..a34299d
--- /dev/null
+++ b/packages/gitbook-core/src/lib/connect.js
@@ -0,0 +1,70 @@
+const React = require('react');
+const ReactRedux = require('react-redux');
+const { injectIntl } = require('react-intl');
+
+const ContextShape = require('../propTypes/Context');
+
+/**
+ * Use the GitBook context provided by ContextProvider to map actions to props
+ * @param {ReactComponent} Component
+ * @param {Function} mapActionsToProps
+ * @return {ReactComponent}
+ */
+function connectToActions(Component, mapActionsToProps) {
+ if (!mapActionsToProps) {
+ return Component;
+ }
+
+ return React.createClass({
+ displayName: `ConnectActions(${Component.displayName})`,
+ propTypes: {
+ children: React.PropTypes.node
+ },
+
+ contextTypes: {
+ gitbook: ContextShape.isRequired
+ },
+
+ render() {
+ const { gitbook } = this.context;
+ const { children, ...props } = this.props;
+ const { actions, store } = gitbook;
+
+ const actionsProps = mapActionsToProps(actions, store.dispatch);
+
+ return <Component {...props} {...actionsProps}>{children}</Component>;
+ }
+ });
+}
+
+/**
+ * Connect to i18n
+ * @param {ReactComponent} Component
+ * @return {ReactComponent}
+ */
+function connectToI18n(Component) {
+ return injectIntl(({intl, children, ...props}) => {
+ const i18n = {
+ t: (id, values) => intl.formatMessage({ id }, values)
+ };
+
+ return <Component {...props} i18n={i18n}>{children}</Component>;
+ });
+}
+
+/**
+ * Connect a component to the GitBook context (store and actions).
+ *
+ * @param {ReactComponent} Component
+ * @param {Function} mapStateToProps
+ * @return {ReactComponent}
+ */
+function connect(Component, mapStateToProps, mapActionsToProps) {
+ Component = ReactRedux.connect(mapStateToProps)(Component);
+ Component = connectToI18n(Component);
+ Component = connectToActions(Component, mapActionsToProps);
+
+ return Component;
+}
+
+module.exports = connect;
diff --git a/packages/gitbook-core/src/lib/createContext.js b/packages/gitbook-core/src/lib/createContext.js
new file mode 100644
index 0000000..ba0c7e1
--- /dev/null
+++ b/packages/gitbook-core/src/lib/createContext.js
@@ -0,0 +1,76 @@
+/* eslint-disable no-console */
+const Redux = require('redux');
+const ReduxThunk = require('redux-thunk').default;
+
+const Plugin = require('../models/Plugin');
+const Context = require('../models/Context');
+const coreReducers = require('../reducers');
+const composeReducer = require('./composeReducer');
+
+const Components = require('../actions/components');
+const I18n = require('../actions/i18n');
+const History = require('../actions/history');
+
+const isBrowser = (typeof window !== 'undefined');
+
+/**
+ * The core plugin defines the defualt behaviour of GitBook and provides
+ * actions to other plugins.
+ * @type {Plugin}
+ */
+const corePlugin = new Plugin({
+ reduce: coreReducers,
+ actions: {
+ Components, I18n, History
+ }
+});
+
+/**
+ * Create a new context containing redux store from an initial state and a list of plugins.
+ * Each plugin entry is the result of {createPlugin}.
+ *
+ * @param {Array<Plugin>} plugins
+ * @param {Object} initialState
+ * @return {Context} context
+ */
+function createContext(plugins, initialState) {
+ plugins = [corePlugin].concat(plugins);
+
+ // Compose the reducer from core with plugins
+ const pluginReducers = plugins.map(plugin => plugin.reduce);
+ const reducer = composeReducer(...pluginReducers);
+
+ // Get actions from all plugins
+ const actions = plugins.reduce((accu, plugin) => {
+ return Object.assign(accu, plugin.actions);
+ }, {});
+
+ // Create thunk middleware which include actions
+ const thunk = ReduxThunk.withExtraArgument(actions);
+
+ // Create the redux store
+ const store = Redux.createStore(
+ (state, action) => {
+ if (isBrowser) {
+ console.log('[store]', action.type);
+ }
+ return reducer(state, action);
+ },
+ initialState,
+ Redux.compose(Redux.applyMiddleware(thunk))
+ );
+
+ // Create the context
+ const context = new Context({
+ store,
+ plugins,
+ actions
+ });
+
+ // Initialize the plugins
+ context.activate();
+
+ return context;
+}
+
+module.exports = createContext;
diff --git a/packages/gitbook-core/src/lib/createPlugin.js b/packages/gitbook-core/src/lib/createPlugin.js
new file mode 100644
index 0000000..cb5d2be
--- /dev/null
+++ b/packages/gitbook-core/src/lib/createPlugin.js
@@ -0,0 +1,27 @@
+const Plugin = require('../models/Plugin');
+
+/**
+ * Create a plugin to extend the state and the views.
+ *
+ * @param {Function(dispatch, state)} plugin.init
+ * @param {Function(state, action)} plugin.reduce
+ * @param {Object} plugin.actions
+ * @return {Plugin}
+ */
+function createPlugin({ activate, deactivate, reduce, actions }) {
+ const plugin = new Plugin({
+ activate,
+ deactivate,
+ reduce,
+ actions
+ });
+
+ if (typeof window !== 'undefined') {
+ window.gitbookPlugins = window.gitbookPlugins || [];
+ window.gitbookPlugins.push(plugin);
+ }
+
+ return plugin;
+}
+
+module.exports = createPlugin;
diff --git a/packages/gitbook-core/src/lib/createReducer.js b/packages/gitbook-core/src/lib/createReducer.js
new file mode 100644
index 0000000..2ebecfb
--- /dev/null
+++ b/packages/gitbook-core/src/lib/createReducer.js
@@ -0,0 +1,27 @@
+
+/**
+ * Helper to create a reducer that extend the store.
+ *
+ * @param {String} property
+ * @param {Function(state, action): state} reduce
+ * @return {Function(state, action): state}
+ */
+function createReducer(name, reduce) {
+ return (state, action) => {
+ const value = state[name];
+ const newValue = reduce(value, action);
+
+ if (newValue === value) {
+ return state;
+ }
+
+ const newState = {
+ ...state,
+ [name]: newValue
+ };
+
+ return newState;
+ };
+}
+
+module.exports = createReducer;
diff --git a/packages/gitbook-core/src/lib/getPayload.js b/packages/gitbook-core/src/lib/getPayload.js
new file mode 100644
index 0000000..2d54b9e
--- /dev/null
+++ b/packages/gitbook-core/src/lib/getPayload.js
@@ -0,0 +1,19 @@
+
+/**
+ * Get the payload for a GitBook page
+ * @param {String|DOMDocument} html
+ * @return {Object}
+ */
+function getPayload(html) {
+ if (typeof html === 'string') {
+ const parser = new DOMParser();
+ html = parser.parseFromString(html, 'text/html');
+ }
+
+ const script = html.querySelector('script[type="application/payload+json"]');
+ const payload = JSON.parse(script.innerHTML);
+
+ return payload;
+}
+
+module.exports = getPayload;
diff --git a/packages/gitbook-core/src/lib/renderWithContext.js b/packages/gitbook-core/src/lib/renderWithContext.js
new file mode 100644
index 0000000..dc7e1f2
--- /dev/null
+++ b/packages/gitbook-core/src/lib/renderWithContext.js
@@ -0,0 +1,55 @@
+const React = require('react');
+
+const { InjectedComponent } = require('../components/InjectedComponent');
+const PJAXWrapper = require('../components/PJAXWrapper');
+const I18nProvider = require('../components/I18nProvider');
+const ContextProvider = require('../components/ContextProvider');
+const History = require('../actions/history');
+const contextShape = require('../propTypes/context');
+
+const GitBookApplication = React.createClass({
+ propTypes: {
+ context: contextShape,
+ matching: React.PropTypes.object
+ },
+
+ componentDidMount() {
+ const { context } = this.props;
+ context.dispatch(History.activate());
+ },
+
+ componentWillUnmount() {
+ const { context } = this.props;
+ context.dispatch(History.deactivate());
+ },
+
+ render() {
+ const { context, matching } = this.props;
+
+ return (
+ <ContextProvider context={context}>
+ <PJAXWrapper>
+ <I18nProvider>
+ <InjectedComponent matching={matching} />
+ </I18nProvider>
+ </PJAXWrapper>
+ </ContextProvider>
+ );
+ }
+});
+
+
+/**
+ * Render the application for a GitBook context.
+ *
+ * @param {GitBookContext} context
+ * @param {Object} matching
+ * @return {React.Element} element
+ */
+function renderWithContext(context, matching) {
+ return (
+ <GitBookApplication context={context} matching={matching} />
+ );
+}
+
+module.exports = renderWithContext;
diff --git a/packages/gitbook-core/src/models/Context.js b/packages/gitbook-core/src/models/Context.js
new file mode 100644
index 0000000..f4b0d4c
--- /dev/null
+++ b/packages/gitbook-core/src/models/Context.js
@@ -0,0 +1,58 @@
+const { Record, List } = require('immutable');
+
+const DEFAULTS = {
+ store: null,
+ actions: {},
+ plugins: List()
+};
+
+class Context extends Record(DEFAULTS) {
+ constructor(...args) {
+ super(...args);
+
+ this.dispatch = this.dispatch.bind(this);
+ this.getState = this.getState.bind(this);
+ }
+
+ /**
+ * Return current state
+ * @return {Object}
+ */
+ getState() {
+ const { store } = this;
+ return store.getState();
+ }
+
+ /**
+ * Dispatch an action
+ * @param {Action} action
+ */
+ dispatch(action) {
+ const { store } = this;
+ return store.dispatch(action);
+ }
+
+ /**
+ * Deactivate the context, cleanup resources from plugins.
+ */
+ deactivate() {
+ const { plugins, actions } = this;
+
+ plugins.forEach(plugin => {
+ plugin.deactivate(this.dispatch, this.getState, actions);
+ });
+ }
+
+ /**
+ * Activate the context and the plugins.
+ */
+ activate() {
+ const { plugins, actions } = this;
+
+ plugins.forEach(plugin => {
+ plugin.activate(this.dispatch, this.getState, actions);
+ });
+ }
+}
+
+module.exports = Context;
diff --git a/packages/gitbook-core/src/models/File.js b/packages/gitbook-core/src/models/File.js
new file mode 100644
index 0000000..efc4f11
--- /dev/null
+++ b/packages/gitbook-core/src/models/File.js
@@ -0,0 +1,54 @@
+const path = require('path');
+const { Record } = require('immutable');
+
+const DEFAULTS = {
+ type: '',
+ mtime: new Date(),
+ path: '',
+ url: ''
+};
+
+class File extends Record(DEFAULTS) {
+ constructor(file = {}) {
+ if (typeof file === 'string') {
+ file = { path: file, url: file };
+ }
+
+ super({
+ ...file,
+ mtime: new Date(file.mtime)
+ });
+ }
+
+ /**
+ * @param {String} to Absolute path
+ * @return {String} The same path, but relative to this file
+ */
+ relative(to) {
+ return path.relative(
+ path.dirname(this.path),
+ to
+ ) || './';
+ }
+
+ /**
+ * Return true if file is an instance of File
+ * @param {Mixed} file
+ * @return {Boolean} isFile
+ */
+ static is(file) {
+ return (file instanceof File);
+ }
+
+ /**
+ * Create a file instance
+ * @param {Mixed|File} file
+ * @return {File} file
+ */
+ static create(file) {
+ return File.is(file) ?
+ file : new File(file);
+ }
+}
+
+module.exports = File;
diff --git a/packages/gitbook-core/src/models/Language.js b/packages/gitbook-core/src/models/Language.js
new file mode 100644
index 0000000..20fc237
--- /dev/null
+++ b/packages/gitbook-core/src/models/Language.js
@@ -0,0 +1,12 @@
+const { Record } = require('immutable');
+
+const DEFAULTS = {
+ id: null,
+ title: null
+};
+
+class Language extends Record(DEFAULTS) {
+
+}
+
+module.exports = Language;
diff --git a/packages/gitbook-core/src/models/Languages.js b/packages/gitbook-core/src/models/Languages.js
new file mode 100644
index 0000000..b698d14
--- /dev/null
+++ b/packages/gitbook-core/src/models/Languages.js
@@ -0,0 +1,40 @@
+const { Record, List } = require('immutable');
+const Language = require('./Language');
+const File = require('./File');
+
+const DEFAULTS = {
+ current: String(),
+ file: new File(),
+ list: List()
+};
+
+class Languages extends Record(DEFAULTS) {
+ constructor(spec = {}) {
+ super({
+ ...spec,
+ file: File.create(spec.file),
+ list: List(spec.list).map(lang => new Language(lang))
+ });
+ }
+
+ /**
+ * Return true if file is an instance of Languages
+ * @param {Mixed} langs
+ * @return {Boolean}
+ */
+ static is(langs) {
+ return (langs instanceof Languages);
+ }
+
+ /**
+ * Create a Languages instance
+ * @param {Mixed|Languages} langs
+ * @return {Languages}
+ */
+ static create(langs) {
+ return Languages.is(langs) ?
+ langs : new Languages(langs);
+ }
+}
+
+module.exports = Languages;
diff --git a/packages/gitbook-core/src/models/Location.js b/packages/gitbook-core/src/models/Location.js
new file mode 100644
index 0000000..cdfea2d
--- /dev/null
+++ b/packages/gitbook-core/src/models/Location.js
@@ -0,0 +1,78 @@
+const { Record, Map } = require('immutable');
+const querystring = require('querystring');
+
+const DEFAULTS = {
+ pathname: String(''),
+ // Hash without the #
+ hash: String(''),
+ // If query is a non empty map
+ query: Map()
+};
+
+class Location extends Record(DEFAULTS) {
+
+ /**
+ * Return search query as a string
+ * @return {String}
+ */
+ get search() {
+ const { query } = this;
+ return query.size === 0 ?
+ '' :
+ '?' + querystring.stringify(query.toJS());
+ }
+
+ /**
+ * Convert this location to a string.
+ * @return {String}
+ */
+ toString() {
+
+ }
+
+ /**
+ * Convert this immutable instance to an object
+ * for "history".
+ * @return {Object}
+ */
+ toNative() {
+ return {
+ pathname: this.pathname,
+ hash: this.hash ? `#${this.hash}` : '',
+ search: this.search
+ };
+ }
+
+ /**
+ * Convert an instance from "history" to Location.
+ * @param {Object|String} location
+ * @return {Location}
+ */
+ static fromNative(location) {
+ if (typeof location === 'string') {
+ location = { pathname: location };
+ }
+
+ const pathname = location.pathname;
+ let hash = location.hash || '';
+ let search = location.search || '';
+ let query = location.query;
+
+ hash = hash[0] === '#' ? hash.slice(1) : hash;
+ search = search[0] === '?' ? search.slice(1) : search;
+
+ if (query) {
+ query = Map(query);
+ } else {
+ query = Map(querystring.parse(search));
+ }
+
+ return new Location({
+ pathname,
+ hash,
+ query
+ });
+ }
+}
+
+module.exports = Location;
diff --git a/packages/gitbook-core/src/models/Page.js b/packages/gitbook-core/src/models/Page.js
new file mode 100644
index 0000000..e3c4a96
--- /dev/null
+++ b/packages/gitbook-core/src/models/Page.js
@@ -0,0 +1,24 @@
+const { Record, Map, fromJS } = require('immutable');
+
+const DEFAULTS = {
+ title: '',
+ content: '',
+ dir: 'ltr',
+ depth: 1,
+ level: '',
+ previous: null,
+ next: null,
+ attributes: Map()
+};
+
+class Page extends Record(DEFAULTS) {
+ static create(state) {
+ return state instanceof Page ?
+ state : new Page({
+ ...state,
+ attributes: fromJS(state.attributes)
+ });
+ }
+}
+
+module.exports = Page;
diff --git a/packages/gitbook-core/src/models/Plugin.js b/packages/gitbook-core/src/models/Plugin.js
new file mode 100644
index 0000000..07b1976
--- /dev/null
+++ b/packages/gitbook-core/src/models/Plugin.js
@@ -0,0 +1,21 @@
+const { Record } = require('immutable');
+
+const DEFAULTS = {
+ activate: ((dispatch, getState) => {}),
+ deactivate: ((dispatch, getState) => {}),
+ reduce: ((state, action) => state),
+ actions: {}
+};
+
+class Plugin extends Record(DEFAULTS) {
+ constructor(plugin) {
+ super({
+ activate: plugin.activate || DEFAULTS.activate,
+ deactivate: plugin.deactivate || DEFAULTS.deactivate,
+ reduce: plugin.reduce || DEFAULTS.reduce,
+ actions: plugin.actions || DEFAULTS.actions
+ });
+ }
+}
+
+module.exports = Plugin;
diff --git a/packages/gitbook-core/src/models/Readme.js b/packages/gitbook-core/src/models/Readme.js
new file mode 100644
index 0000000..f275ca2
--- /dev/null
+++ b/packages/gitbook-core/src/models/Readme.js
@@ -0,0 +1,21 @@
+const { Record } = require('immutable');
+const File = require('./File');
+
+const DEFAULTS = {
+ file: new File()
+};
+
+class Readme extends Record(DEFAULTS) {
+ constructor(state = {}) {
+ super({
+ file: File.create(state.file)
+ });
+ }
+
+ static create(state) {
+ return state instanceof Readme ?
+ state : new Readme(state);
+ }
+}
+
+module.exports = Readme;
diff --git a/packages/gitbook-core/src/models/SummaryArticle.js b/packages/gitbook-core/src/models/SummaryArticle.js
new file mode 100644
index 0000000..3651c8a
--- /dev/null
+++ b/packages/gitbook-core/src/models/SummaryArticle.js
@@ -0,0 +1,32 @@
+const { Record, List } = require('immutable');
+
+const DEFAULTS = {
+ title: '',
+ depth: 0,
+ path: '',
+ url: '',
+ ref: '',
+ level: '',
+ articles: List()
+};
+
+class SummaryArticle extends Record(DEFAULTS) {
+ constructor(article) {
+ super({
+ ...article,
+ articles: (new List(article.articles))
+ .map(art => new SummaryArticle(art))
+ });
+ }
+
+ /**
+ * Return true if article is an instance of SummaryArticle
+ * @param {Mixed} article
+ * @return {Boolean}
+ */
+ static is(article) {
+ return (article instanceof SummaryArticle);
+ }
+}
+
+module.exports = SummaryArticle;
diff --git a/packages/gitbook-core/src/models/SummaryPart.js b/packages/gitbook-core/src/models/SummaryPart.js
new file mode 100644
index 0000000..89c76d4
--- /dev/null
+++ b/packages/gitbook-core/src/models/SummaryPart.js
@@ -0,0 +1,17 @@
+const { Record, List } = require('immutable');
+const SummaryArticle = require('./SummaryArticle');
+
+class SummaryPart extends Record({
+ title: '',
+ articles: List()
+}) {
+ constructor(state) {
+ super({
+ ...state,
+ articles: (new List(state.articles))
+ .map(article => new SummaryArticle(article))
+ });
+ }
+}
+
+module.exports = SummaryPart;
diff --git a/packages/gitbook-core/src/propTypes/Context.js b/packages/gitbook-core/src/propTypes/Context.js
new file mode 100644
index 0000000..dd6d010
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/Context.js
@@ -0,0 +1,11 @@
+const React = require('react');
+const {
+ object,
+ shape
+} = React.PropTypes;
+
+
+module.exports = shape({
+ store: object,
+ actions: object
+});
diff --git a/packages/gitbook-core/src/propTypes/File.js b/packages/gitbook-core/src/propTypes/File.js
new file mode 100644
index 0000000..fb7bc06
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/File.js
@@ -0,0 +1,13 @@
+const React = require('react');
+const {
+ oneOf,
+ string,
+ instanceOf,
+ shape
+} = React.PropTypes;
+
+module.exports = shape({
+ mtime: instanceOf(Date).isRequired,
+ path: string.isRequired,
+ type: oneOf(['', 'markdown', 'asciidoc']).isRequired
+});
diff --git a/packages/gitbook-core/src/propTypes/History.js b/packages/gitbook-core/src/propTypes/History.js
new file mode 100644
index 0000000..1b59ea0
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/History.js
@@ -0,0 +1,11 @@
+const React = require('react');
+const locationShape = require('./Location');
+const {
+ bool,
+ shape
+} = React.PropTypes;
+
+module.exports = shape({
+ loading: bool,
+ location: locationShape
+});
diff --git a/packages/gitbook-core/src/propTypes/Language.js b/packages/gitbook-core/src/propTypes/Language.js
new file mode 100644
index 0000000..eea37a7
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/Language.js
@@ -0,0 +1,7 @@
+const React = require('react');
+const { string, shape } = React.PropTypes;
+
+module.exports = shape({
+ id: string.isRequired,
+ title: string.isRequired
+});
diff --git a/packages/gitbook-core/src/propTypes/Languages.js b/packages/gitbook-core/src/propTypes/Languages.js
new file mode 100644
index 0000000..076aec5
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/Languages.js
@@ -0,0 +1,12 @@
+const React = require('react');
+const { listOf } = require('react-immutable-proptypes');
+const { shape, string } = React.PropTypes;
+
+const fileShape = require('./File');
+const languageShape = require('./Language');
+
+module.exports = shape({
+ current: string.isRequired,
+ file: fileShape.isRequired,
+ list: listOf(languageShape).isRequired
+});
diff --git a/packages/gitbook-core/src/propTypes/Location.js b/packages/gitbook-core/src/propTypes/Location.js
new file mode 100644
index 0000000..13e0a34
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/Location.js
@@ -0,0 +1,12 @@
+const React = require('react');
+const { map } = require('react-immutable-proptypes');
+const {
+ string,
+ shape
+} = React.PropTypes;
+
+module.exports = shape({
+ pathname: string,
+ hash: string,
+ query: map
+});
diff --git a/packages/gitbook-core/src/propTypes/Page.js b/packages/gitbook-core/src/propTypes/Page.js
new file mode 100644
index 0000000..c589f54
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/Page.js
@@ -0,0 +1,16 @@
+const React = require('react');
+const {
+ oneOf,
+ string,
+ number,
+ shape
+} = React.PropTypes;
+
+
+module.exports = shape({
+ title: string.isRequired,
+ content: string.isRequired,
+ level: string.isRequired,
+ depth: number.isRequired,
+ dir: oneOf(['ltr', 'rtl']).isRequired
+});
diff --git a/packages/gitbook-core/src/propTypes/Readme.js b/packages/gitbook-core/src/propTypes/Readme.js
new file mode 100644
index 0000000..8414f05
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/Readme.js
@@ -0,0 +1,11 @@
+const React = require('react');
+
+const {
+ shape
+} = React.PropTypes;
+
+const File = require('./File');
+
+module.exports = shape({
+ file: File.isRequired
+});
diff --git a/packages/gitbook-core/src/propTypes/Summary.js b/packages/gitbook-core/src/propTypes/Summary.js
new file mode 100644
index 0000000..f97e66c
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/Summary.js
@@ -0,0 +1,14 @@
+const React = require('react');
+const { listOf } = require('react-immutable-proptypes');
+
+const {
+ shape
+} = React.PropTypes;
+
+const File = require('./File');
+const Part = require('./SummaryPart');
+
+module.exports = shape({
+ file: File.isRequired,
+ parts: listOf(Part).isRequired
+});
diff --git a/packages/gitbook-core/src/propTypes/SummaryArticle.js b/packages/gitbook-core/src/propTypes/SummaryArticle.js
new file mode 100644
index 0000000..c93bdd9
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/SummaryArticle.js
@@ -0,0 +1,22 @@
+/* eslint-disable no-use-before-define */
+
+const React = require('react');
+const { list } = require('react-immutable-proptypes');
+
+const {
+ string,
+ number,
+ shape
+} = React.PropTypes;
+
+const Article = shape({
+ title: string.isRequired,
+ depth: number.isRequired,
+ path: string,
+ url: string,
+ ref: string,
+ level: string,
+ articles: list
+});
+
+module.exports = Article;
diff --git a/packages/gitbook-core/src/propTypes/SummaryPart.js b/packages/gitbook-core/src/propTypes/SummaryPart.js
new file mode 100644
index 0000000..769ddd1
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/SummaryPart.js
@@ -0,0 +1,14 @@
+const React = require('react');
+const { listOf } = require('react-immutable-proptypes');
+
+const {
+ string,
+ shape
+} = React.PropTypes;
+
+const Article = require('./SummaryArticle');
+
+module.exports = shape({
+ title: string.isRequired,
+ articles: listOf(Article)
+});
diff --git a/packages/gitbook-core/src/propTypes/i18n.js b/packages/gitbook-core/src/propTypes/i18n.js
new file mode 100644
index 0000000..372a240
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/i18n.js
@@ -0,0 +1,10 @@
+const React = require('react');
+const {
+ func,
+ shape
+} = React.PropTypes;
+
+
+module.exports = shape({
+ t: func
+});
diff --git a/packages/gitbook-core/src/propTypes/index.js b/packages/gitbook-core/src/propTypes/index.js
new file mode 100644
index 0000000..f56b78c
--- /dev/null
+++ b/packages/gitbook-core/src/propTypes/index.js
@@ -0,0 +1,19 @@
+const React = require('react');
+const ImmutablePropTypes = require('react-immutable-proptypes');
+
+module.exports = {
+ ...ImmutablePropTypes,
+ dispatch: React.PropTypes.func,
+ I18n: require('./i18n'),
+ Context: require('./Context'),
+ Page: require('./Page'),
+ File: require('./File'),
+ History: require('./History'),
+ Language: require('./Language'),
+ Languages: require('./Languages'),
+ Location: require('./Location'),
+ Readme: require('./Readme'),
+ Summary: require('./Summary'),
+ SummaryPart: require('./SummaryPart'),
+ SummaryArticle: require('./SummaryArticle')
+};
diff --git a/packages/gitbook-core/src/reducers/components.js b/packages/gitbook-core/src/reducers/components.js
new file mode 100644
index 0000000..948a3ac
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/components.js
@@ -0,0 +1,20 @@
+const { List } = require('immutable');
+const ACTION_TYPES = require('../actions/TYPES');
+
+function reduceComponents(state, action) {
+ state = state || List();
+ switch (action.type) {
+
+ case ACTION_TYPES.REGISTER_COMPONENT:
+ return state.push({
+ Component: action.Component,
+ descriptor: action.descriptor
+ });
+
+ default:
+ return state;
+
+ }
+}
+
+module.exports = reduceComponents;
diff --git a/packages/gitbook-core/src/reducers/config.js b/packages/gitbook-core/src/reducers/config.js
new file mode 100644
index 0000000..a49c602
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/config.js
@@ -0,0 +1,15 @@
+const { fromJS } = require('immutable');
+const ACTION_TYPES = require('../actions/TYPES');
+
+module.exports = (state, action) => {
+ state = fromJS(state);
+ switch (action.type) {
+
+ case ACTION_TYPES.PAGE_FETCH_END:
+ return fromJS(action.payload.config);
+
+ default:
+ return state;
+
+ }
+};
diff --git a/packages/gitbook-core/src/reducers/file.js b/packages/gitbook-core/src/reducers/file.js
new file mode 100644
index 0000000..82b0f42
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/file.js
@@ -0,0 +1,16 @@
+const ACTION_TYPES = require('../actions/TYPES');
+const File = require('../models/File');
+
+module.exports = (state, action) => {
+ state = File.create(state);
+
+ switch (action.type) {
+
+ case ACTION_TYPES.PAGE_FETCH_END:
+ return state.merge(action.payload.file);
+
+ default:
+ return state;
+
+ }
+};
diff --git a/packages/gitbook-core/src/reducers/history.js b/packages/gitbook-core/src/reducers/history.js
new file mode 100644
index 0000000..be8fe42
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/history.js
@@ -0,0 +1,82 @@
+const { Record, List } = require('immutable');
+const { createBrowserHistory, createMemoryHistory } = require('history');
+const ACTION_TYPES = require('../actions/TYPES');
+const Location = require('../models/Location');
+
+const isServerSide = (typeof window === 'undefined');
+
+const HistoryState = Record({
+ // Current location
+ location: new Location(),
+ // Are we loading a new page
+ loading: Boolean(false),
+ // Did we fail loading a page?
+ error: null,
+ // Listener for history changes
+ listeners: List(),
+ // Function to call to stop listening
+ unlisten: null,
+ // HistoryJS instance
+ client: null
+});
+
+function reduceHistory(state, action) {
+ state = state || HistoryState();
+ switch (action.type) {
+
+ case ACTION_TYPES.PAGE_FETCH_START:
+ return state.merge({
+ loading: true
+ });
+
+ case ACTION_TYPES.PAGE_FETCH_END:
+ return state.merge({
+ loading: false
+ });
+
+ case ACTION_TYPES.PAGE_FETCH_ERROR:
+ return state.merge({
+ loading: false,
+ error: action.error
+ });
+
+ case ACTION_TYPES.HISTORY_ACTIVATE:
+ const client = isServerSide ? createMemoryHistory() : createBrowserHistory();
+ const unlisten = client.listen(action.listener);
+
+ // We can't use .merge since it convert history to an immutable
+ const newState = state
+ // TODO: we should find a way to have the correct location on server side
+ .set('location', isServerSide ? new Location() : Location.fromNative(window.location))
+ .set('client', client)
+ .set('unlisten', unlisten);
+
+ return newState;
+
+ case ACTION_TYPES.HISTORY_DEACTIVATE:
+ if (state.unlisten) {
+ state.unlisten();
+ }
+
+ return state.merge({
+ client: null,
+ unlisten: null
+ });
+
+ case ACTION_TYPES.HISTORY_UPDATE:
+ return state.merge({
+ location: action.location
+ });
+
+ case ACTION_TYPES.HISTORY_LISTEN:
+ return state.merge({
+ listeners: state.listeners.push(action.listener)
+ });
+
+ default:
+ return state;
+
+ }
+}
+
+module.exports = reduceHistory;
diff --git a/packages/gitbook-core/src/reducers/i18n.js b/packages/gitbook-core/src/reducers/i18n.js
new file mode 100644
index 0000000..4ffd129
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/i18n.js
@@ -0,0 +1,27 @@
+const { Record, Map } = require('immutable');
+const ACTION_TYPES = require('../actions/TYPES');
+
+const I18nState = Record({
+ locale: 'en',
+ // Map of locale -> Map<String:String>
+ messages: Map()
+});
+
+function reduceI18n(state, action) {
+ state = state || I18nState();
+ switch (action.type) {
+
+ case ACTION_TYPES.I18N_REGISTER_LOCALE:
+ return state.merge({
+ messages: state.messages.set(action.locale,
+ state.messages.get(action.locale, Map()).merge(action.messages)
+ )
+ });
+
+ default:
+ return state;
+
+ }
+}
+
+module.exports = reduceI18n;
diff --git a/packages/gitbook-core/src/reducers/index.js b/packages/gitbook-core/src/reducers/index.js
new file mode 100644
index 0000000..a211d3b
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/index.js
@@ -0,0 +1,15 @@
+const composeReducer = require('../lib/composeReducer');
+const createReducer = require('../lib/createReducer');
+
+module.exports = composeReducer(
+ createReducer('components', require('./components')),
+ createReducer('history', require('./history')),
+ createReducer('i18n', require('./i18n')),
+ // GitBook JSON
+ createReducer('config', require('./config')),
+ createReducer('file', require('./file')),
+ createReducer('page', require('./page')),
+ createReducer('summary', require('./summary')),
+ createReducer('readme', require('./readme')),
+ createReducer('languages', require('./languages'))
+);
diff --git a/packages/gitbook-core/src/reducers/languages.js b/packages/gitbook-core/src/reducers/languages.js
new file mode 100644
index 0000000..0ec2ae4
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/languages.js
@@ -0,0 +1,12 @@
+const Languages = require('../models/Languages');
+
+module.exports = (state, action) => {
+ state = Languages.create(state);
+
+ switch (action.type) {
+
+ default:
+ return state;
+
+ }
+};
diff --git a/packages/gitbook-core/src/reducers/page.js b/packages/gitbook-core/src/reducers/page.js
new file mode 100644
index 0000000..9b94d1e
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/page.js
@@ -0,0 +1,16 @@
+const ACTION_TYPES = require('../actions/TYPES');
+const Page = require('../models/Page');
+
+module.exports = (state, action) => {
+ state = Page.create(state);
+
+ switch (action.type) {
+
+ case ACTION_TYPES.PAGE_FETCH_END:
+ return state.merge(action.payload.page);
+
+ default:
+ return state;
+
+ }
+};
diff --git a/packages/gitbook-core/src/reducers/readme.js b/packages/gitbook-core/src/reducers/readme.js
new file mode 100644
index 0000000..9e8656a
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/readme.js
@@ -0,0 +1,5 @@
+const Readme = require('../models/Readme');
+
+module.exports = (state, action) => {
+ return Readme.create(state);
+};
diff --git a/packages/gitbook-core/src/reducers/summary.js b/packages/gitbook-core/src/reducers/summary.js
new file mode 100644
index 0000000..60568ef
--- /dev/null
+++ b/packages/gitbook-core/src/reducers/summary.js
@@ -0,0 +1,28 @@
+const { Record, List } = require('immutable');
+
+const File = require('../models/File');
+const SummaryPart = require('../models/SummaryPart');
+
+
+class SummaryState extends Record({
+ file: new File(),
+ parts: List()
+}) {
+ constructor(state = {}) {
+ super({
+ ...state,
+ file: new File(state.file),
+ parts: (new List(state.parts))
+ .map(article => new SummaryPart(article))
+ });
+ }
+
+ static create(state) {
+ return state instanceof SummaryState ?
+ state : new SummaryState(state);
+ }
+}
+
+module.exports = (state, action) => {
+ return SummaryState.create(state);
+};
diff --git a/packages/gitbook-core/src/server.js b/packages/gitbook-core/src/server.js
new file mode 100644
index 0000000..0363aa0
--- /dev/null
+++ b/packages/gitbook-core/src/server.js
@@ -0,0 +1,2 @@
+const ReactDOMServer = require('react-dom/server');
+module.exports = ReactDOMServer;
diff --git a/packages/gitbook-plugin-copy-code/.gitignore b/packages/gitbook-plugin-copy-code/.gitignore
new file mode 100644
index 0000000..ef47881
--- /dev/null
+++ b/packages/gitbook-plugin-copy-code/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin-copy-code/.npmignore b/packages/gitbook-plugin-copy-code/.npmignore
new file mode 100644
index 0000000..a0e53cf
--- /dev/null
+++ b/packages/gitbook-plugin-copy-code/.npmignore
@@ -0,0 +1,2 @@
+# Publish assets on NPM
+!_assets/plugin.js
diff --git a/packages/gitbook-plugin-copy-code/_assets/website/button.css b/packages/gitbook-plugin-copy-code/_assets/website/button.css
new file mode 100644
index 0000000..2fd034e
--- /dev/null
+++ b/packages/gitbook-plugin-copy-code/_assets/website/button.css
@@ -0,0 +1,27 @@
+.CodeBlockWithCopy-Container {
+ position: relative;
+}
+
+.CodeBlockWithCopy-Button {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ padding: 3px 6px;
+ margin: 0px;
+ text-transform: uppercase;
+ border-radius: 3px;
+ line-height: 1em;
+ font-size: 12px;
+ border: 1px solid rgba(0,0,0, 0.1);
+ color: rgba(0,0,0, 0.4);
+ cursor: pointer;
+ display: none;
+}
+
+.CodeBlockWithCopy-Container:hover .CodeBlockWithCopy-Button {
+ display: block;
+}
+
+.CodeBlockWithCopy-Button:hover {
+ border-color: rgba(0,0,0, 0.2);
+}
diff --git a/packages/gitbook-plugin-copy-code/index.js b/packages/gitbook-plugin-copy-code/index.js
new file mode 100644
index 0000000..e542ae8
--- /dev/null
+++ b/packages/gitbook-plugin-copy-code/index.js
@@ -0,0 +1,10 @@
+
+module.exports = {
+ blocks: {
+
+ },
+
+ hooks: {
+
+ }
+};
diff --git a/packages/gitbook-plugin-copy-code/package.json b/packages/gitbook-plugin-copy-code/package.json
new file mode 100644
index 0000000..b25ca43
--- /dev/null
+++ b/packages/gitbook-plugin-copy-code/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "gitbook-plugin-copy-code",
+ "description": "Button to copy code blocks",
+ "main": "index.js",
+ "browser": "./_assets/plugin.js",
+ "version": "4.0.0",
+ "dependencies": {
+ "copy-to-clipboard": "^3.0.5",
+ "gitbook-core": "4.0.0"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
+ "engines": {
+ "gitbook": ">=3.0.0"
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitbookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-plugin-copy-code/src/index.js b/packages/gitbook-plugin-copy-code/src/index.js
new file mode 100644
index 0000000..73d46c6
--- /dev/null
+++ b/packages/gitbook-plugin-copy-code/src/index.js
@@ -0,0 +1,82 @@
+const copy = require('copy-to-clipboard');
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const COPIED_TIMEOUT = 1000;
+
+/**
+ * Get children as text
+ * @param {React.Children} children
+ * @return {String}
+ */
+function getChildrenToText(children) {
+ return React.Children.map(children, child => {
+ if (typeof child === 'string') {
+ return child;
+ } else {
+ return child.props.children ?
+ getChildrenToText(child.props.children) : '';
+ }
+ }).join('');
+}
+
+let CodeBlockWithCopy = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node,
+ i18n: GitBook.PropTypes.I18n
+ },
+
+ getInitialState() {
+ return {
+ copied: false
+ };
+ },
+
+ onClick(event) {
+ const { children } = this.props;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ const text = getChildrenToText(children);
+ copy(text);
+
+ this.setState({ copied: true }, () => {
+ this.timeout = setTimeout(() => {
+ this.setState({
+ copied: false
+ });
+ }, COPIED_TIMEOUT);
+ });
+ },
+
+ componentWillUnmount() {
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ }
+ },
+
+ render() {
+ const { children, i18n } = this.props;
+ const { copied } = this.state;
+
+ return (
+ <div className="CodeBlockWithCopy-Container">
+ <GitBook.ImportCSS href="gitbook/copy-code/button.css" />
+
+ {children}
+ <span className="CodeBlockWithCopy-Button" onClick={this.onClick}>
+ {copied ? i18n.t('COPIED') : i18n.t('COPY')}
+ </span>
+ </div>
+ );
+ }
+});
+
+CodeBlockWithCopy = GitBook.connect(CodeBlockWithCopy);
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { Components }) => {
+ dispatch(Components.registerComponent(CodeBlockWithCopy, { role: 'html:pre' }));
+ }
+});
diff --git a/packages/gitbook-plugin-headings/.gitignore b/packages/gitbook-plugin-headings/.gitignore
new file mode 100644
index 0000000..ef47881
--- /dev/null
+++ b/packages/gitbook-plugin-headings/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin-headings/.npmignore b/packages/gitbook-plugin-headings/.npmignore
new file mode 100644
index 0000000..a0e53cf
--- /dev/null
+++ b/packages/gitbook-plugin-headings/.npmignore
@@ -0,0 +1,2 @@
+# Publish assets on NPM
+!_assets/plugin.js
diff --git a/packages/gitbook-plugin-headings/_assets/website/headings.css b/packages/gitbook-plugin-headings/_assets/website/headings.css
new file mode 100644
index 0000000..1ef0d64
--- /dev/null
+++ b/packages/gitbook-plugin-headings/_assets/website/headings.css
@@ -0,0 +1,41 @@
+
+.Headings-Container {
+ position: relative;
+ margin-left: -30px;
+ padding-left: 30px;
+}
+
+/* Left anchors rules */
+.Headings-Container > .Headings-Anchor-Left {
+ position: absolute;
+ left: 5px;
+ top: 50%;
+ transform: translateY(-50%);
+ opacity: 0;
+ color: inherit;
+}
+
+/* Right anchors rules */
+.Headings-Container > .Headings-Anchor-Right {
+ padding-left: 5px;
+ opacity: 0;
+ color: inherit;
+}
+
+.Headings-Container.Headings-Right > h1,
+.Headings-Container.Headings-Right > h2,
+.Headings-Container.Headings-Right > h3,
+.Headings-Container.Headings-Right > h4,
+.Headings-Container.Headings-Right > h5,
+.Headings-Container.Headings-Right > h6 {
+ display: inline-block;
+ margin-right: 5px;
+}
+
+/* Display on hover */
+.Headings-Container:hover > .Headings-Anchor-Left,
+.Headings-Container > .Headings-Anchor-Left:focus,
+.Headings-Container:hover > .Headings-Anchor-Right,
+.Headings-Container > .Headings-Anchor-Right:focus {
+ opacity: 1;
+}
diff --git a/packages/gitbook-plugin-headings/index.js b/packages/gitbook-plugin-headings/index.js
new file mode 100644
index 0000000..e542ae8
--- /dev/null
+++ b/packages/gitbook-plugin-headings/index.js
@@ -0,0 +1,10 @@
+
+module.exports = {
+ blocks: {
+
+ },
+
+ hooks: {
+
+ }
+};
diff --git a/packages/gitbook-plugin-headings/package.json b/packages/gitbook-plugin-headings/package.json
new file mode 100644
index 0000000..55a06f6
--- /dev/null
+++ b/packages/gitbook-plugin-headings/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "gitbook-plugin-headings",
+ "description": "Automatically add anchors to headings",
+ "main": "index.js",
+ "browser": "./_assets/plugin.js",
+ "version": "4.0.0",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "gitbook-core": "4.0.0"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
+ "engines": {
+ "gitbook": ">=3.0.0"
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitbookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ },
+ "gitbook": {
+ "properties": {
+ "position": {
+ "type": "string",
+ "title": "Position of anchors",
+ "default": "left"
+ }
+ }
+ }
+}
diff --git a/packages/gitbook-plugin-headings/src/index.js b/packages/gitbook-plugin-headings/src/index.js
new file mode 100644
index 0000000..b023e2e
--- /dev/null
+++ b/packages/gitbook-plugin-headings/src/index.js
@@ -0,0 +1,60 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+const classNames = require('classnames');
+
+function mapStateToProps({ config }) {
+ return {
+ position: config.getIn(['pluginsConfig', 'headings', 'position'], 'left')
+ };
+}
+
+let Heading = React.createClass({
+ propTypes: {
+ id: React.PropTypes.string.isRequired,
+ children: React.PropTypes.node.isRequired,
+ position: React.PropTypes.string.isRequired
+ },
+
+ render() {
+ const { position, children, id } = this.props;
+ const className = classNames('Headings-Container', {
+ 'Headings-Right': (position !== 'left')
+ });
+
+ return (
+ <div className={className}>
+ <GitBook.ImportCSS href="gitbook/headings/headings.css" />
+
+ {position == 'left' ?
+ <a className="Headings-Anchor-Left" href={`#${id}`}>
+ <i className="fa fa-link" />
+ </a>
+ : null}
+
+ {children}
+
+ {position != 'left' ?
+ <a className="Headings-Anchor-Right" href={`#${id}`}>
+ <i className="fa fa-link" />
+ </a>
+ : null}
+ </div>
+ );
+ }
+});
+
+Heading = GitBook.connect(Heading, mapStateToProps);
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { Components }) => {
+ // Attach component to titles
+ dispatch(Components.registerComponent(Heading, { role: 'html:h1' }));
+ dispatch(Components.registerComponent(Heading, { role: 'html:h2' }));
+ dispatch(Components.registerComponent(Heading, { role: 'html:h3' }));
+ dispatch(Components.registerComponent(Heading, { role: 'html:h4' }));
+ dispatch(Components.registerComponent(Heading, { role: 'html:h5' }));
+ dispatch(Components.registerComponent(Heading, { role: 'html:h6' }));
+ },
+ deactivate: (dispatch, getState) => {},
+ reduce: (state, action) => state
+});
diff --git a/packages/gitbook-plugin-highlight/.gitignore b/packages/gitbook-plugin-highlight/.gitignore
new file mode 100644
index 0000000..ef47881
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin-highlight/.npmignore b/packages/gitbook-plugin-highlight/.npmignore
new file mode 100644
index 0000000..a0e53cf
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/.npmignore
@@ -0,0 +1,2 @@
+# Publish assets on NPM
+!_assets/plugin.js
diff --git a/packages/gitbook-plugin-highlight/_assets/website/white.css b/packages/gitbook-plugin-highlight/_assets/website/white.css
new file mode 100644
index 0000000..d59f1d4
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/_assets/website/white.css
@@ -0,0 +1,92 @@
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+
+/* Tomorrow Comment */
+.hljs-comment,
+.hljs-title {
+ color: #8e908c;
+}
+
+/* Tomorrow Red */
+.hljs-variable,
+.hljs-attribute,
+.hljs-tag,
+.hljs-regexp,
+.hljs-deletion,
+.ruby .hljs-constant,
+.xml .hljs-tag .hljs-title,
+.xml .hljs-pi,
+.xml .hljs-doctype,
+.html .hljs-doctype,
+.css .hljs-id,
+.css .hljs-class,
+.css .hljs-pseudo {
+ color: #c82829;
+}
+
+/* Tomorrow Orange */
+.hljs-number,
+.hljs-preprocessor,
+.hljs-pragma,
+.hljs-built_in,
+.hljs-literal,
+.hljs-params,
+.hljs-constant {
+ color: #f5871f;
+}
+
+/* Tomorrow Yellow */
+.ruby .hljs-class .hljs-title,
+.css .hljs-rules .hljs-attribute {
+ color: #eab700;
+}
+
+/* Tomorrow Green */
+.hljs-string,
+.hljs-value,
+.hljs-inheritance,
+.hljs-header,
+.hljs-addition,
+.ruby .hljs-symbol,
+.xml .hljs-cdata {
+ color: #718c00;
+}
+
+/* Tomorrow Aqua */
+.css .hljs-hexcolor {
+ color: #3e999f;
+}
+
+/* Tomorrow Blue */
+.hljs-function,
+.python .hljs-decorator,
+.python .hljs-title,
+.ruby .hljs-function .hljs-title,
+.ruby .hljs-title .hljs-keyword,
+.perl .hljs-sub,
+.javascript .hljs-title,
+.coffeescript .hljs-title {
+ color: #4271ae;
+}
+
+/* Tomorrow Purple */
+.hljs-keyword,
+.javascript .hljs-function {
+ color: #8959a8;
+}
+
+.hljs {
+ display: block;
+ background: white;
+ color: #4d4d4c;
+ padding: 0.5em;
+}
+
+.coffeescript .javascript,
+.javascript .xml,
+.tex .hljs-formula,
+.xml .javascript,
+.xml .vbscript,
+.xml .css,
+.xml .hljs-cdata {
+ opacity: 0.5;
+}
diff --git a/packages/gitbook-plugin-highlight/index.js b/packages/gitbook-plugin-highlight/index.js
new file mode 100644
index 0000000..e542ae8
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/index.js
@@ -0,0 +1,10 @@
+
+module.exports = {
+ blocks: {
+
+ },
+
+ hooks: {
+
+ }
+};
diff --git a/packages/gitbook-plugin-highlight/package.json b/packages/gitbook-plugin-highlight/package.json
new file mode 100644
index 0000000..ce8b8d6
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "gitbook-plugin-highlight",
+ "description": "Syntax highlighter for Gitbook",
+ "main": "index.js",
+ "browser": "./_assets/plugin.js",
+ "version": "4.0.0",
+ "dependencies": {
+ "gitbook-core": "4.0.0",
+ "highlight.js": "9.7.0"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
+ "engines": {
+ "gitbook": ">=3.0.0"
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitbookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-plugin-highlight/src/ALIASES.js b/packages/gitbook-plugin-highlight/src/ALIASES.js
new file mode 100644
index 0000000..799efef
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/src/ALIASES.js
@@ -0,0 +1,10 @@
+
+const ALIASES = {
+ 'py': 'python',
+ 'js': 'javascript',
+ 'json': 'javascript',
+ 'rb': 'ruby',
+ 'csharp': 'cs'
+};
+
+module.exports = ALIASES;
diff --git a/packages/gitbook-plugin-highlight/src/CodeBlock.js b/packages/gitbook-plugin-highlight/src/CodeBlock.js
new file mode 100644
index 0000000..a556d36
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/src/CodeBlock.js
@@ -0,0 +1,55 @@
+const hljs = require('highlight.js');
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const getLanguage = require('./getLanguage');
+
+/**
+ * Get children as text
+ * @param {React.Children} children
+ * @return {String}
+ */
+function getChildrenToText(children) {
+ return React.Children.map(children, child => {
+ if (typeof child === 'string') {
+ return child;
+ } else {
+ return child.props.children ?
+ getChildrenToText(child.props.children) : '';
+ }
+ }).join('');
+}
+
+const CodeBlock = React.createClass({
+ propTypes: {
+ children: React.PropTypes.node,
+ className: React.PropTypes.string
+ },
+
+ render() {
+ const { children, className } = this.props;
+ const content = getChildrenToText(children);
+ const lang = getLanguage(className || '');
+
+ const includeCSS = <GitBook.ImportCSS href="gitbook/highlight/white.css" />;
+
+ try {
+ const html = hljs.highlight(lang, content).value;
+ return (
+ <code>
+ {includeCSS}
+ <span dangerouslySetInnerHTML={{__html: html}} />
+ </code>
+ );
+ } catch (e) {
+ return (
+ <code>
+ {includeCSS}
+ {content}
+ </code>
+ );
+ }
+ }
+});
+
+module.exports = CodeBlock;
diff --git a/packages/gitbook-plugin-highlight/src/getLanguage.js b/packages/gitbook-plugin-highlight/src/getLanguage.js
new file mode 100644
index 0000000..7a1bf8e
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/src/getLanguage.js
@@ -0,0 +1,34 @@
+const GitBook = require('gitbook-core');
+const { List } = GitBook.Immutable;
+
+const ALIASES = require('./ALIASES');
+
+/**
+ * Return language for a code blocks from a list of class names
+ *
+ * @param {String} className
+ * @return {String}
+ */
+function getLanguage(className) {
+ const lang = List(className.split(' '))
+ .map(function(cl) {
+ // Markdown
+ if (cl.search('lang-') === 0) {
+ return cl.slice('lang-'.length);
+ }
+
+ // Asciidoc
+ if (cl.search('language-') === 0) {
+ return cl.slice('language-'.length);
+ }
+
+ return null;
+ })
+ .find(function(cl) {
+ return Boolean(cl);
+ });
+
+ return ALIASES[lang] || lang;
+}
+
+module.exports = getLanguage;
diff --git a/packages/gitbook-plugin-highlight/src/index.js b/packages/gitbook-plugin-highlight/src/index.js
new file mode 100644
index 0000000..3f17c42
--- /dev/null
+++ b/packages/gitbook-plugin-highlight/src/index.js
@@ -0,0 +1,9 @@
+const GitBook = require('gitbook-core');
+const CodeBlock = require('./CodeBlock');
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { Components }) => {
+ dispatch(Components.registerComponent(CodeBlock, { role: 'html:code' }));
+ },
+ reduce: (state, action) => state
+});
diff --git a/packages/gitbook-plugin-hints/.gitignore b/packages/gitbook-plugin-hints/.gitignore
new file mode 100644
index 0000000..ef47881
--- /dev/null
+++ b/packages/gitbook-plugin-hints/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin-hints/.npmignore b/packages/gitbook-plugin-hints/.npmignore
new file mode 100644
index 0000000..a0e53cf
--- /dev/null
+++ b/packages/gitbook-plugin-hints/.npmignore
@@ -0,0 +1,2 @@
+# Publish assets on NPM
+!_assets/plugin.js
diff --git a/packages/gitbook-plugin-hints/README.md b/packages/gitbook-plugin-hints/README.md
new file mode 100644
index 0000000..9952b97
--- /dev/null
+++ b/packages/gitbook-plugin-hints/README.md
@@ -0,0 +1,41 @@
+Styled hint blocks in your docs
+==============
+
+This plugins requires gitbook `>=4.0.0`.
+
+### Install
+
+Add the below to your `book.json` file, then run `gitbook install` :
+
+```json
+{
+ "plugins": ["hints"]
+}
+```
+
+### Usage
+
+You can now provide hints in various ways using the `hint` tag.
+
+```markdown
+{% hint style='info' %}
+Important info: this note needs to be highlighted
+{% endhint %}
+```
+
+##### Styles
+
+Available styles are:
+
+- `info` (default)
+- `tip`
+- `danger`
+- `warning`
+
+##### Custom Icons
+
+```markdown
+{% hint style='info' icon="mail" %}
+Important info: this note needs to be highlighted
+{% endhint %}
+```
diff --git a/packages/gitbook-plugin-hints/_assets/website/plugin.css b/packages/gitbook-plugin-hints/_assets/website/plugin.css
new file mode 100644
index 0000000..343201b
--- /dev/null
+++ b/packages/gitbook-plugin-hints/_assets/website/plugin.css
@@ -0,0 +1,43 @@
+.HintAlert {
+ padding: 10px;
+ border-radius: 3px;
+ display: flex;
+ margin-bottom: 1.275em;
+}
+
+.HintAlert-Icon {
+ flex: 0;
+ padding: 10px 20px;
+ font-size: 24px;
+}
+
+.HintAlert-Content {
+ flex: auto;
+ padding: 10px;
+ padding-left: 0px;
+}
+
+/* Styles */
+.HintAlert-Style-info, .HintAlert-Style-tip {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+.HintAlert-Style-success {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.HintAlert-Style-danger {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1;
+}
+
+.HintAlert-Style-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+}
diff --git a/packages/gitbook-plugin-hints/index.js b/packages/gitbook-plugin-hints/index.js
new file mode 100644
index 0000000..c762232
--- /dev/null
+++ b/packages/gitbook-plugin-hints/index.js
@@ -0,0 +1,12 @@
+
+module.exports = {
+ blocks: {
+ hint: ({ kwargs, children }) => {
+ return {
+ children,
+ style: kwargs.style || 'info',
+ icon: kwargs.icon
+ };
+ }
+ }
+};
diff --git a/packages/gitbook-plugin-hints/package.json b/packages/gitbook-plugin-hints/package.json
new file mode 100644
index 0000000..3afad4d
--- /dev/null
+++ b/packages/gitbook-plugin-hints/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "gitbook-plugin-hints",
+ "description": "Defines four types of styled hint blocks: info, danger, tip, working.",
+ "main": "index.js",
+ "browser": "./_assets/plugin.js",
+ "version": "4.0.0",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "gitbook-core": "4.0.0"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
+ "engines": {
+ "gitbook": ">=4.0.0"
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitBookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitBookIO/gitbook.git"
+ },
+ "bugs": {
+ "url": "https://github.com/GitBookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-plugin-hints/src/index.js b/packages/gitbook-plugin-hints/src/index.js
new file mode 100644
index 0000000..2ee8a1f
--- /dev/null
+++ b/packages/gitbook-plugin-hints/src/index.js
@@ -0,0 +1,45 @@
+const classNames = require('classnames');
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const STYLE_TO_ICON = {
+ info: 'info-circle',
+ tip: 'question',
+ success: 'check-circle',
+ danger: 'exclamation-circle',
+ warning: 'exclamation-triangle'
+};
+
+const HintAlert = React.createClass({
+ propTypes: {
+ icon: React.PropTypes.string,
+ style: React.PropTypes.string,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ const { children, style, icon } = this.props;
+ const className = classNames(
+ 'HintAlert', `HintAlert-Style-${style}`,
+ 'alert', `alert-${style}`
+ );
+
+ return (
+ <div className={className}>
+ <GitBook.ImportCSS href="gitbook/hints/plugin.css" />
+ <div className="HintAlert-Icon">
+ <GitBook.Icon id={icon || STYLE_TO_ICON[style]} />
+ </div>
+ <div className="HintAlert-Content">
+ {children}
+ </div>
+ </div>
+ );
+ }
+});
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { Components }) => {
+ dispatch(Components.registerComponent(HintAlert, { role: 'block:hint' }));
+ }
+});
diff --git a/packages/gitbook-plugin-livereload/.gitignore b/packages/gitbook-plugin-livereload/.gitignore
new file mode 100644
index 0000000..ef47881
--- /dev/null
+++ b/packages/gitbook-plugin-livereload/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin-livereload/LICENSE b/packages/gitbook-plugin-livereload/LICENSE
new file mode 100644
index 0000000..ad410e1
--- /dev/null
+++ b/packages/gitbook-plugin-livereload/LICENSE
@@ -0,0 +1,201 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. \ No newline at end of file
diff --git a/packages/gitbook-plugin-livereload/README.md b/packages/gitbook-plugin-livereload/README.md
new file mode 100644
index 0000000..e2d6f83
--- /dev/null
+++ b/packages/gitbook-plugin-livereload/README.md
@@ -0,0 +1,3 @@
+# `gitbook-plugin-livereload`
+
+See [GitBook](https://github.com/GitbookIO/gitbook) for more information.
diff --git a/packages/gitbook-plugin-livereload/package.json b/packages/gitbook-plugin-livereload/package.json
new file mode 100644
index 0000000..97df231
--- /dev/null
+++ b/packages/gitbook-plugin-livereload/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "gitbook-plugin-livereload",
+ "description": "Live reloading for your gitbook",
+ "main": "index.js",
+ "browser": "./_assets/plugin.js",
+ "version": "4.0.0",
+ "engines": {
+ "gitbook": "*"
+ },
+ "dependencies": {
+ "gitbook-core": "4.0.0"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitbookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "license": "Apache 2",
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-plugin-livereload/src/index.js b/packages/gitbook-plugin-livereload/src/index.js
new file mode 100644
index 0000000..e73f12d
--- /dev/null
+++ b/packages/gitbook-plugin-livereload/src/index.js
@@ -0,0 +1,18 @@
+const GitBook = require('gitbook-core');
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { Components }) => {
+ if (typeof window === 'undefined') {
+ return;
+ }
+
+ const newEl = document.createElement('script');
+ const firstScriptTag = document.getElementsByTagName('script')[0];
+
+ if (firstScriptTag) {
+ newEl.async = 1;
+ newEl.src = '//' + window.location.hostname + ':35729/livereload.js';
+ firstScriptTag.parentNode.insertBefore(newEl, firstScriptTag);
+ }
+ }
+});
diff --git a/packages/gitbook-plugin-lunr/.gitignore b/packages/gitbook-plugin-lunr/.gitignore
new file mode 100644
index 0000000..7c6f0eb
--- /dev/null
+++ b/packages/gitbook-plugin-lunr/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets
diff --git a/packages/gitbook-plugin-lunr/.npmignore b/packages/gitbook-plugin-lunr/.npmignore
new file mode 100644
index 0000000..7bc36b7
--- /dev/null
+++ b/packages/gitbook-plugin-lunr/.npmignore
@@ -0,0 +1,2 @@
+# Publish assets on NPM
+!_assets
diff --git a/packages/gitbook-plugin-lunr/index.js b/packages/gitbook-plugin-lunr/index.js
new file mode 100644
index 0000000..bdde8f6
--- /dev/null
+++ b/packages/gitbook-plugin-lunr/index.js
@@ -0,0 +1,99 @@
+/* eslint-disable no-var, object-shorthand */
+var lunr = require('lunr');
+var Entities = require('html-entities').AllHtmlEntities;
+
+var Html = new Entities();
+
+var searchIndex;
+
+// Called with the `this` context provided by Gitbook
+function getSearchIndex(context) {
+ if (!searchIndex) {
+ // Create search index
+ var ignoreSpecialCharacters = (
+ context.config.get('pluginsConfig.lunr.ignoreSpecialCharacters')
+ || context.config.get('lunr.ignoreSpecialCharacters')
+ );
+
+ searchIndex = lunr(function() {
+ this.ref('url');
+
+ this.field('title', { boost: 10 });
+ this.field('keywords', { boost: 15 });
+ this.field('body');
+
+ if (!ignoreSpecialCharacters) {
+ // Don't trim non words characters (to allow search such as "C++")
+ this.pipeline.remove(lunr.trimmer);
+ }
+ });
+ }
+ return searchIndex;
+}
+
+// Map of Lunr ref to document
+var documentsStore = {};
+
+var searchIndexEnabled = true;
+var indexSize = 0;
+
+module.exports = {
+ hooks: {
+ // Index each page
+ 'page': function(page) {
+ const search = page.attributes.search;
+
+ if (this.output.name != 'website' || !searchIndexEnabled || search === false) {
+ return page;
+ }
+
+ var text, maxIndexSize;
+ maxIndexSize = this.config.get('pluginsConfig.lunr.maxIndexSize') || this.config.get('lunr.maxIndexSize');
+
+ this.log.debug.ln('index page', page.path);
+
+ text = page.content;
+ // Decode HTML
+ text = Html.decode(text);
+ // Strip HTML tags
+ text = text.replace(/(<([^>]+)>)/ig, '');
+
+ indexSize = indexSize + text.length;
+ if (indexSize > maxIndexSize) {
+ this.log.warn.ln('search index is too big, indexing is now disabled');
+ searchIndexEnabled = false;
+ return page;
+ }
+
+ var keywords = [];
+ if (search) {
+ keywords = search.keywords || [];
+ }
+
+ // Add to index
+ var doc = {
+ url: this.output.toURL(page.path),
+ title: page.title,
+ summary: page.description,
+ keywords: keywords.join(' '),
+ body: text
+ };
+
+ documentsStore[doc.url] = doc;
+ getSearchIndex(this).add(doc);
+
+ return page;
+ },
+
+ // Write index to disk
+ 'finish': function() {
+ if (this.output.name != 'website') return;
+
+ this.log.debug.ln('write search index');
+ return this.output.writeFile('search_index.json', JSON.stringify({
+ index: getSearchIndex(this),
+ store: documentsStore
+ }));
+ }
+ }
+};
diff --git a/packages/gitbook-plugin-lunr/package.json b/packages/gitbook-plugin-lunr/package.json
new file mode 100644
index 0000000..6a26f2d
--- /dev/null
+++ b/packages/gitbook-plugin-lunr/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "gitbook-plugin-lunr",
+ "description": "Static and local index for search in GitBook",
+ "main": "index.js",
+ "browser": "./_assets/theme.js",
+ "version": "4.0.0",
+ "dependencies": {
+ "gitbook-core": "4.0.0",
+ "html-entities": "1.2.0",
+ "lunr": "0.5.12"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
+ "engines": {
+ "gitbook": ">=3.0.0"
+ },
+ "gitbook": {
+ "properties": {
+ "maxIndexSize": {
+ "type": "number",
+ "title": "Limit size for the index",
+ "default": 1000000
+ },
+ "ignoreSpecialCharacters": {
+ "type": "boolean",
+ "title": "Ignore special characters in words",
+ "default": false
+ }
+ }
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/theme.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitBookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitBookIO/gitbook.git"
+ },
+ "bugs": {
+ "url": "https://github.com/GitBookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-plugin-lunr/src/actions.js b/packages/gitbook-plugin-lunr/src/actions.js
new file mode 100644
index 0000000..765fa2e
--- /dev/null
+++ b/packages/gitbook-plugin-lunr/src/actions.js
@@ -0,0 +1,43 @@
+const GitBook = require('gitbook-core');
+
+const TYPES = {
+ LOAD: 'lunr/load'
+};
+const INDEX_FILENAME = 'search_index.json';
+
+/**
+ * Load an index set
+ * @param {JSON} json
+ * @return {Action}
+ */
+function load(json) {
+ return { type: TYPES.LOAD, json };
+}
+
+/**
+ * Fetch an index
+ * @return {Action}
+ */
+function fetch() {
+ return (dispatch, getState) => {
+ const { lunr, file } = getState();
+ const { idx } = lunr;
+ const filePath = file.relative(INDEX_FILENAME);
+
+ if (idx) {
+ return GitBook.Promise.resolve();
+ }
+
+ return GitBook.Promise.resolve()
+ .then(() => {
+ return window.fetch(filePath);
+ })
+ .then(response => response.json())
+ .then(json => dispatch(load(json)));
+ };
+}
+
+module.exports = {
+ TYPES,
+ fetch
+};
diff --git a/packages/gitbook-plugin-lunr/src/index.js b/packages/gitbook-plugin-lunr/src/index.js
new file mode 100644
index 0000000..1135f51
--- /dev/null
+++ b/packages/gitbook-plugin-lunr/src/index.js
@@ -0,0 +1,28 @@
+const GitBook = require('gitbook-core');
+const reduce = require('./reducer');
+const actions = require('./actions');
+
+/**
+ * Search in the local index
+ * @param {String} query
+ * @return {Promise<List>}
+ */
+function searchHandler(query, dispatch, getState) {
+ // Fetch the index if non loaded
+ return dispatch(actions.fetch())
+
+ // Execute the search
+ .then(() => {
+ const { idx, store } = getState().lunr;
+ const results = idx.search(query);
+
+ return results.map(({ref}) => store.get(ref).toJS());
+ });
+}
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { Search }) => {
+ dispatch(Search.registerHandler('lunr', searchHandler));
+ },
+ reduce
+});
diff --git a/packages/gitbook-plugin-lunr/src/reducer.js b/packages/gitbook-plugin-lunr/src/reducer.js
new file mode 100644
index 0000000..7e317c4
--- /dev/null
+++ b/packages/gitbook-plugin-lunr/src/reducer.js
@@ -0,0 +1,31 @@
+const lunr = require('lunr');
+const GitBook = require('gitbook-core');
+const { Record } = GitBook.Immutable;
+
+const { TYPES } = require('./actions');
+
+/*
+ We store the lunr index an the document index in the store.
+ */
+
+const LunrState = Record({
+ idx: null,
+ store: {}
+});
+
+module.exports = GitBook.createReducer('lunr', (state, action) => {
+ state = state || LunrState();
+
+ switch (action.type) {
+
+ case TYPES.LOAD:
+ return state
+ .set('idx', lunr.Index.load(action.json.index))
+ .merge({
+ store: action.json.store
+ });
+
+ default:
+ return state;
+ }
+});
diff --git a/packages/gitbook-plugin-search/.gitignore b/packages/gitbook-plugin-search/.gitignore
new file mode 100644
index 0000000..dfd90dc
--- /dev/null
+++ b/packages/gitbook-plugin-search/.gitignore
@@ -0,0 +1 @@
+_assets
diff --git a/packages/gitbook-plugin-search/.npmignore b/packages/gitbook-plugin-search/.npmignore
new file mode 100644
index 0000000..75e0923
--- /dev/null
+++ b/packages/gitbook-plugin-search/.npmignore
@@ -0,0 +1 @@
+!_assets
diff --git a/packages/gitbook-plugin-search/README.md b/packages/gitbook-plugin-search/README.md
new file mode 100644
index 0000000..f667e4c
--- /dev/null
+++ b/packages/gitbook-plugin-search/README.md
@@ -0,0 +1,41 @@
+# plugin-search
+
+This plugin is the interface used by all the search plugins (`plugin-lunr`, `plugin-algolia`, etc.)
+
+## Registering a Search handler
+
+Your plugin must register as a Search handler during its `activate` method:
+
+
+``` js
+GitBook.createPlugin({
+ activate: (dispatch, getState, { Search }) => {
+ dispatch(Search.registerHandler('my-plugin-name', searchHandler));
+ },
+ reduce
+})
+
+/**
+ * Search against a query
+ * @param {String} query
+ * @return {Promise<List<Result>>}
+ */
+function searchHandler(query, dispatch, getState) {
+ ...
+}
+```
+
+Your search handler must return a List of result-shaped objects. A result object has the following shape:
+
+``` js
+result = {
+ title: string, // The title of the resource, as displayed in the list of results.
+
+ url: string, // The URL to access the matched resource.
+
+ body: string // (optional) The context of the matched text (can be a sentence
+ // containing matching words). It will be displayed near the result.
+}
+```
+
+
diff --git a/packages/gitbook-plugin-search/index.js b/packages/gitbook-plugin-search/index.js
new file mode 100644
index 0000000..5803889
--- /dev/null
+++ b/packages/gitbook-plugin-search/index.js
@@ -0,0 +1,4 @@
+
+module.exports = {
+
+};
diff --git a/packages/gitbook-plugin-search/package.json b/packages/gitbook-plugin-search/package.json
new file mode 100644
index 0000000..ef4ae9e
--- /dev/null
+++ b/packages/gitbook-plugin-search/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "gitbook-plugin-search",
+ "description": "Search integration in GitBook",
+ "main": "index.js",
+ "browser": "./_assets/theme.js",
+ "version": "4.0.0",
+ "dependencies": {
+ "gitbook-core": "4.0.0",
+ "react": "^15.4.1"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0",
+ "react-highlighter": "^0.3.3"
+ },
+ "engines": {
+ "gitbook": ">=3.0.0"
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/theme.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitbookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-plugin-search/src/actions/search.js b/packages/gitbook-plugin-search/src/actions/search.js
new file mode 100644
index 0000000..24151c6
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/actions/search.js
@@ -0,0 +1,121 @@
+const { Promise, Immutable } = require('gitbook-core');
+const { List } = Immutable;
+
+const TYPES = require('./types');
+const Result = require('../models/Result');
+
+/*
+ Search workflow:
+
+ 1. Typing in the search input
+ 2. Trigger an update of the url
+ 3. An update of the url, trigger an update of search results
+ */
+
+/**
+ * Start a search query
+ * @param {String} q
+ * @return {Action}
+ */
+function query(q) {
+ return (dispatch, getState, { History }) => {
+ const searchState = getState().search;
+ const currentQuery = searchState.query;
+
+ const queryString = q ? { q } : {};
+
+ if (currentQuery && q) {
+ dispatch(History.replace({ query: queryString }));
+ } else {
+ dispatch(History.push({ query: queryString }));
+ }
+ };
+}
+
+/**
+ * Update results for a query
+ * @param {String} q
+ * @return {Action}
+ */
+function handleQuery(q) {
+ if (!q) {
+ return clear();
+ }
+
+ return (dispatch, getState, actions) => {
+ const { handlers } = getState().search;
+
+ dispatch({ type: TYPES.START, query: q });
+
+ return Promise.reduce(
+ handlers.toArray(),
+ (results, handler) => {
+ return Promise.resolve(handler(q, dispatch, getState, actions))
+ .then(handlerResults => {
+ return handlerResults.map(result => new Result(result));
+ })
+ .then(handlerResults => results.concat(handlerResults));
+ },
+ List()
+ )
+ .then(
+ results => {
+ dispatch({ type: TYPES.END, query: q, results });
+ }
+ );
+ };
+}
+
+/**
+ * Refresh current search (when handlers have changed)
+ * @return {Action}
+ */
+function refresh() {
+ return (dispatch, getState) => {
+ const q = getState().search.query;
+ if (q) {
+ dispatch(handleQuery(q));
+ }
+ };
+}
+
+/**
+ * Clear the whole search
+ * @return {Action}
+ */
+function clear() {
+ return { type: TYPES.CLEAR };
+}
+
+/**
+ * Register a search handler
+ * @param {String} name
+ * @param {Function} handler
+ * @return {Action}
+ */
+function registerHandler(name, handler) {
+ return (dispatch) => {
+ dispatch({ type: TYPES.REGISTER_HANDLER, name, handler });
+ dispatch(refresh());
+ };
+}
+
+/**
+ * Unregister a search handler
+ * @param {String} name
+ * @return {Action}
+ */
+function unregisterHandler(name) {
+ return (dispatch) => {
+ dispatch({ type: TYPES.UNREGISTER_HANDLER, name });
+ dispatch(refresh());
+ };
+}
+
+module.exports = {
+ clear,
+ query,
+ handleQuery,
+ registerHandler,
+ unregisterHandler
+};
diff --git a/packages/gitbook-plugin-search/src/actions/types.js b/packages/gitbook-plugin-search/src/actions/types.js
new file mode 100644
index 0000000..3cd1a89
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/actions/types.js
@@ -0,0 +1,8 @@
+
+module.exports = {
+ CLEAR: 'search/clear',
+ REGISTER_HANDLER: 'search/handlers/register',
+ UNREGISTER_HANDLER: 'search/handlers/unregister',
+ START: 'search/start',
+ END: 'search/end'
+};
diff --git a/packages/gitbook-plugin-search/src/components/Input.js b/packages/gitbook-plugin-search/src/components/Input.js
new file mode 100644
index 0000000..216a5d2
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/components/Input.js
@@ -0,0 +1,73 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const search = require('../actions/search');
+
+const ESCAPE = 27;
+
+const SearchInput = React.createClass({
+ propTypes: {
+ query: React.PropTypes.string,
+ i18n: GitBook.PropTypes.I18n,
+ dispatch: GitBook.PropTypes.dispatch
+ },
+
+ onChange(event) {
+ const { dispatch } = this.props;
+ const { value } = event.currentTarget;
+
+ dispatch(search.query(value));
+ },
+
+ /**
+ * On Escape key down, clear the search field
+ */
+ onKeyDown(e) {
+ const { query } = this.props;
+ if (e.keyCode == ESCAPE && query != '') {
+ e.preventDefault();
+ e.stopPropagation();
+ this.clearSearch();
+ }
+ },
+
+ clearSearch() {
+ this.props.dispatch(search.query(''));
+ },
+
+ render() {
+ const { i18n, query } = this.props;
+
+ let clear;
+ if (query != '') {
+ clear = (
+ <span className="Search-Clear"
+ onClick={this.clearSearch}>
+ ✕
+ </span>
+ );
+ // clear = <GitBook.Icon id="x" onClick={this.clearSearch}/>;
+ }
+
+ return (
+ <div className="Search-Input">
+ <input
+ type="text"
+ onKeyDown={this.onKeyDown}
+ value={query}
+ placeholder={i18n.t('SEARCH_PLACEHOLDER')}
+ onChange={this.onChange}
+ />
+
+ { clear }
+ </div>
+ );
+ }
+});
+
+const mapStateToProps = state => {
+ const { query } = state.search;
+ return { query };
+};
+
+module.exports = GitBook.connect(SearchInput, mapStateToProps);
diff --git a/packages/gitbook-plugin-search/src/components/Results.js b/packages/gitbook-plugin-search/src/components/Results.js
new file mode 100644
index 0000000..16a8cbd
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/components/Results.js
@@ -0,0 +1,80 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+const Highlight = require('react-highlighter');
+
+const MAX_DESCRIPTION_SIZE = 500;
+
+const Result = React.createClass({
+ propTypes: {
+ result: React.PropTypes.object,
+ query: React.PropTypes.string
+ },
+
+ render() {
+ const { result, query } = this.props;
+
+ let summary = result.body.trim();
+ if (summary.length > MAX_DESCRIPTION_SIZE) {
+ summary = summary.slice(0, MAX_DESCRIPTION_SIZE).trim() + '...';
+ }
+
+ return (
+ <div className="Search-ResultContainer">
+ <GitBook.InjectedComponent matching={{ role: 'search:result' }} props={{ result, query }}>
+ <div className="Search-Result">
+ <h3>
+ <GitBook.Link to={result.url}>{result.title}</GitBook.Link>
+ </h3>
+ <p>
+ <Highlight
+ matchElement="span"
+ matchClass="Search-MatchSpan"
+ search={query}>
+ {summary}
+ </Highlight>
+ </p>
+ </div>
+ </GitBook.InjectedComponent>
+ </div>
+ );
+ }
+});
+
+const SearchResults = React.createClass({
+ propTypes: {
+ i18n: GitBook.PropTypes.I18n,
+ results: GitBook.PropTypes.list,
+ query: React.PropTypes.string,
+ children: React.PropTypes.node
+ },
+
+ render() {
+ const { i18n, query, results, children } = this.props;
+
+ if (!query) {
+ return React.Children.only(children);
+ }
+
+ return (
+ <div className="Search-ResultsContainer">
+ <GitBook.InjectedComponent matching={{ role: 'search:results' }} props={{ results, query }}>
+ <div className="Search-Results">
+ <h1>{i18n.t('SEARCH_RESULTS_TITLE', { query, count: results.size })}</h1>
+ <div className="Search-Results">
+ {results.map((result, i) => {
+ return <Result key={i} result={result} query={query} />;
+ })}
+ </div>
+ </div>
+ </GitBook.InjectedComponent>
+ </div>
+ );
+ }
+});
+
+const mapStateToProps = (state) => {
+ const { results, query } = state.search;
+ return { results, query };
+};
+
+module.exports = GitBook.connect(SearchResults, mapStateToProps);
diff --git a/packages/gitbook-plugin-search/src/index.js b/packages/gitbook-plugin-search/src/index.js
new file mode 100644
index 0000000..f8c59aa
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/index.js
@@ -0,0 +1,33 @@
+const GitBook = require('gitbook-core');
+
+const SearchInput = require('./components/Input');
+const SearchResults = require('./components/Results');
+const reducers = require('./reducers');
+const Search = require('./actions/search');
+
+/**
+ * Url of the page changed, we update the search according to this.
+ * @param {GitBook.Location} location
+ * @param {Function} dispatch
+ */
+const onLocationChange = (location, dispatch) => {
+ const { query } = location;
+ const q = query.get('q');
+
+ dispatch(Search.handleQuery(q));
+};
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { History, Components }) => {
+ // Register the navigation handler
+ dispatch(History.listen(onLocationChange));
+
+ // Register components
+ dispatch(Components.registerComponent(SearchInput, { role: 'search:container:input' }));
+ dispatch(Components.registerComponent(SearchResults, { role: 'search:container:results' }));
+ },
+ reduce: reducers,
+ actions: {
+ Search
+ }
+});
diff --git a/packages/gitbook-plugin-search/src/models/Result.js b/packages/gitbook-plugin-search/src/models/Result.js
new file mode 100644
index 0000000..0012b2b
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/models/Result.js
@@ -0,0 +1,20 @@
+const GitBook = require('gitbook-core');
+const { Record } = GitBook.Immutable;
+
+const DEFAULTS = {
+ url: String(''),
+ title: String(''),
+ body: String('')
+};
+
+class Result extends Record(DEFAULTS) {
+ constructor(spec) {
+ if (!spec.url || !spec.title) {
+ throw new Error('"url" and "title" are required to create a search result');
+ }
+
+ super(spec);
+ }
+}
+
+module.exports = Result;
diff --git a/packages/gitbook-plugin-search/src/reducers/index.js b/packages/gitbook-plugin-search/src/reducers/index.js
new file mode 100644
index 0000000..bfce2bd
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/reducers/index.js
@@ -0,0 +1,3 @@
+const GitBook = require('gitbook-core');
+
+module.exports = GitBook.createReducer('search', require('./search'));
diff --git a/packages/gitbook-plugin-search/src/reducers/search.js b/packages/gitbook-plugin-search/src/reducers/search.js
new file mode 100644
index 0000000..b960a77
--- /dev/null
+++ b/packages/gitbook-plugin-search/src/reducers/search.js
@@ -0,0 +1,56 @@
+const GitBook = require('gitbook-core');
+const { Record, List, OrderedMap } = GitBook.Immutable;
+
+const TYPES = require('../actions/types');
+
+const SearchState = Record({
+ // Is the search being processed
+ loading: Boolean(false),
+ // Current query
+ query: String(''),
+ // Current list of results
+ results: List(),
+ // Search handlers
+ handlers: OrderedMap()
+});
+
+module.exports = (state = SearchState(), action) => {
+ switch (action.type) {
+
+ case TYPES.CLEAR:
+ return state.merge({
+ loading: false,
+ query: '',
+ results: List()
+ });
+
+ case TYPES.START:
+ return state.merge({
+ loading: true,
+ query: action.query
+ });
+
+ case TYPES.END:
+ if (action.query !== state.query) {
+ return state;
+ }
+
+ return state.merge({
+ loading: false,
+ results: action.results
+ });
+
+ case TYPES.REGISTER_HANDLER:
+ return state.merge({
+ handlers: state.handlers.set(action.name, action.handler)
+ });
+
+ case TYPES.UNREGISTER_HANDLER:
+ return state.merge({
+ handlers: state.handlers.remove(action.name)
+ });
+
+ default:
+ return state;
+ }
+};
diff --git a/packages/gitbook-plugin-sharing/.gitignore b/packages/gitbook-plugin-sharing/.gitignore
new file mode 100644
index 0000000..ef47881
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin-sharing/.npmignore b/packages/gitbook-plugin-sharing/.npmignore
new file mode 100644
index 0000000..a0e53cf
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/.npmignore
@@ -0,0 +1,2 @@
+# Publish assets on NPM
+!_assets/plugin.js
diff --git a/packages/gitbook-plugin-sharing/README.md b/packages/gitbook-plugin-sharing/README.md
new file mode 100644
index 0000000..28ae0d4
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/README.md
@@ -0,0 +1,38 @@
+# plugin-sharing
+
+This plugin adds sharing buttons in the GitBook website toolbar to share book on social networks.
+
+### Disable this plugin
+
+This is a default plugin and it can be disabled using a `book.json` configuration:
+
+```
+{
+ plugins: ["-sharing"]
+}
+```
+
+### Configuration
+
+This plugin can be configured in the `book.json`:
+
+Default configuration is:
+
+```js
+{
+ "pluginsConfig": {
+ "sharing": {
+ "facebook": true,
+ "twitter": true,
+ "google": false,
+ "weibo": false,
+ "instapaper": false,
+ "vk": false,
+ "all": [
+ "facebook", "google", "twitter",
+ "weibo", "instapaper"
+ ]
+ }
+ }
+}
+```
diff --git a/packages/gitbook-plugin-sharing/index.js b/packages/gitbook-plugin-sharing/index.js
new file mode 100644
index 0000000..e542ae8
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/index.js
@@ -0,0 +1,10 @@
+
+module.exports = {
+ blocks: {
+
+ },
+
+ hooks: {
+
+ }
+};
diff --git a/packages/gitbook-plugin-sharing/package.json b/packages/gitbook-plugin-sharing/package.json
new file mode 100644
index 0000000..b0540e8
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "gitbook-plugin-sharing",
+ "description": "Sharing buttons in the toolbar",
+ "main": "index.js",
+ "browser": "./_assets/plugin.js",
+ "version": "4.0.0",
+ "gitbook": {
+ "properties": {
+ "facebook": {
+ "type": "boolean",
+ "default": true,
+ "title": "Facebook"
+ },
+ "twitter": {
+ "type": "boolean",
+ "default": true,
+ "title": "Twitter"
+ },
+ "google": {
+ "type": "boolean",
+ "default": false,
+ "title": "Google"
+ },
+ "weibo": {
+ "type": "boolean",
+ "default": false,
+ "description": "Weibo"
+ },
+ "instapaper": {
+ "type": "boolean",
+ "default": false,
+ "description": "Instapaper"
+ },
+ "vk": {
+ "type": "boolean",
+ "default": false,
+ "description": "VK"
+ },
+ "all": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "default": [
+ "facebook",
+ "google",
+ "twitter",
+ "weibo",
+ "instapaper"
+ ],
+ "uniqueItems": true
+ }
+ }
+ },
+ "dependencies": {
+ "gitbook-core": "4.0.0"
+ },
+ "devDependencies": {
+ "gitbook-plugin": "4.0.0"
+ },
+ "engines": {
+ "gitbook": ">=3.0.0"
+ },
+ "scripts": {
+ "build-js": "gitbook-plugin build ./src/index.js ./_assets/plugin.js",
+ "prepublish": "npm run build-js"
+ },
+ "homepage": "https://github.com/GitbookIO/gitbook",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ },
+ "license": "Apache-2.0"
+}
diff --git a/packages/gitbook-plugin-sharing/src/SITES.js b/packages/gitbook-plugin-sharing/src/SITES.js
new file mode 100644
index 0000000..86eae74
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/SITES.js
@@ -0,0 +1,72 @@
+// All the sharing platforms
+const SITES = {
+
+ // One sharing platform
+ 'facebook': {
+ // Displayed name
+ label: 'Facebook',
+
+ // Font-awesome icon id
+ icon: 'facebook',
+
+ /**
+ * Share a page on this platform
+ * @param {String} url The url to share
+ * @param {String} title The title of the url page
+ */
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`http://www.facebook.com/sharer/sharer.php?s=100&p[url]=${url}`);
+ }
+ },
+
+ 'twitter': {
+ label: 'Twitter',
+ icon: 'twitter',
+ onShare(url, title) {
+ const status = encodeURIComponent(title + ' ' + url);
+ window.open(`http://twitter.com/home?status=${status}`);
+ }
+ },
+
+ 'google': {
+ label: 'Google+',
+ icon: 'google-plus',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`https://plus.google.com/share?url=${url}`);
+ }
+ },
+
+ 'weibo': {
+ label: 'Weibo',
+ icon: 'weibo',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ title = encodeURIComponent(title);
+ window.open(`http://service.weibo.com/share/share.php?content=utf-8&url=${url}&title=${title}`);
+ }
+ },
+
+ 'instapaper': {
+ label: 'Instapaper',
+ icon: 'instapaper',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`http://www.instapaper.com/text?u=${url}`);
+ }
+ },
+
+ 'vk': {
+ label: 'VK',
+ icon: 'vk',
+ onShare(url, title) {
+ url = encodeURIComponent(url);
+ window.open(`http://vkontakte.ru/share.php?url=${url}`);
+ }
+ }
+};
+
+SITES.ALL = Object.keys(SITES);
+
+module.exports = SITES;
diff --git a/packages/gitbook-plugin-sharing/src/components/ShareButton.js b/packages/gitbook-plugin-sharing/src/components/ShareButton.js
new file mode 100644
index 0000000..8983423
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/components/ShareButton.js
@@ -0,0 +1,47 @@
+const GitBook = require('gitbook-core');
+const { React, Dropdown, Backdrop } = GitBook;
+
+const SITES = require('../SITES');
+
+// Share button with dropdown list of sites
+const ShareButton = React.createClass({
+ propTypes: {
+ siteIds: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
+ onShare: React.PropTypes.func.isRequired
+ },
+
+ getInitialState() {
+ return { open: false };
+ },
+
+ onToggle() {
+ const { open } = this.state;
+ this.setState({ open: !open });
+ },
+
+ render() {
+ const { siteIds, onShare } = this.props;
+ const { open } = this.state;
+
+ return (
+ <Dropdown.Container>
+ {open ? <Backdrop onClose={this.onToggle} /> : null}
+
+ <GitBook.Button onClick={this.onToggle}>
+ <GitBook.Icon id="share-alt" />
+ </GitBook.Button>
+
+ {open ? (
+ <Dropdown.Menu>
+ {siteIds.map((id) => (
+ <Dropdown.ItemLink onClick={() => onShare(SITES[id])} key={id}>
+ {SITES[id].label}
+ </Dropdown.ItemLink>
+ ))}
+ </Dropdown.Menu>) : null}
+ </Dropdown.Container>
+ );
+ }
+});
+
+module.exports = ShareButton;
diff --git a/packages/gitbook-plugin-sharing/src/components/SharingButtons.js b/packages/gitbook-plugin-sharing/src/components/SharingButtons.js
new file mode 100644
index 0000000..4f5ada9
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/components/SharingButtons.js
@@ -0,0 +1,63 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const SITES = require('../SITES');
+const optionsShape = require('../shapes/options');
+const SiteButton = require('./SiteButton');
+const ShareButton = require('./ShareButton');
+
+/**
+ * Displays the group of sharing buttons
+ */
+const SharingButtons = React.createClass({
+ propTypes: {
+ options: optionsShape.isRequired,
+ page: GitBook.PropTypes.Page.isRequired
+ },
+
+ onShare(site) {
+ site.onShare(location.href, this.props.page.title);
+ },
+
+ render() {
+ const { options } = this.props;
+
+ // Highlighted sites
+ const mainButtons = SITES
+ .ALL
+ .filter(id => options[id])
+ .map(id => <SiteButton key={id} onShare={this.onShare} site={SITES[id]} />);
+
+ // Other sites
+ let shareButton = undefined;
+ if (options.all.length > 0) {
+ shareButton = (
+ <ShareButton siteIds={options.all}
+ onShare={this.onShare} />
+ );
+ }
+
+ return (
+ <GitBook.ButtonGroup>
+ { mainButtons }
+ { shareButton }
+ </GitBook.ButtonGroup>
+ );
+ }
+});
+
+function mapStateToProps(state) {
+ let options = state.config.getIn(['pluginsConfig', 'sharing']);
+ if (options) {
+ options = options.toJS();
+ } else {
+ options = { all: [] };
+ }
+
+ return {
+ page: state.page,
+ options
+ };
+}
+
+module.exports = GitBook.connect(SharingButtons, mapStateToProps);
diff --git a/packages/gitbook-plugin-sharing/src/components/SiteButton.js b/packages/gitbook-plugin-sharing/src/components/SiteButton.js
new file mode 100644
index 0000000..e03720d
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/components/SiteButton.js
@@ -0,0 +1,29 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const siteShape = require('../shapes/site');
+
+// An individual site sharing button
+const SiteButton = React.createClass({
+ propTypes: {
+ site: siteShape.isRequired,
+ onShare: React.PropTypes.func.isRequired
+ },
+
+ onClick(e) {
+ e.preventDefault();
+ this.props.onShare(this.props.site);
+ },
+
+ render() {
+ const { site } = this.props;
+
+ return (
+ <GitBook.Button onClick={this.onClick}>
+ <GitBook.Icon id={site.icon}/>
+ </GitBook.Button>
+ );
+ }
+});
+
+module.exports = SiteButton;
diff --git a/packages/gitbook-plugin-sharing/src/index.js b/packages/gitbook-plugin-sharing/src/index.js
new file mode 100644
index 0000000..174adfc
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/index.js
@@ -0,0 +1,9 @@
+const GitBook = require('gitbook-core');
+const SharingButtons = require('./components/SharingButtons');
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState, { Components }) => {
+ // Dispatch initialization actions
+ dispatch(Components.registerComponent(SharingButtons, { role: 'toolbar:buttons:right' }));
+ }
+});
diff --git a/packages/gitbook-plugin-sharing/src/optionsShape.js b/packages/gitbook-plugin-sharing/src/optionsShape.js
new file mode 100644
index 0000000..dd51016
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/optionsShape.js
@@ -0,0 +1,20 @@
+const {
+ bool,
+ arrayOf,
+ oneOf,
+ shape
+} = require('gitbook-core').React.PropTypes;
+
+const { ALL } = require('./SITES');
+
+const optionsShape = shape({
+ facebook: bool,
+ twitter: bool,
+ google: bool,
+ weibo: bool,
+ instapaper: bool,
+ vk: bool,
+ all: arrayOf(oneOf(ALL)).isRequired
+});
+
+module.exports = optionsShape;
diff --git a/packages/gitbook-plugin-sharing/src/shapes/options.js b/packages/gitbook-plugin-sharing/src/shapes/options.js
new file mode 100644
index 0000000..885feb6
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/shapes/options.js
@@ -0,0 +1,19 @@
+const {
+ bool,
+ arrayOf,
+ oneOf,
+ shape
+} = require('gitbook-core').React.PropTypes;
+const { ALL } = require('../SITES');
+
+const optionsShape = shape({
+ facebook: bool,
+ twitter: bool,
+ google: bool,
+ weibo: bool,
+ instapaper: bool,
+ vk: bool,
+ all: arrayOf(oneOf(ALL)).isRequired
+});
+
+module.exports = optionsShape;
diff --git a/packages/gitbook-plugin-sharing/src/shapes/site.js b/packages/gitbook-plugin-sharing/src/shapes/site.js
new file mode 100644
index 0000000..2227429
--- /dev/null
+++ b/packages/gitbook-plugin-sharing/src/shapes/site.js
@@ -0,0 +1,13 @@
+const {
+ string,
+ func,
+ shape
+} = require('gitbook-core').React.PropTypes;
+
+const siteShape = shape({
+ label: string.isRequired,
+ icon: string.isRequired,
+ onShare: func.isRequired
+});
+
+module.exports = siteShape;
diff --git a/packages/gitbook-plugin-theme-default/.gitignore b/packages/gitbook-plugin-theme-default/.gitignore
new file mode 100644
index 0000000..dfd90dc
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/.gitignore
@@ -0,0 +1 @@
+_assets
diff --git a/packages/gitbook-plugin-theme-default/.npmignore b/packages/gitbook-plugin-theme-default/.npmignore
new file mode 100644
index 0000000..75e0923
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/.npmignore
@@ -0,0 +1 @@
+!_assets
diff --git a/packages/gitbook-plugin-theme-default/index.js b/packages/gitbook-plugin-theme-default/index.js
new file mode 100644
index 0000000..f4d6253
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+
+};
diff --git a/packages/gitbook-plugin-theme-default/less/Body.less b/packages/gitbook-plugin-theme-default/less/Body.less
new file mode 100644
index 0000000..4bc33db
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Body.less
@@ -0,0 +1,9 @@
+.Body-Flex {
+ .flex(1 0 auto);
+}
+
+.Body {
+ overflow: auto;
+ width: 100%;
+ height: 100%;
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Button.less b/packages/gitbook-plugin-theme-default/less/Button.less
new file mode 100644
index 0000000..336d16e
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Button.less
@@ -0,0 +1,22 @@
+.GitBook-Button {
+ border: 0;
+ background-color: transparent;
+ background: @button-background;
+ color: @button-color;
+ text-align: center;
+ line-height: @line-height-base;
+ outline: none;
+ padding: @button-padding;
+
+ &:hover {
+ color: @button-hover-color;
+ }
+
+ &:focus, &:hover {
+ outline: none;
+ }
+}
+
+.GitBook-ButtonGroup {
+ display: inline-block;
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Dropdown.less b/packages/gitbook-plugin-theme-default/less/Dropdown.less
new file mode 100644
index 0000000..2c341e4
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Dropdown.less
@@ -0,0 +1,56 @@
+.GitBook-Dropdown {
+ display: inline-block;
+ position: relative;
+}
+
+.GitBook-DropdownMenu {
+ position: absolute;
+ top: 100%;
+ right: 0;
+ z-index: 300;
+ border: 1px solid @dropdown-border-color;
+ margin: 5px;
+ margin-top: 0px;
+ border-radius: 3px;
+ background: @dropdown-background;
+
+ &:before {
+ content: " ";
+ width: 0;
+ height: 0;
+ border-left: @dropdown-arrow-width solid transparent;
+ border-right: @dropdown-arrow-width solid transparent;
+ border-bottom: @dropdown-arrow-width solid @dropdown-border-color;
+ position: absolute;
+ top: -@dropdown-arrow-width;
+ right: 10px;
+ }
+
+ &:after {
+ content: " ";
+ width: 0;
+ height: 0;
+ border-left: (@dropdown-arrow-width - 1) solid transparent;
+ border-right: (@dropdown-arrow-width - 1) solid transparent;
+ border-bottom: (@dropdown-arrow-width - 1) solid @dropdown-background;
+ position: absolute;
+ top: -(@dropdown-arrow-width - 1);
+ right: 11px;
+ }
+}
+
+.GitBook-DropdownItem {
+ padding: @dropdown-padding-v @dropdown-padding-h;
+}
+
+.GitBook-DropdownItemLink {
+ width: 100%;
+ display: inline-block;
+ padding: @dropdown-padding-v @dropdown-padding-h;
+ text-align: center;
+ color: @dropdown-color;
+
+ &:hover {
+ color: @dropdown-hover-color;
+ }
+}
diff --git a/packages/gitbook-plugin-theme-default/less/LoadingBar.less b/packages/gitbook-plugin-theme-default/less/LoadingBar.less
new file mode 100644
index 0000000..1fca2ea
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/LoadingBar.less
@@ -0,0 +1,30 @@
+.LoadingBar {
+ pointer-events: none;
+ transition: 400ms linear all;
+
+ .LoadingBar-Bar {
+ background: @color-primary;
+ height: 2px;
+
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 10000;
+ display: none;
+ width: 100%;
+ border-radius: 0 1px 1px 0;
+ transition: width 350ms;
+ }
+
+ .LoadingBar-Shadow {
+ content: '';
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 70px;
+ height: 2px;
+ border-radius: 50%;
+ opacity: .45;
+ box-shadow: @color-primary 1px 0 6px 1px;
+ }
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Page.less b/packages/gitbook-plugin-theme-default/less/Page.less
new file mode 100644
index 0000000..6011533
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Page.less
@@ -0,0 +1,16 @@
+.PageContainer {
+ position: relative;
+ outline: none;
+ width: 100%;
+ max-width: @page-width;
+ margin: 0px auto;
+ padding: 20px 15px 40px 15px;
+ font-size: @page-font-size;
+ .gitbook-markdown(@md-color: @page-color, @md-line-height: @page-line-height);
+ overflow: visible;
+
+ .glossary-term {
+ cursor: help;
+ text-decoration: underline;
+ }
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Panel.less b/packages/gitbook-plugin-theme-default/less/Panel.less
new file mode 100644
index 0000000..507396c
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Panel.less
@@ -0,0 +1,7 @@
+.GitBook-Panel {
+ border: 2px solid #f5f5f5;
+ padding: 10px;
+ background: #fafafa;
+ border-radius: 2px;
+ margin-top: 20px;
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Search.less b/packages/gitbook-plugin-theme-default/less/Search.less
new file mode 100644
index 0000000..faa871f
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Search.less
@@ -0,0 +1,38 @@
+.Search-Input {
+ padding: 6px;
+ background: transparent;
+ transition: top 0.5s ease;
+ background: #fff;
+ border-bottom: 1px solid @sidebar-border-color;
+ border-top: 1px solid @sidebar-border-color;
+ margin-bottom: 10px;
+
+ // Move top to hide top border
+ margin-top: -1px;
+
+ input, input:focus, input:hover {
+ width: 90%; // 10% room for clear input X
+ background: transparent;
+ border: 1px solid transparent;
+ box-shadow: none;
+ outline: none;
+ line-height: 22px;
+ padding: 7px 7px;
+ color: inherit;
+ }
+}
+
+.Search-Clear {
+ width: 10%;
+ display: inline-block;
+ text-align: center;
+ font-size: 14px;
+ line-height: 22px;
+ color: @search-clear-color;
+ cursor: pointer;
+}
+
+
+.Search-MatchSpan {
+ background: @search-highlight-color;
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Sidebar.less b/packages/gitbook-plugin-theme-default/less/Sidebar.less
new file mode 100644
index 0000000..1689b9f
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Sidebar.less
@@ -0,0 +1,29 @@
+.Sidebar-Flex {
+ .flex(0 0 @sidebar-width);
+
+ &.Layout-enter {
+ margin-left: -@sidebar-width;
+
+ &.Layout-enter-active {
+ margin-left: 0;
+ transition: margin-left 250ms ease-in-out;
+ }
+ }
+
+ &.Layout-leave {
+ margin-left: 0;
+
+ &.Layout-leave-active {
+ margin-left: -@sidebar-width;
+ transition: margin-left 250ms ease-in-out;
+ }
+ }
+}
+
+.Sidebar {
+ height: 100%;
+ background: @sidebar-background;
+ background: rgb(250, 250, 250);
+ border-right: 1px solid @sidebar-border-color;
+ overflow-y: auto;
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Summary.less b/packages/gitbook-plugin-theme-default/less/Summary.less
new file mode 100644
index 0000000..1e1e8ba
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Summary.less
@@ -0,0 +1,51 @@
+.Summary {
+
+}
+
+.SummaryPart {
+
+}
+
+.SummaryPart-Title {
+ margin: 0px;
+ padding: 2*@summary-article-padding-v @summary-article-padding-h;
+ text-transform: uppercase;
+ color: @summary-header-color;
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+.SummaryArticles {
+ list-style: none;
+ margin: 0px;
+ padding: 0px;
+}
+
+.SummaryArticle {
+ list-style: none;
+
+ a, span {
+ display: block;
+ padding: @summary-article-padding-v @summary-article-padding-h;
+ border-bottom: none;
+ color: @summary-article-color;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ position: relative;
+ text-decoration: none;
+ outline: none;
+ }
+
+ a:hover {
+ text-decoration: none;
+ color: @summary-article-hover-color;
+ }
+
+ &.active, &.active:hover {
+ a {
+ color: @summary-article-active-color;
+ background: @summary-article-active-background;
+ }
+ }
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Toolbar.less b/packages/gitbook-plugin-theme-default/less/Toolbar.less
new file mode 100644
index 0000000..8c59d96
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Toolbar.less
@@ -0,0 +1,27 @@
+.Toolbar {
+ .Toolbar-Title {
+ padding: 0px 20px;
+ margin: 0;
+ font-size: 20px;
+ font-weight: 200;
+ text-align: center;
+ line-height: 50px;
+ opacity: 0;
+ .transition(~"opacity ease .2s");
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: @button-hover-color;
+
+ a, a:hover {
+ text-decoration: none;
+ color: inherit;
+ }
+ }
+
+ &:hover {
+ .Toolbar-Title {
+ opacity: 1;
+ }
+ }
+}
diff --git a/packages/gitbook-plugin-theme-default/less/Tooltipped.less b/packages/gitbook-plugin-theme-default/less/Tooltipped.less
new file mode 100644
index 0000000..126daab
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/Tooltipped.less
@@ -0,0 +1,100 @@
+.GitBook-Tooltipped {
+ display: inline-block;
+ position: relative;
+
+ &:hover, &.Tooltipped-o {
+ &:after {
+ line-height: 1em;
+ background: @tooltip-background;
+ border-radius: @tooltip-radius;
+ bottom: auto;
+ top: ~"calc(100% + 10px)";
+ color: @tooltip-color;
+ content: attr(aria-label);
+ display: block;
+ left: 50%;
+ padding: 5px 5px;
+ position: absolute;
+ white-space: nowrap;
+ z-index: @zindex-tooltip;
+ font-size: 13px;
+ text-transform: none;
+ font-weight: @font-size-base;
+ pointer-events: none;
+ transform: translateX(-50%);
+ }
+
+ &:before {
+ border: solid;
+ border-color: @tooltip-background transparent;
+ bottom:auto;
+ top: ~"calc(100% + 5px)";
+ border-width: 0px 5px 5px 5px;
+ content: "";
+ display: block;
+ left: 50%;
+ position: absolute;
+ z-index: @zindex-tooltip+1;
+ transform: translateX(-50%);
+ }
+ }
+
+ .north() {
+ &:after {
+ top: auto;
+ bottom: ~"calc(100% + 10px)";
+ transform: translateX(0%);
+ }
+ &:before {
+ top: auto;
+ border-width: 5px 5px 0px 5px;
+ bottom: ~"calc(100% + 5px)";
+ transform: translateX(0%);
+ }
+ }
+ .west() {
+ &:after {
+ left: auto;
+ right: 5px;
+ transform: translateX(0%);
+ }
+ &:before {
+ left: auto;
+ right: 10px;
+ transform: translateX(0%);
+ }
+ }
+ .east() {
+ &:after {
+ right: auto;
+ left: 5px;
+ transform: translateX(0%);
+ }
+ &:before {
+ right: auto;
+ left: 10px;
+ transform: translateX(0%);
+ }
+ }
+
+ &.Tooltipped-e {
+ .east()
+ }
+
+ &.Tooltipped-n {
+ .north();
+ }
+
+ &.Tooltipped-ne {
+ .north();
+ .east();
+ }
+
+ &.Tooltipped-nw {
+ .north();
+ .west();
+ }
+ &.Tooltipped-sw, &.Tooltipped-w {
+ .west();
+ }
+}
diff --git a/packages/gitbook-plugin-theme-default/less/main.less b/packages/gitbook-plugin-theme-default/less/main.less
new file mode 100644
index 0000000..d3c0dd5
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/main.less
@@ -0,0 +1,50 @@
+@import "../node_modules/preboot/less/preboot.less";
+@import "../node_modules/gitbook-markdown-css/less/mixin.less";
+@import "../node_modules/font-awesome/less/font-awesome.less";
+
+@import "mixins.less";
+@import "reset.less";
+@import "variables.less";
+
+@import "Button.less";
+@import "Sidebar.less";
+@import "Summary.less";
+@import "Page.less";
+@import "Toolbar.less";
+@import "Search.less";
+@import "Body.less";
+@import "Dropdown.less";
+@import "LoadingBar.less";
+@import "Tooltipped.less";
+@import "Panel.less";
+
+* {
+ .box-sizing(border-box);
+ -webkit-overflow-scrolling: touch;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-text-size-adjust: none;
+ -webkit-touch-callout: none;
+ -webkit-font-smoothing: antialiased;
+}
+
+a {
+ text-decoration: none;
+}
+
+html, body {
+ margin: 0px;
+ height: 100%;
+}
+
+html {
+ font-size: 62.5%;
+}
+
+body {
+ text-rendering: optimizeLegibility;
+ font-smoothing: antialiased;
+ font-family: @font-family-base;
+ font-size: @font-size-base;
+ letter-spacing: .2px;
+ .text-adjust(100%);
+}
diff --git a/packages/gitbook-plugin-theme-default/less/mixins.less b/packages/gitbook-plugin-theme-default/less/mixins.less
new file mode 100644
index 0000000..e4308b9
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/mixins.less
@@ -0,0 +1,15 @@
+.text-adjust(@value) {
+ text-size-adjust: @value;
+ -ms-text-size-adjust: @value;
+ -webkit-text-size-adjust: @value;
+}
+
+// The 'flex' shorthand
+// - applies to: flex items
+// <positive-number>, initial, auto, or none
+.flex(@columns: initial) {
+ -webkit-flex: @columns;
+ -moz-flex: @columns;
+ -ms-flex: @columns;
+ flex: @columns;
+}
diff --git a/packages/gitbook-plugin-theme-default/less/reset.less b/packages/gitbook-plugin-theme-default/less/reset.less
new file mode 100644
index 0000000..a9c6f52
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/reset.less
@@ -0,0 +1,396 @@
+/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
+
+/* ==========================================================================
+ HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined in IE 8/9.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 8/9.
+ */
+
+audio,
+canvas,
+video {
+ display: inline-block;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+[hidden] {
+ display: none;
+}
+
+/* ==========================================================================
+ Base
+ ========================================================================== */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+ -ms-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Links
+ ========================================================================== */
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+ outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* ==========================================================================
+ Typography
+ ========================================================================== */
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari 5, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9, Safari 5, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari 5 and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Correct font family set oddly in Safari 5 and Chrome.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em;
+}
+
+/**
+ * Improve readability of pre-formatted text in all browsers.
+ */
+
+pre {
+ white-space: pre-wrap;
+}
+
+/**
+ * Set consistent quote types.
+ */
+
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* ==========================================================================
+ Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow displayed oddly in IE 9.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* ==========================================================================
+ Figures
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari 5.
+ */
+
+figure {
+ margin: 0;
+}
+
+/* ==========================================================================
+ Forms
+ ========================================================================== */
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ */
+
+button,
+input,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+ line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to `content-box` in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+ overflow: auto; /* 1 */
+ vertical-align: top; /* 2 */
+}
+
+/* ==========================================================================
+ Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/packages/gitbook-plugin-theme-default/less/variables.less b/packages/gitbook-plugin-theme-default/less/variables.less
new file mode 100644
index 0000000..5c6842d
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/less/variables.less
@@ -0,0 +1,55 @@
+// Colors
+@color-primary: hsl(207, 100%, 50%); // rgb(44, 106, 254);
+// Fonts
+@font-family-serif: Georgia, serif;
+@font-family-sans: "Helvetica Neue", Helvetica, Arial, sans-serif;
+@font-family-base: @font-family-sans;
+// Font sizes
+@font-size-base: 14px;
+@font-size-large: ceil(@font-size-base * 1.25); // ~18px
+@font-size-small: ceil(@font-size-base * 0.85); // ~12px
+@line-height-base: 1.428571429; // 20/14
+@line-height-computed: floor(@font-size-base * @line-height-base);
+// Sidebar
+@sidebar-background: rgb(250, 250, 250);
+@sidebar-border-color: rgba(0, 0, 0, 0.0666667);
+@sidebar-width: 300px;
+// Summary
+@summary-header-color: #939da3;
+@summary-article-padding-v: 10px;
+@summary-article-padding-h: 15px;
+@summary-article-color: hsl(207, 15%, 25%);
+@summary-article-hover-color: @color-primary;
+@summary-article-active-color: @summary-article-color;
+@summary-article-active-background: #f5f5f5;
+// Page
+@page-width: 800px;
+@page-color: #333333;
+@page-line-height: 1.7;
+@page-font-size: 16px;
+// Button
+@button-padding: 19px;
+@button-background: transparent;
+@button-color: #bbb;
+@button-hover-color: #a1a1a1;
+// Dropdown
+@dropdown-padding-v: 10px;
+@dropdown-padding-h: 15px;
+@dropdown-arrow-width: 8px;
+@dropdown-border-color: #e5e5e5;
+@dropdown-color: @button-color;
+@dropdown-hover-color: @button-hover-color;
+@dropdown-background: #fff;
+// Tooltip
+@tooltip-background: rgba(0,0,0,.8);
+@tooltip-radius: 3px;
+@tooltip-color: #fff;
+// Search
+@search-highlight-color: rgba(255, 220, 0, 0.4);
+@search-clear-color: @button-color;
+// Font awesome
+@path-assets: '.';
+@path-fonts: '@{path-assets}/fonts';
+@fa-font-path: '@{path-fonts}/fontawesome';
+// Z-indexes
+@zindex-tooltip: 300;
diff --git a/packages/gitbook-plugin-theme-default/package.json b/packages/gitbook-plugin-theme-default/package.json
new file mode 100644
index 0000000..7c44305
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/package.json
@@ -0,0 +1,80 @@
+{
+ "name": "gitbook-plugin-theme-default",
+ "description": "Default theme for GitBook",
+ "main": "./index.js",
+ "browser": "./_assets/theme.js",
+ "version": "4.0.0",
+ "engines": {
+ "gitbook": ">=3.0.0"
+ },
+ "dependencies": {
+ "debounce": "^1.0.0",
+ "gitbook-core": "4.0.0"
+ },
+ "devDependencies": {
+ "classnames": "^2.2.5",
+ "font-awesome": "^4.6.3",
+ "gitbook-markdown-css": "^1.0.1",
+ "gitbook-plugin": "4.0.0",
+ "less": "^2.7.1",
+ "less-plugin-clean-css": "^1.5.1",
+ "preboot": "git+https://github.com/mdo/preboot.git#4aab4edd85f076d50609cbe28e4fe66cc0771701"
+ },
+ "scripts": {
+ "prepublish": "./prepublish.sh"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "author": "GitBook Inc. <contact@gitbook.com>",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ },
+ "contributors": [
+ {
+ "name": "Samy Pessé",
+ "email": "samy@gitbook.com"
+ }
+ ],
+ "gitbook": {
+ "properties": {
+ "styles": {
+ "type": "object",
+ "title": "Custom Stylesheets",
+ "properties": {
+ "website": {
+ "title": "Stylesheet for website output",
+ "default": "styles/website.css"
+ },
+ "pdf": {
+ "title": "Stylesheet for PDF output",
+ "default": "styles/pdf.css"
+ },
+ "epub": {
+ "title": "Stylesheet for ePub output",
+ "default": "styles/epub.css"
+ },
+ "mobi": {
+ "title": "Stylesheet for Mobi output",
+ "default": "styles/mobi.css"
+ },
+ "ebook": {
+ "title": "Stylesheet for ebook outputs (PDF, ePub, Mobi)",
+ "default": "styles/ebook.css"
+ },
+ "print": {
+ "title": "Stylesheet to replace default ebook css",
+ "default": "styles/print.css"
+ }
+ }
+ },
+ "showLevel": {
+ "type": "boolean",
+ "title": "Show level indicator in TOC",
+ "default": false
+ }
+ }
+ }
+}
diff --git a/packages/gitbook-plugin-theme-default/prepublish.sh b/packages/gitbook-plugin-theme-default/prepublish.sh
new file mode 100755
index 0000000..458df9b
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/prepublish.sh
@@ -0,0 +1,11 @@
+#! /bin/bash
+#
+# Compile LESS To CSS
+lessc -clean-css ./less/main.less ./_assets/website/theme.css
+
+# Compile JS
+gitbook-plugin build ./src/index.js ./_assets/theme.js
+
+# Copy fonts
+mkdir -p _assets/website/fonts
+cp -R node_modules/font-awesome/fonts/ _assets/website/fonts/fontawesome/
diff --git a/packages/gitbook-plugin-theme-default/src/actions/sidebar.js b/packages/gitbook-plugin-theme-default/src/actions/sidebar.js
new file mode 100644
index 0000000..52f8422
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/actions/sidebar.js
@@ -0,0 +1,13 @@
+const ActionTypes = require('./types');
+
+/**
+ * Toggle the sidebar
+ * @return {Action}
+ */
+function toggle() {
+ return { type: ActionTypes.TOGGLE_SIDEBAR };
+}
+
+module.exports = {
+ toggle
+};
diff --git a/packages/gitbook-plugin-theme-default/src/actions/types.js b/packages/gitbook-plugin-theme-default/src/actions/types.js
new file mode 100644
index 0000000..9f8a80f
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/actions/types.js
@@ -0,0 +1,4 @@
+
+module.exports = {
+ TOGGLE_SIDEBAR: 'theme-default/sidebar/toggle'
+};
diff --git a/packages/gitbook-plugin-theme-default/src/components/Body.js b/packages/gitbook-plugin-theme-default/src/components/Body.js
new file mode 100644
index 0000000..c61a2f3
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Body.js
@@ -0,0 +1,121 @@
+const debounce = require('debounce');
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const Page = require('./Page');
+const Toolbar = require('./Toolbar');
+
+const HEADINGS_SELECTOR = 'h1[id],h2[id],h3[id],h4[id]';
+
+/**
+ * Get offset of an element relative to a parent container.
+ * @param {DOMElement} container
+ * @param {DOMElement} element
+ * @return {Number} offset
+ */
+function getOffset(container, element, type = 'Top') {
+ const parent = element.parentElement;
+ let base = 0;
+
+ if (parent != container) {
+ base = getOffset(container, parent, type);
+ }
+
+ return base + element[`offset${type}`];
+}
+
+/**
+ * Find the current heading anchor for a scroll position.
+ * @param {DOMElement} container
+ * @param {Number} top
+ * @return {String}
+ */
+function getHeadingID(container, top) {
+ let id;
+ const headings = container.querySelectorAll(HEADINGS_SELECTOR);
+
+ headings.forEach(heading => {
+ if (id) {
+ return;
+ }
+
+ const offset = getOffset(container, heading);
+
+ if (offset > top) {
+ id = heading.getAttribute('id');
+ }
+ });
+
+ return id;
+}
+
+const Body = React.createClass({
+ propTypes: {
+ page: GitBook.PropTypes.Page,
+ readme: GitBook.PropTypes.Readme,
+ history: GitBook.PropTypes.History,
+ updateURI: React.PropTypes.func
+ },
+
+ getInitialState() {
+ this.debouncedOnScroll = debounce(this.onScroll, 300);
+ return {};
+ },
+
+ /**
+ * User is scrolling the page, update the location with current section's ID.
+ */
+ onScroll() {
+ const { scrollContainer } = this;
+ const { history, updateURI } = this.props;
+ const { location } = history;
+
+ // Find the id matching the current scroll position
+ const hash = getHeadingID(scrollContainer, scrollContainer.scrollTop);
+
+ // Update url if changed
+ if (hash !== location.hash) {
+ updateURI(location.merge({ hash }));
+ }
+ },
+
+ /**
+ * Component has been updated with a new location,
+ * scroll to the right anchor.
+ */
+ componentDidUpdate() {
+
+ },
+
+ render() {
+ const { page, readme } = this.props;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'body:wrapper' }}>
+ <div
+ className="Body page-wrapper"
+ onScroll={this.debouncedOnScroll}
+ ref={div => this.scrollContainer = div}
+ >
+ <GitBook.InjectedComponent matching={{ role: 'toolbar:wrapper' }}>
+ <Toolbar title={page.title} readme={readme} />
+ </GitBook.InjectedComponent>
+ <GitBook.InjectedComponent matching={{ role: 'page:wrapper' }}>
+ <Page page={page} />
+ </GitBook.InjectedComponent>
+ </div>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+module.exports = GitBook.connect(Body,
+ () => {
+ return {};
+ },
+ ({ History }, dispatch) => {
+ return {
+ updateURI: (location) => dispatch(History.replace(location))
+ };
+ }
+);
diff --git a/packages/gitbook-plugin-theme-default/src/components/LoadingBar.js b/packages/gitbook-plugin-theme-default/src/components/LoadingBar.js
new file mode 100644
index 0000000..11e1ddb
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/LoadingBar.js
@@ -0,0 +1,124 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+/**
+ * Displays a progress bar (YouTube-like) at the top of container
+ * Based on https://github.com/lonelyclick/react-loading-bar/blob/master/src/Loading.jsx
+ */
+const LoadingBar = React.createClass({
+ propTypes: {
+ show: React.PropTypes.bool
+ },
+
+ getDefaultProps() {
+ return {
+ show: false
+ };
+ },
+
+ getInitialState() {
+ return {
+ size: 0,
+ disappearDelayHide: false, // when dispappear, first transition then display none
+ percent: 0,
+ appearDelayWidth: 0 // when appear, first display block then transition width
+ };
+ },
+
+ componentWillReceiveProps(nextProps) {
+ const { show } = nextProps;
+
+ if (show) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ },
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return true; // !shallowEqual(nextState, this.state)
+ },
+
+ show() {
+ let { size, percent } = this.state;
+
+ const appearDelayWidth = size === 0;
+ percent = calculatePercent(percent);
+
+ this.setState({
+ size: ++size,
+ appearDelayWidth,
+ percent
+ });
+
+ if (appearDelayWidth) {
+ setTimeout(() => {
+ this.setState({
+ appearDelayWidth: false
+ });
+ });
+ }
+ },
+
+ hide() {
+ let { size } = this.state;
+
+ if (--size < 0) {
+ this.setState({ size: 0 });
+ return;
+ }
+
+ this.setState({
+ size: 0,
+ disappearDelayHide: true,
+ percent: 1
+ });
+
+ setTimeout(() => {
+ this.setState({
+ disappearDelayHide: false,
+ percent: 0
+ });
+ }, 500);
+ },
+
+ getBarStyle() {
+ const { disappearDelayHide, appearDelayWidth, percent } = this.state;
+
+ return {
+ width: appearDelayWidth ? 0 : percent * 100 + '%',
+ display: disappearDelayHide || percent > 0 ? 'block' : 'none'
+ };
+ },
+
+ getShadowStyle() {
+ const { percent, disappearDelayHide } = this.state;
+
+ return {
+ display: disappearDelayHide || percent > 0 ? 'block' : 'none'
+ };
+ },
+
+ render() {
+ return (
+ <div className="LoadingBar">
+ <div className="LoadingBar-Bar" style={this.getBarStyle()}>
+ <div className="LoadingBar-Shadow"
+ style={this.getShadowStyle()}>
+ </div>
+ </div>
+ </div>
+ );
+ }
+});
+
+function calculatePercent(percent) {
+ percent = percent || 0;
+
+ // How much of remaining bar we advance
+ const progress = 0.1 + Math.random() * 0.3;
+
+ return percent + progress * (1 - percent);
+}
+
+module.exports = LoadingBar;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Page.js b/packages/gitbook-plugin-theme-default/src/components/Page.js
new file mode 100644
index 0000000..cbce704
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Page.js
@@ -0,0 +1,30 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const Page = React.createClass({
+ propTypes: {
+ page: GitBook.PropTypes.Page
+ },
+
+ render() {
+ const { page } = this.props;
+
+ return (
+ <div className="PageContainer">
+ <GitBook.InjectedComponent matching={{ role: 'search:container:results' }} props={this.props}>
+ <div className="Page">
+ <GitBook.InjectedComponentSet matching={{ role: 'page:header' }} props={this.props} />
+
+ <GitBook.InjectedComponent matching={{ role: 'page:container' }} props={this.props}>
+ <GitBook.HTMLContent html={page.content} />
+ </GitBook.InjectedComponent>
+
+ <GitBook.InjectedComponentSet matching={{ role: 'page:footer' }} props={this.props} />
+ </div>
+ </GitBook.InjectedComponent>
+ </div>
+ );
+ }
+});
+
+module.exports = Page;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Sidebar.js b/packages/gitbook-plugin-theme-default/src/components/Sidebar.js
new file mode 100644
index 0000000..ab628df
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Sidebar.js
@@ -0,0 +1,25 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const Summary = require('./Summary');
+
+const Sidebar = React.createClass({
+ propTypes: {
+ summary: GitBook.PropTypes.Summary
+ },
+
+ render() {
+ const { summary } = this.props;
+
+ return (
+ <div className="Sidebar-Flex">
+ <div className="Sidebar book-summary">
+ <GitBook.InjectedComponent matching={{ role: 'search:container:input' }} />
+ <Summary summary={summary} />
+ </div>
+ </div>
+ );
+ }
+});
+
+module.exports = Sidebar;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Summary.js b/packages/gitbook-plugin-theme-default/src/components/Summary.js
new file mode 100644
index 0000000..ef6ab3f
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Summary.js
@@ -0,0 +1,111 @@
+const classNames = require('classnames');
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+let SummaryArticle = React.createClass({
+ propTypes: {
+ active: React.PropTypes.bool,
+ article: GitBook.PropTypes.SummaryArticle
+ },
+
+ render() {
+ const { article, active } = this.props;
+ const className = classNames('SummaryArticle', {
+ active
+ });
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:article' }} props={this.props}>
+ <li className={className}>
+ {article.ref ?
+ <GitBook.Link to={article}>{article.title}</GitBook.Link>
+ : <span>{article.title}</span>}
+ </li>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+SummaryArticle = GitBook.connect(SummaryArticle, ({page}, {article}) => {
+ return {
+ active: page.level === article.level
+ };
+});
+
+const SummaryArticles = React.createClass({
+ propTypes: {
+ articles: GitBook.PropTypes.listOf(GitBook.PropTypes.SummaryArticle)
+ },
+
+ render() {
+ const { articles } = this.props;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:articles' }} props={this.props}>
+ <ul className="SummaryArticles">
+ {articles.map(article => <SummaryArticle key={article.level} article={article} />)}
+ </ul>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+const SummaryPart = React.createClass({
+ propTypes: {
+ part: GitBook.PropTypes.SummaryPart
+ },
+
+ render() {
+ const { part } = this.props;
+ const { title, articles } = part;
+
+ const titleEL = title ? <h2 className="SummaryPart-Title">{title}</h2> : null;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:part' }} props={this.props}>
+ <div className="SummaryPart">
+ {titleEL}
+ <SummaryArticles articles={articles} />
+ </div>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+const SummaryParts = React.createClass({
+ propTypes: {
+ parts: GitBook.PropTypes.listOf(GitBook.PropTypes.SummaryPart)
+ },
+
+ render() {
+ const { parts } = this.props;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:parts' }} props={this.props}>
+ <div className="SummaryParts">
+ {parts.map((part, i) => <SummaryPart key={i} part={part} />)}
+ </div>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+const Summary = React.createClass({
+ propTypes: {
+ summary: GitBook.PropTypes.Summary
+ },
+
+ render() {
+ const { summary } = this.props;
+ const { parts } = summary;
+
+ return (
+ <GitBook.InjectedComponent matching={{ role: 'summary:container' }} props={this.props}>
+ <div className="Summary book-summary">
+ <SummaryParts parts={parts} />
+ </div>
+ </GitBook.InjectedComponent>
+ );
+ }
+});
+
+module.exports = Summary;
diff --git a/packages/gitbook-plugin-theme-default/src/components/Theme.js b/packages/gitbook-plugin-theme-default/src/components/Theme.js
new file mode 100644
index 0000000..b323fc4
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Theme.js
@@ -0,0 +1,57 @@
+const GitBook = require('gitbook-core');
+const { React, ReactCSSTransitionGroup } = GitBook;
+
+const Sidebar = require('./Sidebar');
+const Body = require('./Body');
+const LoadingBar = require('./LoadingBar');
+
+const Theme = React.createClass({
+ propTypes: {
+ // State
+ page: GitBook.PropTypes.Page,
+ summary: GitBook.PropTypes.Summary,
+ readme: GitBook.PropTypes.Readme,
+ history: GitBook.PropTypes.History,
+ sidebar: React.PropTypes.object,
+ // Other props
+ children: React.PropTypes.node
+ },
+
+ render() {
+ const { page, summary, children, sidebar, readme, history } = this.props;
+
+ return (
+ <GitBook.FlexLayout column className="GitBook book">
+ <LoadingBar show={history.loading} />
+ <GitBook.Head
+ title={page.title}
+ titleTemplate="%s - GitBook" />
+ <GitBook.ImportCSS href="gitbook/theme-default/theme.css" />
+
+ <GitBook.FlexBox>
+ <ReactCSSTransitionGroup
+ component={GitBook.FlexLayout}
+ transitionName="Layout"
+ transitionEnterTimeout={300}
+ transitionLeaveTimeout={300}>
+ {sidebar.open ? (
+ <Sidebar key={0} summary={summary} />
+ ) : null}
+ <div key={1} className="Body-Flex">
+ <Body
+ page={page}
+ readme={readme}
+ history={history}
+ />
+ </div>
+ </ReactCSSTransitionGroup>
+ </GitBook.FlexBox>
+ {children}
+ </GitBook.FlexLayout>
+ );
+ }
+});
+
+module.exports = GitBook.connect(Theme, ({page, summary, sidebar, readme, history}) => {
+ return { page, summary, sidebar, readme, history };
+});
diff --git a/packages/gitbook-plugin-theme-default/src/components/Toolbar.js b/packages/gitbook-plugin-theme-default/src/components/Toolbar.js
new file mode 100644
index 0000000..d426a40
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/components/Toolbar.js
@@ -0,0 +1,43 @@
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const sidebar = require('../actions/sidebar');
+
+const Toolbar = React.createClass({
+ propTypes: {
+ title: React.PropTypes.string.isRequired,
+ dispatch: React.PropTypes.func,
+ readme: GitBook.PropTypes.Readme
+ },
+
+ onToggle() {
+ const { dispatch } = this.props;
+ dispatch(sidebar.toggle());
+ },
+
+ render() {
+ const { title, readme } = this.props;
+
+ return (
+ <GitBook.FlexLayout className="Toolbar">
+ <GitBook.FlexBox className="Toolbar-left">
+ <GitBook.InjectedComponentSet align="flex-end" matching={{ role: 'toolbar:buttons:left' }}>
+ <GitBook.Button onClick={this.onToggle}>
+ <GitBook.Icon id="align-justify" />
+ </GitBook.Button>
+ </GitBook.InjectedComponentSet>
+ </GitBook.FlexBox>
+ <GitBook.FlexBox auto>
+ <h1 className="Toolbar-Title">
+ <GitBook.Link to={readme.file}>{title}</GitBook.Link>
+ </h1>
+ </GitBook.FlexBox>
+ <GitBook.FlexBox className="Toolbar-right">
+ <GitBook.InjectedComponentSet align="flex-end" matching={{ role: 'toolbar:buttons:right' }} />
+ </GitBook.FlexBox>
+ </GitBook.FlexLayout>
+ );
+ }
+});
+
+module.exports = GitBook.connect(Toolbar);
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/ar.json b/packages/gitbook-plugin-theme-default/src/i18n/ar.json
new file mode 100644
index 0000000..f652c1a
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/ar.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "اختيار اللغة",
+ "GLOSSARY": "قاموس مصطلحات",
+ "GLOSSARY_INDEX": "مؤشر المصطلحات",
+ "GLOSSARY_OPEN": "قاموس مصطلحات",
+ "GITBOOK_LINK": "نشرت مع GitBook",
+ "SUMMARY": "جدول المحتويات",
+ "SUMMARY_INTRODUCTION": "مقدمة",
+ "SUMMARY_TOGGLE": "جدول المحتويات",
+ "SEARCH_TOGGLE": "بحث",
+ "SEARCH_PLACEHOLDER": "اكتب للبحث",
+ "FONTSETTINGS_TOGGLE": "إعدادات الخط",
+ "SHARE_TOGGLE": "حصة",
+ "SHARE_ON": "على {{platform}} حصة",
+ "FONTSETTINGS_WHITE": "أبيض",
+ "FONTSETTINGS_SEPIA": "بني داكن",
+ "FONTSETTINGS_NIGHT": "ليل",
+ "FONTSETTINGS_SANS": "بلا",
+ "FONTSETTINGS_SERIF": "الرقيق"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/bn.json b/packages/gitbook-plugin-theme-default/src/i18n/bn.json
new file mode 100644
index 0000000..24baec3
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/bn.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "ভাষা নির্বাচন করুন",
+ "GLOSSARY": "গ্লোসারি",
+ "GLOSSARY_INDEX": "ইন্ডেক্স",
+ "GLOSSARY_OPEN": "গ্লোসারি",
+ "GITBOOK_LINK": "গিটবুকের মাধ্যমে প্রকাশিত",
+ "SUMMARY": "সূচিপত্র",
+ "SUMMARY_INTRODUCTION": "সূচনা",
+ "SUMMARY_TOGGLE": "সূচিপত্র",
+ "SEARCH_TOGGLE": "অনুসন্ধান",
+ "SEARCH_PLACEHOLDER": "অনুসন্ধান",
+ "FONTSETTINGS_TOGGLE": "ফন্ট সেটিংস",
+ "SHARE_TOGGLE": "শেয়ার",
+ "SHARE_ON": "{{platform}}-এ শেয়ার",
+ "FONTSETTINGS_WHITE": "সাদা",
+ "FONTSETTINGS_SEPIA": "সেপিয়া",
+ "FONTSETTINGS_NIGHT": "রাত",
+ "FONTSETTINGS_SANS": "স্যান্স",
+ "FONTSETTINGS_SERIF": "শেরিফ"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/ca.json b/packages/gitbook-plugin-theme-default/src/i18n/ca.json
new file mode 100644
index 0000000..d26edb6
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/ca.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Selecciona un idioma",
+ "GLOSSARY": "Glossari",
+ "GLOSSARY_INDEX": "Índex",
+ "GLOSSARY_OPEN": "Glossari",
+ "GITBOOK_LINK": "Publicat amb GitBook",
+ "SUMMARY": "Taula de contingut",
+ "SUMMARY_INTRODUCTION": "Introducció",
+ "SUMMARY_TOGGLE": "Taula de contingut",
+ "SEARCH_TOGGLE": "Cercar",
+ "SEARCH_PLACEHOLDER": "Escriu per cercar",
+ "FONTSETTINGS_TOGGLE": "Configuració de font",
+ "SHARE_TOGGLE": "Compartir",
+ "SHARE_ON": "Compartir en {{platform}}",
+ "FONTSETTINGS_WHITE": "Clar",
+ "FONTSETTINGS_SEPIA": "Sèpia",
+ "FONTSETTINGS_NIGHT": "Nit",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/cs.json b/packages/gitbook-plugin-theme-default/src/i18n/cs.json
new file mode 100644
index 0000000..b2e19c0
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/cs.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Zvolte jazyk",
+ "GLOSSARY": "Slovníček",
+ "GLOSSARY_INDEX": "Rejstřík",
+ "GLOSSARY_OPEN": "Slovníček",
+ "GITBOOK_LINK": "Publikováno pomocí GitBook",
+ "SUMMARY": "Obsah",
+ "SUMMARY_INTRODUCTION": "Úvod",
+ "SUMMARY_TOGGLE": "Obsah",
+ "SEARCH_TOGGLE": "Hledání",
+ "SEARCH_PLACEHOLDER": "Vyhledat",
+ "FONTSETTINGS_TOGGLE": "Nastavení písma",
+ "SHARE_TOGGLE": "Sdílet",
+ "SHARE_ON": "Sdílet na {{platform}}",
+ "FONTSETTINGS_WHITE": "Bílá",
+ "FONTSETTINGS_SEPIA": "Sépie",
+ "FONTSETTINGS_NIGHT": "Noc",
+ "FONTSETTINGS_SANS": "Bezpatkové",
+ "FONTSETTINGS_SERIF": "Patkové"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/de.json b/packages/gitbook-plugin-theme-default/src/i18n/de.json
new file mode 100644
index 0000000..b51732e
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/de.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Sprache auswählen",
+ "GLOSSARY": "Glossar",
+ "GLOSSARY_INDEX": "Index",
+ "GLOSSARY_OPEN": "Glossar",
+ "GITBOOK_LINK": "Veröffentlicht mit GitBook",
+ "SUMMARY": "Inhaltsverzeichnis",
+ "SUMMARY_INTRODUCTION": "Einleitung",
+ "SUMMARY_TOGGLE": "Inhaltsverzeichnis",
+ "SEARCH_TOGGLE": "Suche",
+ "SEARCH_PLACEHOLDER": "Suchbegriff eingeben",
+ "FONTSETTINGS_TOGGLE": "Schrifteinstellungen",
+ "SHARE_TOGGLE": "Teilen",
+ "SHARE_ON": "Auf {{platform}} teilen",
+ "FONTSETTINGS_WHITE": "Hell",
+ "FONTSETTINGS_SEPIA": "Sepia",
+ "FONTSETTINGS_NIGHT": "Nacht",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+} \ No newline at end of file
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/el.json b/packages/gitbook-plugin-theme-default/src/i18n/el.json
new file mode 100644
index 0000000..5198e60
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/el.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Επιλογή γλώσσας",
+ "GLOSSARY": "Γλωσσάρι",
+ "GLOSSARY_INDEX": "Ευρετήριο",
+ "GLOSSARY_OPEN": "Γλωσσάρι",
+ "GITBOOK_LINK": "Δημοσιεύτηκε με το GitBook",
+ "SUMMARY": "Πίνακας Περιεχομένων",
+ "SUMMARY_INTRODUCTION": "Εισαγωγή",
+ "SUMMARY_TOGGLE": "Πίνακας Περιεχομένων",
+ "SEARCH_TOGGLE": "Αναζήτηση",
+ "SEARCH_PLACEHOLDER": "Αναζήτηση για ...",
+ "FONTSETTINGS_TOGGLE": "Επιλογές γραμματοσειράς",
+ "SHARE_TOGGLE": "Κοινοποίηση",
+ "SHARE_ON": "Κοινοποίηση σε {{platform}}",
+ "FONTSETTINGS_WHITE": "Λευκό",
+ "FONTSETTINGS_SEPIA": "Καστανόχρους",
+ "FONTSETTINGS_NIGHT": "Βραδινό",
+ "FONTSETTINGS_SANS": "Χωρίς πατούρες",
+ "FONTSETTINGS_SERIF": "Με πατούρες"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/en.json b/packages/gitbook-plugin-theme-default/src/i18n/en.json
new file mode 100644
index 0000000..b6504d3
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/en.json
@@ -0,0 +1,21 @@
+{
+ "LANGS_CHOOSE": "Choose a language",
+ "GLOSSARY": "Glossary",
+ "GLOSSARY_INDEX": "Index",
+ "GLOSSARY_OPEN": "Glossary",
+ "GITBOOK_LINK": "Published with GitBook",
+ "SUMMARY": "Table of Contents",
+ "SUMMARY_INTRODUCTION": "Introduction",
+ "SUMMARY_TOGGLE": "Table of Contents",
+ "SEARCH_TOGGLE": "Search",
+ "SEARCH_PLACEHOLDER": "Type to search",
+ "SEARCH_RESULTS_TITLE": "{count, plural, =0 {No results} one {1 result} other {{count} results}} matching \"{query}\"",
+ "FONTSETTINGS_TOGGLE": "Font Settings",
+ "SHARE_TOGGLE": "Share",
+ "SHARE_ON": "Share on {{platform}}",
+ "FONTSETTINGS_WHITE": "White",
+ "FONTSETTINGS_SEPIA": "Sepia",
+ "FONTSETTINGS_NIGHT": "Night",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/es.json b/packages/gitbook-plugin-theme-default/src/i18n/es.json
new file mode 100644
index 0000000..36159be
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/es.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Selecciona un idioma",
+ "GLOSSARY": "Glosario",
+ "GLOSSARY_INDEX": "Índice",
+ "GLOSSARY_OPEN": "Glosario",
+ "GITBOOK_LINK": "Publicado con GitBook",
+ "SUMMARY": "Tabla de contenido",
+ "SUMMARY_INTRODUCTION": "Introducción",
+ "SUMMARY_TOGGLE": "Tabla de contenido",
+ "SEARCH_TOGGLE": "Buscar",
+ "SEARCH_PLACEHOLDER": "Escribe para buscar",
+ "FONTSETTINGS_TOGGLE": "Configuración de fuente",
+ "SHARE_TOGGLE": "Compartir",
+ "SHARE_ON": "Compartir en {{platform}}",
+ "FONTSETTINGS_WHITE": "Claro",
+ "FONTSETTINGS_SEPIA": "Sépia",
+ "FONTSETTINGS_NIGHT": "Noche",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/fa.json b/packages/gitbook-plugin-theme-default/src/i18n/fa.json
new file mode 100644
index 0000000..56ded4f
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/fa.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "انتخاب زبان",
+ "GLOSSARY": "واژه‌نامه",
+ "GLOSSARY_INDEX": "فهرست واژه‌ها",
+ "GLOSSARY_OPEN": "واژه‌نامه",
+ "GITBOOK_LINK": "انتشار یافته توسط GitBook",
+ "SUMMARY": "فهرست مطالب",
+ "SUMMARY_INTRODUCTION": "مقدمه",
+ "SUMMARY_TOGGLE": "فهرست مطالب",
+ "SEARCH_TOGGLE": "جستجو",
+ "SEARCH_PLACEHOLDER": "چیزی برای جستجو بنویسید",
+ "FONTSETTINGS_TOGGLE": "تنظیمات فونت",
+ "SHARE_TOGGLE": "اشتراک",
+ "SHARE_ON": "در {{platform}} به اشتراک بگذارید",
+ "FONTSETTINGS_WHITE": "سفید",
+ "FONTSETTINGS_SEPIA": "سپیا",
+ "FONTSETTINGS_NIGHT": "شب",
+ "FONTSETTINGS_SANS": "سنس",
+ "FONTSETTINGS_SERIF": "سریف"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/fi.json b/packages/gitbook-plugin-theme-default/src/i18n/fi.json
new file mode 100644
index 0000000..a8476ca
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/fi.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Valitse kieli",
+ "GLOSSARY": "Sanasto",
+ "GLOSSARY_INDEX": "Hakemisto",
+ "GLOSSARY_OPEN": "Sanasto",
+ "GITBOOK_LINK": "Julkaistu GitBookilla",
+ "SUMMARY": "Sisällysluettelo",
+ "SUMMARY_INTRODUCTION": "Johdanto",
+ "SUMMARY_TOGGLE": "Sisällysluettelu",
+ "SEARCH_TOGGLE": "Etsi",
+ "SEARCH_PLACEHOLDER": "Kirjoita hakusana",
+ "FONTSETTINGS_TOGGLE": "Fonttivalinnat",
+ "SHARE_TOGGLE": "Jaa",
+ "SHARE_ON": "Jaa {{platform}}ssa",
+ "FONTSETTINGS_WHITE": "Valkoinen",
+ "FONTSETTINGS_SEPIA": "Seepia",
+ "FONTSETTINGS_NIGHT": "Yö",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/fr.json b/packages/gitbook-plugin-theme-default/src/i18n/fr.json
new file mode 100644
index 0000000..8cc10e2
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/fr.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Choisissez une langue",
+ "GLOSSARY": "Glossaire",
+ "GLOSSARY_INDEX": "Index",
+ "GLOSSARY_OPEN": "Glossaire",
+ "GITBOOK_LINK": "Publié avec GitBook",
+ "SUMMARY": "Table des matières",
+ "SUMMARY_INTRODUCTION": "Introduction",
+ "SUMMARY_TOGGLE": "Table des matières",
+ "SEARCH_TOGGLE": "Recherche",
+ "SEARCH_PLACEHOLDER": "Tapez pour rechercher",
+ "FONTSETTINGS_TOGGLE": "Paramètres de Police",
+ "SHARE_TOGGLE": "Partage",
+ "SHARE_ON": "Partager sur {{platform}}",
+ "FONTSETTINGS_WHITE": "Clair",
+ "FONTSETTINGS_SEPIA": "Sépia",
+ "FONTSETTINGS_NIGHT": "Nuit",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+} \ No newline at end of file
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/he.json b/packages/gitbook-plugin-theme-default/src/i18n/he.json
new file mode 100644
index 0000000..353d3b5
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/he.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "בחר שפה",
+ "GLOSSARY": "מונחים",
+ "GLOSSARY_INDEX": "מפתח",
+ "GLOSSARY_OPEN": "מונחים",
+ "GITBOOK_LINK": "הוצאה לאור באמצעות גיט-בוק GITBOOK",
+ "SUMMARY": "תוכן העניינים",
+ "SUMMARY_INTRODUCTION": "הוראות",
+ "SUMMARY_TOGGLE": "תקציר",
+ "SEARCH_TOGGLE": "חיפוש",
+ "SEARCH_PLACEHOLDER": "סוג החיפוש",
+ "FONTSETTINGS_TOGGLE": "הגדרת אותיות",
+ "SHARE_TOGGLE": "שתף",
+ "SHARE_ON": "{{platform}} שתף ב",
+ "FONTSETTINGS_WHITE": "בהיר",
+ "FONTSETTINGS_SEPIA": "חום כהה",
+ "FONTSETTINGS_NIGHT": "מצב לילה",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/index.js b/packages/gitbook-plugin-theme-default/src/i18n/index.js
new file mode 100644
index 0000000..d09de1b
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/index.js
@@ -0,0 +1,30 @@
+
+module.exports = {
+ ar: require('./ar'),
+ bn: require('./bn'),
+ ca: require('./ca'),
+ cs: require('./cs'),
+ de: require('./de'),
+ el: require('./el'),
+ en: require('./en'),
+ es: require('./es'),
+ fa: require('./fa'),
+ fi: require('./fi'),
+ fr: require('./fr'),
+ he: require('./he'),
+ it: require('./it'),
+ ja: require('./ja'),
+ ko: require('./ko'),
+ nl: require('./nl'),
+ no: require('./no'),
+ pl: require('./pl'),
+ pt: require('./pt'),
+ ro: require('./ro'),
+ ru: require('./ru'),
+ sv: require('./sv'),
+ tr: require('./tr'),
+ uk: require('./uk'),
+ vi: require('./vi'),
+ 'zh-hans': require('./zh-hans'),
+ 'zh-tw': require('./zh-tw'),
+};
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/it.json b/packages/gitbook-plugin-theme-default/src/i18n/it.json
new file mode 100644
index 0000000..3f5e95d
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/it.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Scegli una lingua",
+ "GLOSSARY": "Glossario",
+ "GLOSSARY_INDEX": "Indice",
+ "GLOSSARY_OPEN": "Glossario",
+ "GITBOOK_LINK": "Pubblicato con GitBook",
+ "SUMMARY": "Sommario",
+ "SUMMARY_INTRODUCTION": "Introduzione",
+ "SUMMARY_TOGGLE": "Sommario",
+ "SEARCH_TOGGLE": "Cerca",
+ "SEARCH_PLACEHOLDER": "Scrivi per cercare",
+ "FONTSETTINGS_TOGGLE": "Impostazioni dei caratteri",
+ "SHARE_TOGGLE": "Condividi",
+ "SHARE_ON": "Condividi su {{platform}}",
+ "FONTSETTINGS_WHITE": "Bianco",
+ "FONTSETTINGS_SEPIA": "Seppia",
+ "FONTSETTINGS_NIGHT": "Notte",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+} \ No newline at end of file
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/ja.json b/packages/gitbook-plugin-theme-default/src/i18n/ja.json
new file mode 100644
index 0000000..b1afd02
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/ja.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "言語を選択",
+ "GLOSSARY": "用語集",
+ "GLOSSARY_INDEX": "索引",
+ "GLOSSARY_OPEN": "用語集",
+ "GITBOOK_LINK": "GitBookで公開 ",
+ "SUMMARY": "目次",
+ "SUMMARY_INTRODUCTION": "はじめに",
+ "SUMMARY_TOGGLE": "目次",
+ "SEARCH_TOGGLE": "検索",
+ "SEARCH_PLACEHOLDER": "検索すると入力",
+ "FONTSETTINGS_TOGGLE": "フォント設定",
+ "SHARE_TOGGLE": "シェア",
+ "SHARE_ON": "{{platform}}でシェア",
+ "FONTSETTINGS_WHITE": "白",
+ "FONTSETTINGS_SEPIA": "セピア",
+ "FONTSETTINGS_NIGHT": "夜",
+ "FONTSETTINGS_SANS": "ゴシック体",
+ "FONTSETTINGS_SERIF": "明朝体"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/ko.json b/packages/gitbook-plugin-theme-default/src/i18n/ko.json
new file mode 100644
index 0000000..5015a93
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/ko.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "언어를 선택하세요",
+ "GLOSSARY": "어휘",
+ "GLOSSARY_INDEX": "색인",
+ "GLOSSARY_OPEN": "어휘",
+ "GITBOOK_LINK": "GitBook에 게시",
+ "SUMMARY": "차례",
+ "SUMMARY_INTRODUCTION": "소개",
+ "SUMMARY_TOGGLE": "차례",
+ "SEARCH_TOGGLE": "검색",
+ "SEARCH_PLACEHOLDER": "검색어 입력",
+ "FONTSETTINGS_TOGGLE": "글꼴 설정",
+ "SHARE_TOGGLE": "공유",
+ "SHARE_ON": "{{platform}}에 공유",
+ "FONTSETTINGS_WHITE": "화이트",
+ "FONTSETTINGS_SEPIA": "세피아",
+ "FONTSETTINGS_NIGHT": "나이트",
+ "FONTSETTINGS_SANS": "고딕",
+ "FONTSETTINGS_SERIF": "명조"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/nl.json b/packages/gitbook-plugin-theme-default/src/i18n/nl.json
new file mode 100644
index 0000000..da4f59e
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/nl.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Kies een taal",
+ "GLOSSARY": "Begrippenlijst",
+ "GLOSSARY_INDEX": "Index",
+ "GLOSSARY_OPEN": "Begrippenlijst",
+ "GITBOOK_LINK": "Gepubliceerd met GitBook",
+ "SUMMARY": "Inhoudsopgave",
+ "SUMMARY_INTRODUCTION": "Inleiding",
+ "SUMMARY_TOGGLE": "Inhoudsopgave",
+ "SEARCH_TOGGLE": "Zoeken",
+ "SEARCH_PLACEHOLDER": "Zoeken",
+ "FONTSETTINGS_TOGGLE": "Lettertype instellingen",
+ "SHARE_TOGGLE": "Delen",
+ "SHARE_ON": "Delen op {{platform}}",
+ "FONTSETTINGS_WHITE": "Wit",
+ "FONTSETTINGS_SEPIA": "Sepia",
+ "FONTSETTINGS_NIGHT": "Zwart",
+ "FONTSETTINGS_SANS": "Schreefloos",
+ "FONTSETTINGS_SERIF": "Schreef"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/no.json b/packages/gitbook-plugin-theme-default/src/i18n/no.json
new file mode 100644
index 0000000..1ed6236
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/no.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Velg språk",
+ "GLOSSARY": "Register",
+ "GLOSSARY_INDEX": "Indeks",
+ "GLOSSARY_OPEN": "Register",
+ "GITBOOK_LINK": "Publisert med GitBook",
+ "SUMMARY": "Innholdsfortegnelse",
+ "SUMMARY_INTRODUCTION": "Innledning",
+ "SUMMARY_TOGGLE": "Innholdsfortegnelse",
+ "SEARCH_TOGGLE": "Søk",
+ "SEARCH_PLACEHOLDER": "Skriv inn søkeord",
+ "FONTSETTINGS_TOGGLE": "Tekstinnstillinger",
+ "SHARE_TOGGLE": "Del",
+ "SHARE_ON": "Del på {{platform}}",
+ "FONTSETTINGS_WHITE": "Lys",
+ "FONTSETTINGS_SEPIA": "Sepia",
+ "FONTSETTINGS_NIGHT": "Mørk",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/pl.json b/packages/gitbook-plugin-theme-default/src/i18n/pl.json
new file mode 100644
index 0000000..4f009fc
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/pl.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Wybierz język",
+ "GLOSSARY": "Glosariusz",
+ "GLOSSARY_INDEX": "Indeks",
+ "GLOSSARY_OPEN": "Glosariusz",
+ "GITBOOK_LINK": "Opublikowano dzięki GitBook",
+ "SUMMARY": "Spis treści",
+ "SUMMARY_INTRODUCTION": "Wstęp",
+ "SUMMARY_TOGGLE": "Spis treści",
+ "SEARCH_TOGGLE": "Szukaj",
+ "SEARCH_PLACEHOLDER": "Wpisz szukaną frazę",
+ "FONTSETTINGS_TOGGLE": "Ustawienia czcionki",
+ "SHARE_TOGGLE": "Udostępnij",
+ "SHARE_ON": "Udostępnij na {{platform}}",
+ "FONTSETTINGS_WHITE": "Jasny",
+ "FONTSETTINGS_SEPIA": "Sepia",
+ "FONTSETTINGS_NIGHT": "Noc",
+ "FONTSETTINGS_SANS": "Bezszeryfowa",
+ "FONTSETTINGS_SERIF": "Szeryfowa"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/pt.json b/packages/gitbook-plugin-theme-default/src/i18n/pt.json
new file mode 100644
index 0000000..9d6bde0
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/pt.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Escolher sua língua",
+ "GLOSSARY": "Glossário",
+ "GLOSSARY_INDEX": "Índice",
+ "GLOSSARY_OPEN": "Glossário",
+ "GITBOOK_LINK": "Publicado com GitBook",
+ "SUMMARY": "Tabela de conteúdos",
+ "SUMMARY_INTRODUCTION": "Introdução",
+ "SUMMARY_TOGGLE": "Tabela de conteúdos",
+ "SEARCH_TOGGLE": "Pesquise",
+ "SEARCH_PLACEHOLDER": "Escreva para pesquisar",
+ "FONTSETTINGS_TOGGLE": "Configurações de fonte",
+ "SHARE_TOGGLE": "Compartilhar",
+ "SHARE_ON": "Compartilhar no {{platform}}",
+ "FONTSETTINGS_WHITE": "Claro",
+ "FONTSETTINGS_SEPIA": "Sépia",
+ "FONTSETTINGS_NIGHT": "Noite",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/ro.json b/packages/gitbook-plugin-theme-default/src/i18n/ro.json
new file mode 100644
index 0000000..24295a4
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/ro.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Alege o limba",
+ "GLOSSARY": "Glosar",
+ "GLOSSARY_INDEX": "Index",
+ "GLOSSARY_OPEN": "Glosar",
+ "GITBOOK_LINK": "Publicata cu GitBook",
+ "SUMMARY": "Cuprins",
+ "SUMMARY_INTRODUCTION": "Introducere",
+ "SUMMARY_TOGGLE": "Cuprins",
+ "SEARCH_TOGGLE": "Cauta",
+ "SEARCH_PLACEHOLDER": "Ce cauti",
+ "FONTSETTINGS_TOGGLE": "Setari de font",
+ "SHARE_TOGGLE": "Distribuie",
+ "SHARE_ON": "Distribuie pe {{platform}}",
+ "FONTSETTINGS_WHITE": "Alb",
+ "FONTSETTINGS_SEPIA": "Sepia",
+ "FONTSETTINGS_NIGHT": "Noapte",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/ru.json b/packages/gitbook-plugin-theme-default/src/i18n/ru.json
new file mode 100644
index 0000000..9e6b9dd
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/ru.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Выберите язык",
+ "GLOSSARY": "Алфавитный указатель",
+ "GLOSSARY_INDEX": "Алфавитный указатель",
+ "GLOSSARY_OPEN": "Алфавитный указатель",
+ "GITBOOK_LINK": "Опубликовано с помощью GitBook",
+ "SUMMARY": "Содержание",
+ "SUMMARY_INTRODUCTION": "Введение",
+ "SUMMARY_TOGGLE": "Содержание",
+ "SEARCH_TOGGLE": "Поиск",
+ "SEARCH_PLACEHOLDER": "Введите условия поиска",
+ "FONTSETTINGS_TOGGLE": "Шрифт",
+ "SHARE_TOGGLE": "Поделиться",
+ "SHARE_ON": "Поделиться в {{platform}}",
+ "FONTSETTINGS_WHITE": "Светлый",
+ "FONTSETTINGS_SEPIA": "Сепия",
+ "FONTSETTINGS_NIGHT": "Тёмный",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+} \ No newline at end of file
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/sv.json b/packages/gitbook-plugin-theme-default/src/i18n/sv.json
new file mode 100644
index 0000000..2e2f6ac
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/sv.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Välj språk",
+ "GLOSSARY": "Gloslista",
+ "GLOSSARY_INDEX": "Index",
+ "GLOSSARY_OPEN": "Gloslista",
+ "GITBOOK_LINK": "Publicera med GitBook",
+ "SUMMARY": "Innehållsförteckning",
+ "SUMMARY_INTRODUCTION": "Inledning",
+ "SUMMARY_TOGGLE": "Innehållsförteckning",
+ "SEARCH_TOGGLE": "Sök",
+ "SEARCH_PLACEHOLDER": "Skriv sökord",
+ "FONTSETTINGS_TOGGLE": "Textinställningar",
+ "SHARE_TOGGLE": "Dela",
+ "SHARE_ON": "Dela på {{platform}}",
+ "FONTSETTINGS_WHITE": "Ljus",
+ "FONTSETTINGS_SEPIA": "Sepia",
+ "FONTSETTINGS_NIGHT": "Mörk",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/tr.json b/packages/gitbook-plugin-theme-default/src/i18n/tr.json
new file mode 100644
index 0000000..d92d5a2
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/tr.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Dil seçimi",
+ "GLOSSARY": "Sözlük",
+ "GLOSSARY_INDEX": "Dizin",
+ "GLOSSARY_OPEN": "Sözlük",
+ "GITBOOK_LINK": "GitBook ile yayınla",
+ "SUMMARY": "İçindekiler",
+ "SUMMARY_INTRODUCTION": "Giriş",
+ "SUMMARY_TOGGLE": "İçindekiler",
+ "SEARCH_TOGGLE": "Arama",
+ "SEARCH_PLACEHOLDER": "Aramak istediğiniz",
+ "FONTSETTINGS_TOGGLE": "Font Ayarları",
+ "SHARE_TOGGLE": "Paylaş",
+ "SHARE_ON": "{{platform}} ile paylaş",
+ "FONTSETTINGS_WHITE": "Beyaz",
+ "FONTSETTINGS_SEPIA": "Sepya",
+ "FONTSETTINGS_NIGHT": "Karanlık",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/uk.json b/packages/gitbook-plugin-theme-default/src/i18n/uk.json
new file mode 100644
index 0000000..a582d6c
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/uk.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Виберіть мову",
+ "GLOSSARY": "Алфавітний покажчик",
+ "GLOSSARY_INDEX": "Алфавітний покажчик",
+ "GLOSSARY_OPEN": "Алфавітний покажчик",
+ "GITBOOK_LINK": "Опубліковано за допомогою GitBook",
+ "SUMMARY": "Зміст",
+ "SUMMARY_INTRODUCTION": "Вступ",
+ "SUMMARY_TOGGLE": "Зміст",
+ "SEARCH_TOGGLE": "Пошук",
+ "SEARCH_PLACEHOLDER": "Введіть для пошуку",
+ "FONTSETTINGS_TOGGLE": "Шрифт",
+ "SHARE_TOGGLE": "Поділитися",
+ "SHARE_ON": "Поділитися в {{platform}}",
+ "FONTSETTINGS_WHITE": "Світлий",
+ "FONTSETTINGS_SEPIA": "Сепія",
+ "FONTSETTINGS_NIGHT": "Темний",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+} \ No newline at end of file
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/vi.json b/packages/gitbook-plugin-theme-default/src/i18n/vi.json
new file mode 100644
index 0000000..0addb8e
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/vi.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "Lựa chọn ngôn ngữ",
+ "GLOSSARY": "Chú giải",
+ "GLOSSARY_INDEX": "Chỉ mục",
+ "GLOSSARY_OPEN": "Chú giải",
+ "GITBOOK_LINK": "Xuất bản với GitBook",
+ "SUMMARY": "Mục Lục",
+ "SUMMARY_INTRODUCTION": "Giới thiệu",
+ "SUMMARY_TOGGLE": "Mục Lục",
+ "SEARCH_TOGGLE": "Tìm kiếm",
+ "SEARCH_PLACEHOLDER": "Nhập thông tin cần tìm",
+ "FONTSETTINGS_TOGGLE": "Tùy chỉnh phông chữ",
+ "SHARE_TOGGLE": "Chia sẻ",
+ "SHARE_ON": "Chia sẻ trên {{platform}}",
+ "FONTSETTINGS_WHITE": "Sáng",
+ "FONTSETTINGS_SEPIA": "Vàng nâu",
+ "FONTSETTINGS_NIGHT": "Tối",
+ "FONTSETTINGS_SANS": "Sans",
+ "FONTSETTINGS_SERIF": "Serif"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/zh-hans.json b/packages/gitbook-plugin-theme-default/src/i18n/zh-hans.json
new file mode 100644
index 0000000..8aa372c
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/zh-hans.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "选择一种语言",
+ "GLOSSARY": "术语表",
+ "GLOSSARY_INDEX": "索引",
+ "GLOSSARY_OPEN": "术语表",
+ "GITBOOK_LINK": "本书使用 GitBook 发布",
+ "SUMMARY": "目录",
+ "SUMMARY_INTRODUCTION": "介绍",
+ "SUMMARY_TOGGLE": "目录",
+ "SEARCH_TOGGLE": "搜索",
+ "SEARCH_PLACEHOLDER": "输入并搜索",
+ "FONTSETTINGS_TOGGLE": "字体设置",
+ "SHARE_TOGGLE": "分享",
+ "SHARE_ON": "分享到 {{platform}}",
+ "FONTSETTINGS_WHITE": "白色",
+ "FONTSETTINGS_SEPIA": "棕褐色",
+ "FONTSETTINGS_NIGHT": "夜间",
+ "FONTSETTINGS_SANS": "无衬线体",
+ "FONTSETTINGS_SERIF": "衬线体"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/i18n/zh-tw.json b/packages/gitbook-plugin-theme-default/src/i18n/zh-tw.json
new file mode 100644
index 0000000..d5ff1ad
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/i18n/zh-tw.json
@@ -0,0 +1,20 @@
+{
+ "LANGS_CHOOSE": "選擇一種語言",
+ "GLOSSARY": "術語表",
+ "GLOSSARY_INDEX": "索引",
+ "GLOSSARY_OPEN": "術語表",
+ "GITBOOK_LINK": "本書使用 GitBook 釋出",
+ "SUMMARY": "目錄",
+ "SUMMARY_INTRODUCTION": "介紹",
+ "SUMMARY_TOGGLE": "目錄",
+ "SEARCH_TOGGLE": "搜尋",
+ "SEARCH_PLACEHOLDER": "輸入並搜尋",
+ "FONTSETTINGS_TOGGLE": "字型設定",
+ "SHARE_TOGGLE": "分享",
+ "SHARE_ON": "分享到 {{platform}}",
+ "FONTSETTINGS_WHITE": "白色",
+ "FONTSETTINGS_SEPIA": "棕褐色",
+ "FONTSETTINGS_NIGHT": "夜間",
+ "FONTSETTINGS_SANS": "無襯線體",
+ "FONTSETTINGS_SERIF": "襯線體"
+}
diff --git a/packages/gitbook-plugin-theme-default/src/index.js b/packages/gitbook-plugin-theme-default/src/index.js
new file mode 100644
index 0000000..ad96175
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/index.js
@@ -0,0 +1,14 @@
+const GitBook = require('gitbook-core');
+
+const Theme = require('./components/Theme');
+const reduceState = require('./reducers');
+const locales = require('./i18n');
+
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, state, { Components, I18n }) => {
+ dispatch(Components.registerComponent(Theme, { role: 'website:body' }));
+ dispatch(I18n.registerLocales(locales));
+ },
+ reduce: reduceState
+});
diff --git a/packages/gitbook-plugin-theme-default/src/reducers/index.js b/packages/gitbook-plugin-theme-default/src/reducers/index.js
new file mode 100644
index 0000000..ac53d3a
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/reducers/index.js
@@ -0,0 +1,5 @@
+const GitBook = require('gitbook-core');
+
+module.exports = GitBook.composeReducer(
+ GitBook.createReducer('sidebar', require('./sidebar'))
+);
diff --git a/packages/gitbook-plugin-theme-default/src/reducers/sidebar.js b/packages/gitbook-plugin-theme-default/src/reducers/sidebar.js
new file mode 100644
index 0000000..eef68d4
--- /dev/null
+++ b/packages/gitbook-plugin-theme-default/src/reducers/sidebar.js
@@ -0,0 +1,18 @@
+const GitBook = require('gitbook-core');
+const { Record } = GitBook.Immutable;
+const ActionTypes = require('../actions/types');
+
+const SidebarState = Record({
+ open: true
+});
+
+function reduceSidebar(state = SidebarState(), action) {
+ switch (action.type) {
+ case ActionTypes.TOGGLE_SIDEBAR:
+ return state.set('open', !state.get('open'));
+ default:
+ return state;
+ }
+}
+
+module.exports = reduceSidebar;
diff --git a/packages/gitbook-plugin/CONTRIBUTING.md b/packages/gitbook-plugin/CONTRIBUTING.md
new file mode 100644
index 0000000..19119b6
--- /dev/null
+++ b/packages/gitbook-plugin/CONTRIBUTING.md
@@ -0,0 +1,11 @@
+Compile the CLI using:
+
+```
+npm run dist
+```
+
+Then run the CLI in `lib/`:
+
+```
+./lib/cli.js
+```
diff --git a/packages/gitbook-plugin/README.md b/packages/gitbook-plugin/README.md
new file mode 100644
index 0000000..b2dab88
--- /dev/null
+++ b/packages/gitbook-plugin/README.md
@@ -0,0 +1 @@
+For instructions on how to create plugins, see [GitBook: Create a plugin](toolchain.gitbook.com/api/).
diff --git a/packages/gitbook-plugin/package.json b/packages/gitbook-plugin/package.json
new file mode 100644
index 0000000..9cdc2f8
--- /dev/null
+++ b/packages/gitbook-plugin/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "gitbook-plugin",
+ "version": "4.0.0",
+ "description": "CLI for compiling and testing plugins",
+ "main": "./lib/index.js",
+ "dependencies": {
+ "babel-preset-es2015": "^6.14.0",
+ "babel-preset-react": "^6.11.1",
+ "babelify": "^7.3.0",
+ "browserify": "^13.1.0",
+ "commander": "^2.9.0",
+ "fs-extra": "^0.30.0",
+ "inquirer": "^1.1.3",
+ "q": "^1.4.1",
+ "winston": "^2.2.0"
+ },
+ "devDependencies": {
+ "babel-cli": "^6.14.0",
+ "babel-preset-es2015": "^6.14.0",
+ "babel-preset-react": "^6.11.1",
+ "babel-preset-stage-2": "^6.13.0"
+ },
+ "bin": {
+ "gitbook-plugin": "./lib/cli.js"
+ },
+ "scripts": {
+ "dist": "rm -rf lib/ && babel -d lib/ src/ && chmod +x ./lib/cli.js",
+ "prepublish": "npm run dist"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "author": "GitBook Inc. <contact@gitbook.com>",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ }
+}
diff --git a/packages/gitbook-plugin/src/cli.js b/packages/gitbook-plugin/src/cli.js
new file mode 100644
index 0000000..06e421d
--- /dev/null
+++ b/packages/gitbook-plugin/src/cli.js
@@ -0,0 +1,84 @@
+#! /usr/bin/env node
+
+const program = require('commander');
+const path = require('path');
+const winston = require('winston');
+const inquirer = require('inquirer');
+
+const pkg = require('../package.json');
+const compile = require('./compile');
+const create = require('./create');
+
+const resolve = (input => path.resolve(process.cwd(), input));
+
+program.version(pkg.version);
+winston.cli();
+
+program
+ .command('build [input] [output]')
+ .description('build a browser plugin')
+ .action(function(input, output, options) {
+ compile(resolve(input), resolve(output))
+ .then(
+ () => winston.info('Plugin compiled successfully'),
+ (err) => winston.error('Error: ', err)
+ );
+ });
+
+program
+ .command('create [output]')
+ .description('create a new plugin')
+ .action(function(output, options) {
+ inquirer.prompt([
+ {
+ name: 'title',
+ message: 'Title (as displayed on GitBook.com):'
+ },
+ {
+ name: 'name',
+ message: 'Name (unique identifier for the plugin):'
+ },
+ {
+ name: 'desc',
+ message: 'Description:'
+ },
+ {
+ name: 'github',
+ message: 'GitHub repository URL:'
+ },
+ {
+ name: 'categories',
+ message: 'Categories (as displayed on GitBook.com):',
+ type: 'checkbox',
+ choices: [
+ 'analytics',
+ 'search',
+ 'content',
+ 'structure',
+ 'social',
+ 'visual'
+ ]
+ }
+ ])
+ .then(answers => {
+ output = resolve(output || answers.name);
+ return create(output, answers);
+ })
+ .then(
+ () => winston.info(`Plugin created successfully in "${output}"`),
+ (err) => winston.error('Error: ', err)
+ );
+ });
+
+program
+ .command('test [plugin]')
+ .description('test specs for a plugin')
+ .action(function(plugin, options) {
+
+ });
+
+
+program.parse(process.argv);
+
+// Display help if no arguments
+if (!program.args.length) program.help();
diff --git a/packages/gitbook-plugin/src/compile.js b/packages/gitbook-plugin/src/compile.js
new file mode 100644
index 0000000..61c8777
--- /dev/null
+++ b/packages/gitbook-plugin/src/compile.js
@@ -0,0 +1,41 @@
+const fs = require('fs-extra');
+const Promise = require('q');
+const browserify = require('browserify');
+const babelify = require('babelify');
+
+/**
+ * Compile a plugin to work with "gitbook-core" in the browser.
+ * @param {String} inputFile
+ * @param {String} outputFile
+ * @return {Promise}
+ */
+function compilePlugin(inputFile, outputFile) {
+ const d = Promise.defer();
+ const b = browserify({
+ standalone: 'GitBookPlugin'
+ });
+
+ b.add(inputFile);
+ b.external('react');
+ b.external('react-dom');
+ b.external('gitbook-core');
+ b.transform(babelify, {
+ presets: [
+ require('babel-preset-es2015'),
+ require('babel-preset-react')
+ ]
+ });
+
+ fs.ensureFileSync(outputFile);
+
+ const output = fs.createWriteStream(outputFile);
+
+ b.bundle()
+ .pipe(output)
+ .on('error', (err) => d.reject(err))
+ .on('end', () => d.resolve());
+
+ return d.promise;
+}
+
+module.exports = compilePlugin;
diff --git a/packages/gitbook-plugin/src/create.js b/packages/gitbook-plugin/src/create.js
new file mode 100644
index 0000000..31edb85
--- /dev/null
+++ b/packages/gitbook-plugin/src/create.js
@@ -0,0 +1,61 @@
+const fs = require('fs-extra');
+const path = require('path');
+const GITBOOK_VERSION = require('../package.json').version;
+
+const TEMPLATE_DIR = path.resolve(__dirname, '../template');
+
+/**
+ * Create a new plugin
+ * @param {String} outputDir
+ * @param {String} spec.title
+ * @param {String} spec.name
+ * @param {String} spec.desc
+ * @param {Array} spec.keywords
+ */
+function create(outputDir, spec) {
+ const pkg = {
+ 'title': `${spec.title}`,
+ 'name': `gitbook-plugin-${spec.name}`,
+ 'description': `${spec.desc}`,
+ 'version': '0.0.0',
+ 'main': 'index.js',
+ 'browser': './_assets/plugin.js',
+ 'ebook': './_assets/plugin.js',
+ 'dependencies': {
+ 'gitbook-core': '^' + GITBOOK_VERSION
+ },
+ 'devDependencies': {
+ 'gitbook-plugin': '^' + GITBOOK_VERSION,
+ 'eslint': '3.7.1',
+ 'eslint-config-gitbook': '1.4.0'
+ },
+ 'engines': {
+ 'gitbook': '>=4.0.0-alpha.0'
+ },
+ 'scripts': {
+ 'lint': 'eslint ./',
+ 'build-website': 'gitbook-plugin build ./src/index.js ./_assets/plugin.js',
+ 'prepublish': 'npm run build-website',
+ 'test': 'gitbook-plugin test && npm run lint'
+ },
+ 'homepage': `${spec.github}`,
+ 'keywords': spec.categories.map(category => `gitbook:${category}`),
+ 'repository': {
+ 'type': 'git',
+ 'url': `${spec.github}.git`
+ },
+ 'bugs': {
+ 'url': `${spec.github}/issues`
+ }
+ };
+
+ fs.copySync(TEMPLATE_DIR, outputDir, {
+ clobber: true
+ });
+
+ fs.outputJsonSync(path.resolve(outputDir, 'package.json'), pkg, {
+ spaces: 2
+ });
+}
+
+module.exports = create;
diff --git a/lib/modifiers/summary/__tests__/editArticle.js b/packages/gitbook-plugin/src/index.js
index e69de29..e69de29 100644
--- a/lib/modifiers/summary/__tests__/editArticle.js
+++ b/packages/gitbook-plugin/src/index.js
diff --git a/packages/gitbook-plugin/template/.eslintignore b/packages/gitbook-plugin/template/.eslintignore
new file mode 100644
index 0000000..1d35cda
--- /dev/null
+++ b/packages/gitbook-plugin/template/.eslintignore
@@ -0,0 +1,2 @@
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin/template/.eslintrc b/packages/gitbook-plugin/template/.eslintrc
new file mode 100644
index 0000000..90359b2
--- /dev/null
+++ b/packages/gitbook-plugin/template/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": "gitbook/plugin"
+}
diff --git a/packages/gitbook-plugin/template/.gitignore b/packages/gitbook-plugin/template/.gitignore
new file mode 100644
index 0000000..ef47881
--- /dev/null
+++ b/packages/gitbook-plugin/template/.gitignore
@@ -0,0 +1,31 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# 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
+
+# vim swapfile
+*.swp
+
+# Plugin assets
+_assets/plugin.js
diff --git a/packages/gitbook-plugin/template/.npmignore b/packages/gitbook-plugin/template/.npmignore
new file mode 100644
index 0000000..a0e53cf
--- /dev/null
+++ b/packages/gitbook-plugin/template/.npmignore
@@ -0,0 +1,2 @@
+# Publish assets on NPM
+!_assets/plugin.js
diff --git a/packages/gitbook-plugin/template/index.js b/packages/gitbook-plugin/template/index.js
new file mode 100644
index 0000000..e542ae8
--- /dev/null
+++ b/packages/gitbook-plugin/template/index.js
@@ -0,0 +1,10 @@
+
+module.exports = {
+ blocks: {
+
+ },
+
+ hooks: {
+
+ }
+};
diff --git a/packages/gitbook-plugin/template/src/index.js b/packages/gitbook-plugin/template/src/index.js
new file mode 100644
index 0000000..0fe8869
--- /dev/null
+++ b/packages/gitbook-plugin/template/src/index.js
@@ -0,0 +1,11 @@
+const GitBook = require('gitbook-core');
+
+module.exports = GitBook.createPlugin({
+ activate: (dispatch, getState) => {
+ // Dispatch initialization actions
+ },
+ deactivate: (dispatch, getState) => {
+ // Dispatch cleanup actions
+ },
+ reduce: (state, action) => state
+});
diff --git a/packages/gitbook/.babelrc b/packages/gitbook/.babelrc
new file mode 100644
index 0000000..5f27bda
--- /dev/null
+++ b/packages/gitbook/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015", "react", "stage-2"]
+}
diff --git a/packages/gitbook/.gitignore b/packages/gitbook/.gitignore
new file mode 100644
index 0000000..a65b417
--- /dev/null
+++ b/packages/gitbook/.gitignore
@@ -0,0 +1 @@
+lib
diff --git a/packages/gitbook/.npmignore b/packages/gitbook/.npmignore
new file mode 100644
index 0000000..e04684f
--- /dev/null
+++ b/packages/gitbook/.npmignore
@@ -0,0 +1,2 @@
+src
+!lib
diff --git a/bin/gitbook.js b/packages/gitbook/bin/gitbook.js
index 5cadbc9..0492d29 100755
--- a/bin/gitbook.js
+++ b/packages/gitbook/bin/gitbook.js
@@ -1,5 +1,5 @@
#! /usr/bin/env node
-/* eslint-disable no-console */
+/* eslint-disable no-console,no-var */
var color = require('bash-color');
diff --git a/packages/gitbook/package.json b/packages/gitbook/package.json
new file mode 100644
index 0000000..0e85179
--- /dev/null
+++ b/packages/gitbook/package.json
@@ -0,0 +1,107 @@
+{
+ "name": "gitbook",
+ "version": "4.0.0",
+ "homepage": "https://www.gitbook.com",
+ "description": "Library and cmd utility to generate GitBooks",
+ "main": "lib/index.js",
+ "browser": "./lib/browser.js",
+ "dependencies": {
+ "bash-color": "0.0.4",
+ "cheerio": "0.20.0",
+ "chokidar": "1.5.0",
+ "cp": "0.2.0",
+ "cpr": "1.1.1",
+ "crc": "3.4.0",
+ "destroy": "1.0.4",
+ "direction": "0.1.5",
+ "dom-serializer": "0.1.0",
+ "error": "7.0.2",
+ "escape-html": "^1.0.3",
+ "escape-string-regexp": "1.0.5",
+ "extend": "^3.0.0",
+ "fresh-require": "1.0.3",
+ "front-matter": "^2.1.0",
+ "gitbook-asciidoc": "1.2.2",
+ "gitbook-core": "4.0.0",
+ "gitbook-markdown": "1.3.2",
+ "gitbook-plugin-copy-code": "4.0.0",
+ "gitbook-plugin-headings": "4.0.0",
+ "gitbook-plugin-highlight": "4.0.0",
+ "gitbook-plugin-hints": "4.0.0",
+ "gitbook-plugin-livereload": "4.0.0",
+ "gitbook-plugin-lunr": "4.0.0",
+ "gitbook-plugin-search": "4.0.0",
+ "gitbook-plugin-sharing": "4.0.0",
+ "gitbook-plugin-theme-default": "4.0.0",
+ "github-slugid": "1.0.1",
+ "graceful-fs": "4.1.4",
+ "i18n-t": "1.0.1",
+ "ied": "2.3.6",
+ "ignore": "3.1.2",
+ "immutable": "^3.8.1",
+ "is": "^3.1.0",
+ "js-yaml": "^3.6.1",
+ "json-schema-defaults": "0.1.1",
+ "jsonschema": "1.1.0",
+ "juice": "2.0.0",
+ "mkdirp": "0.5.1",
+ "moment": "2.13.0",
+ "npm": "3.10.9",
+ "nunjucks": "2.5.2",
+ "object-path": "^0.9.2",
+ "omit-keys": "^0.1.0",
+ "open": "0.0.5",
+ "q": "1.4.1",
+ "read-installed": "^4.0.3",
+ "redux": "^3.5.2",
+ "request": "2.72.0",
+ "resolve": "1.1.7",
+ "rmdir": "1.2.0",
+ "semver": "5.1.0",
+ "send": "0.13.2",
+ "spawn-cmd": "0.0.2",
+ "tiny-lr": "0.2.1",
+ "tmp": "0.0.28",
+ "urijs": "1.18.0"
+ },
+ "scripts": {
+ "test": "./node_modules/.bin/mocha ./testing/setup.js \"./src/**/*/__tests__/*.js\" --bail --reporter=list --timeout=100000 --compilers js:babel-register",
+ "dist": "rm -rf lib/ && babel -d lib/ src/ --source-maps --ignore \"**/*/__tests__/*.js\"",
+ "prepublish": "npm run dist"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/GitbookIO/gitbook.git"
+ },
+ "bin": {
+ "gitbook": "./bin/gitbook.js"
+ },
+ "keywords": [
+ "git",
+ "book",
+ "gitbook"
+ ],
+ "author": "GitBook Inc. <contact@gitbook.com>",
+ "license": "Apache-2.0",
+ "bugs": {
+ "url": "https://github.com/GitbookIO/gitbook/issues"
+ },
+ "contributors": [
+ {
+ "name": "Aaron O'Mullan",
+ "email": "aaron@gitbook.com"
+ },
+ {
+ "name": "Samy Pessé",
+ "email": "samy@gitbook.com"
+ }
+ ],
+ "devDependencies": {
+ "babel-cli": "^6.14.0",
+ "babel-preset-es2015": "^6.14.0",
+ "babel-preset-react": "^6.11.1",
+ "babel-preset-stage-2": "^6.13.0",
+ "babel-register": "^6.14.0",
+ "mocha": "^3.0.2"
+ }
+}
diff --git a/lib/__tests__/gitbook.js b/packages/gitbook/src/__tests__/gitbook.js
index c3669bb..5292e01 100644
--- a/lib/__tests__/gitbook.js
+++ b/packages/gitbook/src/__tests__/gitbook.js
@@ -1,4 +1,4 @@
-var gitbook = require('../gitbook');
+const gitbook = require('../gitbook');
describe('satisfies', function() {
diff --git a/lib/__tests__/init.js b/packages/gitbook/src/__tests__/init.js
index 66188a3..d8e5398 100644
--- a/lib/__tests__/init.js
+++ b/packages/gitbook/src/__tests__/init.js
@@ -1,10 +1,10 @@
-var tmp = require('tmp');
-var initBook = require('../init');
+const tmp = require('tmp');
+const initBook = require('../init');
describe('initBook', function() {
it('should create a README and SUMMARY for empty book', function() {
- var dir = tmp.dirSync();
+ const dir = tmp.dirSync();
return initBook(dir.name)
.then(function() {
diff --git a/lib/__tests__/module.js b/packages/gitbook/src/__tests__/module.js
index d9220f5..d9220f5 100644
--- a/lib/__tests__/module.js
+++ b/packages/gitbook/src/__tests__/module.js
diff --git a/lib/api/decodeConfig.js b/packages/gitbook/src/api/decodeConfig.js
index 5e00df5..0c5ba66 100644
--- a/lib/api/decodeConfig.js
+++ b/packages/gitbook/src/api/decodeConfig.js
@@ -6,7 +6,7 @@
@return {Config}
*/
function decodeGlobal(config, result) {
- var values = result.values;
+ const values = result.values;
delete values.generator;
delete values.output;
diff --git a/packages/gitbook/src/api/decodeGlobal.js b/packages/gitbook/src/api/decodeGlobal.js
new file mode 100644
index 0000000..c7bbcc7
--- /dev/null
+++ b/packages/gitbook/src/api/decodeGlobal.js
@@ -0,0 +1,22 @@
+const decodeConfig = require('./decodeConfig');
+
+/**
+ * Decode changes from a JS API to a output object.
+ * Only the configuration can be edited by plugin's hooks
+ *
+ * @param {Output} output
+ * @param {Object} result: result from API
+ * @return {Output} output
+ */
+function decodeGlobal(output, result) {
+ let book = output.getBook();
+ let config = book.getConfig();
+
+ // Update config
+ config = decodeConfig(config, result.config);
+ book = book.set('config', config);
+
+ return output.set('book', book);
+}
+
+module.exports = decodeGlobal;
diff --git a/packages/gitbook/src/api/decodePage.js b/packages/gitbook/src/api/decodePage.js
new file mode 100644
index 0000000..16e5115
--- /dev/null
+++ b/packages/gitbook/src/api/decodePage.js
@@ -0,0 +1,34 @@
+const Immutable = require('immutable');
+
+/**
+ * Decode changes from a JS API to a page object.
+ * Only the content can be edited by plugin's hooks.
+ *
+ * @param {Output} output
+ * @param {Page} page: page instance to edit
+ * @param {Object} result: result from API
+ * @return {Page}
+ */
+function decodePage(output, page, result) {
+ const originalContent = page.getContent();
+
+ // No returned value
+ // Existing content will be used
+ if (!result) {
+ return page;
+ }
+
+ // Update page attributes
+ const newAttributes = Immutable.fromJS(result.attributes);
+ page = page.set('attributes', newAttributes);
+
+ // GitBook 3
+ // Use returned page.content if different from original content
+ if (result.content != originalContent) {
+ page = page.set('content', result.content);
+ }
+
+ return page;
+}
+
+module.exports = decodePage;
diff --git a/packages/gitbook/src/api/deprecate.js b/packages/gitbook/src/api/deprecate.js
new file mode 100644
index 0000000..c781971
--- /dev/null
+++ b/packages/gitbook/src/api/deprecate.js
@@ -0,0 +1,120 @@
+const is = require('is');
+const objectPath = require('object-path');
+
+const logged = {};
+const disabled = {};
+
+/**
+ * Log a deprecated notice
+ *
+ * @param {Book|Output} book
+ * @param {String} key
+ * @param {String} message
+ */
+function logNotice(book, key, message) {
+ if (logged[key] || disabled[key]) return;
+
+ logged[key] = true;
+
+ const logger = book.getLogger();
+ logger.warn.ln(message);
+}
+
+/**
+ * Deprecate a function
+ *
+ * @param {Book|Output} book
+ * @param {String} key: unique identitifer for the deprecated
+ * @param {Function} fn
+ * @param {String} msg: message to print when called
+ * @return {Function}
+ */
+function deprecateMethod(book, key, fn, msg) {
+ return function(...args) {
+ logNotice(book, key, msg);
+ return fn.apply(this, args);
+ };
+}
+
+/**
+ * Deprecate a property of an object
+ *
+ * @param {Book|Output} book
+ * @param {String} key: unique identitifer for the deprecated
+ * @param {Object} instance
+ * @param {String|Function} property
+ * @param {String} msg: message to print when called
+ * @return {Function}
+ */
+function deprecateField(book, key, instance, property, value, msg) {
+ let store = undefined;
+
+ const prepare = () => {
+ if (!is.undefined(store)) return;
+
+ if (is.fn(value)) store = value();
+ else store = value;
+ };
+
+ const getter = () => {
+ prepare();
+
+ logNotice(book, key, msg);
+ return store;
+ };
+
+ const setter = (v) => {
+ prepare();
+
+ logNotice(book, key, msg);
+ store = v;
+ return store;
+ };
+
+ Object.defineProperty(instance, property, {
+ get: getter,
+ set: setter,
+ enumerable: false,
+ configurable: true
+ });
+}
+
+/**
+ * Enable a deprecation
+ * @param {String} key: unique identitifer
+ */
+function enableDeprecation(key) {
+ disabled[key] = false;
+}
+
+/**
+ * Disable a deprecation
+ * @param {String} key: unique identitifer
+ */
+function disableDeprecation(key) {
+ disabled[key] = true;
+}
+
+/**
+ * Deprecate a method in favor of another one.
+ *
+ * @param {Book} book
+ * @param {String} key
+ * @param {Object} instance
+ * @param {String} oldName
+ * @param {String} newName
+ */
+function deprecateRenamedMethod(book, key, instance, oldName, newName, msg) {
+ msg = msg || ('"' + oldName + '" is deprecated, use "' + newName + '()" instead');
+ const fn = objectPath.get(instance, newName);
+
+ instance[oldName] = deprecateMethod(book, key, fn, msg);
+}
+
+module.exports = {
+ method: deprecateMethod,
+ renamedMethod: deprecateRenamedMethod,
+ field: deprecateField,
+ enable: enableDeprecation,
+ disable: disableDeprecation
+};
diff --git a/lib/api/encodeConfig.js b/packages/gitbook/src/api/encodeConfig.js
index 2a05528..cdfc0b7 100644
--- a/lib/api/encodeConfig.js
+++ b/packages/gitbook/src/api/encodeConfig.js
@@ -1,22 +1,22 @@
-var objectPath = require('object-path');
-var deprecate = require('./deprecate');
+const objectPath = require('object-path');
+const deprecate = require('./deprecate');
/**
- Encode a config object into a JS config api
-
- @param {Output} output
- @param {Config} config
- @return {Object}
-*/
+ * Encode a config object into a JS config api
+ *
+ * @param {Output} output
+ * @param {Config} config
+ * @return {Object}
+ */
function encodeConfig(output, config) {
- var result = {
+ const result = {
values: config.getValues().toJS(),
- get: function(key, defaultValue) {
+ get(key, defaultValue) {
return objectPath.get(result.values, key, defaultValue);
},
- set: function(key, value) {
+ set(key, value) {
return objectPath.set(result.values, key, value);
}
};
diff --git a/packages/gitbook/src/api/encodeGlobal.js b/packages/gitbook/src/api/encodeGlobal.js
new file mode 100644
index 0000000..89db629
--- /dev/null
+++ b/packages/gitbook/src/api/encodeGlobal.js
@@ -0,0 +1,264 @@
+const path = require('path');
+const Promise = require('../utils/promise');
+const PathUtils = require('../utils/path');
+const fs = require('../utils/fs');
+
+const Plugins = require('../plugins');
+const deprecate = require('./deprecate');
+const defaultBlocks = require('../constants/defaultBlocks');
+const gitbook = require('../gitbook');
+const parsers = require('../parsers');
+
+const encodeConfig = require('./encodeConfig');
+const encodeSummary = require('./encodeSummary');
+const encodeNavigation = require('./encodeNavigation');
+const encodePage = require('./encodePage');
+
+/**
+ * Encode a global context into a JS object
+ * It's the context for page's hook, etc
+ *
+ * @param {Output} output
+ * @return {Object}
+ */
+function encodeGlobal(output) {
+ const book = output.getBook();
+ const bookFS = book.getContentFS();
+ const logger = output.getLogger();
+ const outputFolder = output.getRoot();
+ const plugins = output.getPlugins();
+ const blocks = Plugins.listBlocks(plugins);
+ const urls = output.getURLIndex();
+
+ const result = {
+ log: logger,
+ config: encodeConfig(output, book.getConfig()),
+ summary: encodeSummary(output, book.getSummary()),
+
+ /**
+ * Return absolute path to the root folder of the book
+ * @return {String}
+ */
+ root() {
+ return book.getRoot();
+ },
+
+ /**
+ * Return absolute path to the root folder of the book (for content)
+ * @return {String}
+ */
+ contentRoot() {
+ return book.getContentRoot();
+ },
+
+ /**
+ * Check if the book is a multilingual book.
+ * @return {Boolean}
+ */
+ isMultilingual() {
+ return book.isMultilingual();
+ },
+
+ /**
+ * Check if the book is a language book for a multilingual book.
+ * @return {Boolean}
+ */
+ isLanguageBook() {
+ return book.isLanguageBook();
+ },
+
+ /**
+ * Read a file from the book.
+ * @param {String} fileName
+ * @return {Promise<Buffer>}
+ */
+ readFile(fileName) {
+ return bookFS.read(fileName);
+ },
+
+ /**
+ * Read a file from the book as a string.
+ * @param {String} fileName
+ * @return {Promise<String>}
+ */
+ readFileAsString(fileName) {
+ return bookFS.readAsString(fileName);
+ },
+
+ /**
+ * Resolve a file from the book root.
+ * @param {String} fileName
+ * @return {String}
+ */
+ resolve(fileName) {
+ return path.resolve(book.getContentRoot(), fileName);
+ },
+
+ /**
+ * Resolve a page by it path.
+ * @param {String} filePath
+ * @return {String}
+ */
+ getPageByPath(filePath) {
+ const page = output.getPage(filePath);
+ if (!page) return undefined;
+
+ return encodePage(output, page);
+ },
+
+ /**
+ * Render a block of text (markdown/asciidoc).
+ * @param {String} type
+ * @param {String} text
+ * @return {Promise<String>}
+ */
+ renderBlock(type, text) {
+ const parser = parsers.get(type);
+
+ return parser.parsePage(text)
+ .get('content');
+ },
+
+ /**
+ * Render an inline text (markdown/asciidoc).
+ * @param {String} type
+ * @param {String} text
+ * @return {Promise<String>}
+ */
+ renderInline(type, text) {
+ const parser = parsers.get(type);
+
+ return parser.parseInline(text)
+ .get('content');
+ },
+
+ template: {
+
+ /**
+ * Apply a templating block and returns its result.
+ * @param {String} name
+ * @param {Object} blockData
+ * @return {Promise|Object}
+ */
+ applyBlock(name, blockData) {
+ const block = blocks.get(name) || defaultBlocks.get(name);
+ return Promise(block.applyBlock(blockData, result));
+ }
+ },
+
+ output: {
+
+ /**
+ * Name of the generator being used
+ * {String}
+ */
+ name: output.getGenerator(),
+
+ /**
+ * Return absolute path to the root folder of output
+ * @return {String}
+ */
+ root() {
+ return outputFolder;
+ },
+
+ /**
+ * Resolve a file from the output root.
+ * @param {String} fileName
+ * @return {String}
+ */
+ resolve(fileName) {
+ return path.resolve(outputFolder, fileName);
+ },
+
+ /**
+ * Convert a filepath into an url
+ * @return {String}
+ */
+ toURL(filePath) {
+ return urls.resolveToURL(filePath);
+ },
+
+ /**
+ * Check that a file exists.
+ * @param {String} fileName
+ * @return {Promise}
+ */
+ hasFile(fileName, content) {
+ return Promise()
+ .then(function() {
+ const filePath = PathUtils.resolveInRoot(outputFolder, fileName);
+
+ return fs.exists(filePath);
+ });
+ },
+
+ /**
+ * Write a file to the output folder,
+ * It creates the required folder
+ *
+ * @param {String} fileName
+ * @param {Buffer} content
+ * @return {Promise}
+ */
+ writeFile(fileName, content) {
+ return Promise()
+ .then(function() {
+ const filePath = PathUtils.resolveInRoot(outputFolder, fileName);
+
+ return fs.ensureFile(filePath)
+ .then(function() {
+ return fs.writeFile(filePath, content);
+ });
+ });
+ },
+
+ /**
+ * Copy a file to the output folder
+ * It creates the required folder.
+ *
+ * @param {String} inputFile
+ * @param {String} outputFile
+ * @param {Buffer} content
+ * @return {Promise}
+ */
+ copyFile(inputFile, outputFile, content) {
+ return Promise()
+ .then(function() {
+ const outputFilePath = PathUtils.resolveInRoot(outputFolder, outputFile);
+
+ return fs.ensureFile(outputFilePath)
+ .then(function() {
+ return fs.copy(inputFile, outputFilePath);
+ });
+ });
+ }
+ },
+
+ gitbook: {
+ version: gitbook.version
+ }
+ };
+
+ // Deprecated properties
+
+ deprecate.renamedMethod(output, 'this.isSubBook', result, 'isSubBook', 'isLanguageBook');
+ deprecate.renamedMethod(output, 'this.contentLink', result, 'contentLink', 'output.toURL');
+
+ deprecate.field(output, 'this.generator', result, 'generator',
+ output.getGenerator(), '"this.generator" property is deprecated, use "this.output.name" instead');
+
+ deprecate.field(output, 'this.navigation', result, 'navigation', function() {
+ return encodeNavigation(output);
+ }, '"navigation" property is deprecated');
+
+ deprecate.field(output, 'this.book', result, 'book',
+ result, '"book" property is deprecated, use "this" directly instead');
+
+ deprecate.field(output, 'this.options', result, 'options',
+ result.config.values, '"options" property is deprecated, use config.get(key) instead');
+
+ return result;
+}
+
+module.exports = encodeGlobal;
diff --git a/lib/api/encodeNavigation.js b/packages/gitbook/src/api/encodeNavigation.js
index 8e329a1..95ab8e3 100644
--- a/lib/api/encodeNavigation.js
+++ b/packages/gitbook/src/api/encodeNavigation.js
@@ -1,14 +1,14 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
/**
- Encode an article for next/prev
-
- @param {Map<String:Page>}
- @param {Article}
- @return {Object}
-*/
+ * Encode an article for next/prev
+ *
+ * @param {Map<String:Page>}
+ * @param {Article}
+ * @return {Object}
+ */
function encodeArticle(pages, article) {
- var articlePath = article.getPath();
+ const articlePath = article.getPath();
return {
path: articlePath,
@@ -20,27 +20,27 @@ function encodeArticle(pages, article) {
}
/**
- this.navigation is a deprecated property from GitBook v2
-
- @param {Output}
- @return {Object}
-*/
+ * this.navigation is a deprecated property from GitBook v2
+ *
+ * @param {Output}
+ * @return {Object}
+ */
function encodeNavigation(output) {
- var book = output.getBook();
- var pages = output.getPages();
- var summary = book.getSummary();
- var articles = summary.getArticlesAsList();
+ const book = output.getBook();
+ const pages = output.getPages();
+ const summary = book.getSummary();
+ const articles = summary.getArticlesAsList();
- var navigation = articles
+ const navigation = articles
.map(function(article, i) {
- var ref = article.getRef();
+ const ref = article.getRef();
if (!ref) {
return undefined;
}
- var prev = articles.get(i - 1);
- var next = articles.get(i + 1);
+ const prev = articles.get(i - 1);
+ const next = articles.get(i + 1);
return [
ref,
@@ -48,8 +48,8 @@ function encodeNavigation(output) {
index: i,
title: article.getTitle(),
introduction: (i === 0),
- prev: prev? encodeArticle(pages, prev) : undefined,
- next: next? encodeArticle(pages, next) : undefined,
+ prev: prev ? encodeArticle(pages, prev) : undefined,
+ next: next ? encodeArticle(pages, next) : undefined,
level: article.getLevel()
}
];
diff --git a/lib/api/encodePage.js b/packages/gitbook/src/api/encodePage.js
index 379d3d5..7d563cd 100644
--- a/lib/api/encodePage.js
+++ b/packages/gitbook/src/api/encodePage.js
@@ -1,27 +1,33 @@
-var JSONUtils = require('../json');
-var deprecate = require('./deprecate');
-var encodeProgress = require('./encodeProgress');
+const JSONUtils = require('../json');
+const deprecate = require('./deprecate');
+const encodeProgress = require('./encodeProgress');
/**
- Encode a page in a context to a JS API
-
- @param {Output} output
- @param {Page} page
- @return {Object}
-*/
+ * Encode a page in a context to a JS API
+ *
+ * @param {Output} output
+ * @param {Page} page
+ * @return {Object}
+ */
function encodePage(output, page) {
- var book = output.getBook();
- var summary = book.getSummary();
- var fs = book.getContentFS();
- var file = page.getFile();
+ const book = output.getBook();
+ const urls = output.getURLIndex();
+ const summary = book.getSummary();
+ const fs = book.getContentFS();
+ const file = page.getFile();
// JS Page is based on the JSON output
- var result = JSONUtils.encodePage(page, summary);
+ const result = JSONUtils.encodePage(page, summary, urls);
result.type = file.getType();
result.path = file.getPath();
result.rawPath = fs.resolve(result.path);
+ result.setAttribute = (key, value) => {
+ result.attributes[key] = value;
+ return result;
+ };
+
deprecate.field(output, 'page.progress', result, 'progress', function() {
return encodeProgress(output, page);
}, '"page.progress" property is deprecated');
diff --git a/lib/api/encodeProgress.js b/packages/gitbook/src/api/encodeProgress.js
index afa0341..3224370 100644
--- a/lib/api/encodeProgress.js
+++ b/packages/gitbook/src/api/encodeProgress.js
@@ -1,5 +1,5 @@
-var Immutable = require('immutable');
-var encodeNavigation = require('./encodeNavigation');
+const Immutable = require('immutable');
+const encodeNavigation = require('./encodeNavigation');
/**
page.progress is a deprecated property from GitBook v2
@@ -9,15 +9,15 @@ var encodeNavigation = require('./encodeNavigation');
@return {Object}
*/
function encodeProgress(output, page) {
- var current = page.getPath();
- var navigation = encodeNavigation(output);
+ const current = page.getPath();
+ let navigation = encodeNavigation(output);
navigation = Immutable.Map(navigation);
- var n = navigation.size;
- var percent = 0, prevPercent = 0, currentChapter = null;
- var done = true;
+ const n = navigation.size;
+ let percent = 0, prevPercent = 0, currentChapter = null;
+ let done = true;
- var chapters = navigation
+ const chapters = navigation
.map(function(nav, chapterPath) {
nav.path = chapterPath;
return nav;
@@ -46,13 +46,13 @@ function encodeProgress(output, page) {
return {
// Previous percent
- prevPercent: prevPercent,
+ prevPercent,
// Current percent
- percent: percent,
+ percent,
// List of chapter with progress
- chapters: chapters,
+ chapters,
// Current chapter
current: currentChapter
diff --git a/lib/api/encodeSummary.js b/packages/gitbook/src/api/encodeSummary.js
index 0d66ded..323f5d4 100644
--- a/lib/api/encodeSummary.js
+++ b/packages/gitbook/src/api/encodeSummary.js
@@ -1,4 +1,4 @@
-var encodeSummaryArticle = require('../json/encodeSummaryArticle');
+const encodeSummaryArticle = require('../json/encodeSummaryArticle');
/**
Encode summary to provide an API to plugin
@@ -8,15 +8,16 @@ var encodeSummaryArticle = require('../json/encodeSummaryArticle');
@return {Object}
*/
function encodeSummary(output, summary) {
- var result = {
+ const result = {
+
/**
Iterate over the summary, it stops when the "iter" returns false
@param {Function} iter
*/
- walk: function (iter) {
+ walk(iter) {
summary.getArticle(function(article) {
- var jsonArticle = encodeSummaryArticle(article, false);
+ const jsonArticle = encodeSummaryArticle(article, false);
return iter(jsonArticle);
});
@@ -28,9 +29,9 @@ function encodeSummary(output, summary) {
@param {String} level
@return {Object}
*/
- getArticleByLevel: function(level) {
- var article = summary.getByLevel(level);
- return (article? encodeSummaryArticle(article) : undefined);
+ getArticleByLevel(level) {
+ const article = summary.getByLevel(level);
+ return (article ? encodeSummaryArticle(article) : undefined);
},
/**
@@ -39,9 +40,9 @@ function encodeSummary(output, summary) {
@param {String} level
@return {Object}
*/
- getArticleByPath: function(level) {
- var article = summary.getByPath(level);
- return (article? encodeSummaryArticle(article) : undefined);
+ getArticleByPath(level) {
+ const article = summary.getByPath(level);
+ return (article ? encodeSummaryArticle(article) : undefined);
}
};
diff --git a/packages/gitbook/src/api/index.js b/packages/gitbook/src/api/index.js
new file mode 100644
index 0000000..3956c62
--- /dev/null
+++ b/packages/gitbook/src/api/index.js
@@ -0,0 +1,7 @@
+
+module.exports = {
+ encodePage: require('./encodePage'),
+ decodePage: require('./decodePage'),
+ encodeGlobal: require('./encodeGlobal'),
+ decodeGlobal: require('./decodeGlobal')
+};
diff --git a/packages/gitbook/src/browser.js b/packages/gitbook/src/browser.js
new file mode 100644
index 0000000..1e7fad2
--- /dev/null
+++ b/packages/gitbook/src/browser.js
@@ -0,0 +1,23 @@
+const Modifiers = require('./modifiers');
+
+module.exports = {
+ Parse: require('./parse'),
+ // Models
+ Book: require('./models/book'),
+ FS: require('./models/fs'),
+ File: require('./models/file'),
+ Summary: require('./models/summary'),
+ Glossary: require('./models/glossary'),
+ Config: require('./models/config'),
+ Page: require('./models/page'),
+ PluginDependency: require('./models/pluginDependency'),
+ // Modifiers
+ SummaryModifier: Modifiers.Summary,
+ ConfigModifier: Modifiers.Config,
+ // Constants
+ CONFIG_FILES: require('./constants/configFiles.js'),
+ IGNORE_FILES: require('./constants/ignoreFiles.js'),
+ DEFAULT_PLUGINS: require('./constants/defaultPlugins'),
+ EXTENSIONS_MARKDOWN: require('./constants/extsMarkdown'),
+ EXTENSIONS_ASCIIDOC: require('./constants/extsAsciidoc')
+};
diff --git a/packages/gitbook/src/browser/__tests__/render.js b/packages/gitbook/src/browser/__tests__/render.js
new file mode 100644
index 0000000..799be44
--- /dev/null
+++ b/packages/gitbook/src/browser/__tests__/render.js
@@ -0,0 +1,4 @@
+
+describe('render', () => {
+
+});
diff --git a/packages/gitbook/src/browser/loadPlugins.js b/packages/gitbook/src/browser/loadPlugins.js
new file mode 100644
index 0000000..c9bf7a6
--- /dev/null
+++ b/packages/gitbook/src/browser/loadPlugins.js
@@ -0,0 +1,31 @@
+const path = require('path');
+const timing = require('../utils/timing');
+
+/**
+ * Load all browser plugins.
+ *
+ * @param {OrderedMap<Plugin>} plugins
+ * @param {String} type ('browser', 'ebook')
+ * @return {Array}
+ */
+function loadPlugins(plugins, type) {
+ return timing.measure(
+ 'browser.loadPlugins',
+ () => {
+ return plugins
+ .valueSeq()
+ .filter(plugin => plugin.getPackage().has(type))
+ .map(plugin => {
+ const browserFile = path.resolve(
+ plugin.getPath(),
+ plugin.getPackage().get(type)
+ );
+
+ return require(browserFile);
+ })
+ .toArray();
+ }
+ );
+}
+
+module.exports = loadPlugins;
diff --git a/packages/gitbook/src/browser/render.js b/packages/gitbook/src/browser/render.js
new file mode 100644
index 0000000..86c3dff
--- /dev/null
+++ b/packages/gitbook/src/browser/render.js
@@ -0,0 +1,103 @@
+const ReactDOMServer = require('gitbook-core/lib/server');
+const GitBook = require('gitbook-core');
+const { React } = GitBook;
+
+const timing = require('../utils/timing');
+const loadPlugins = require('./loadPlugins');
+
+function HTML({head, innerHTML, payload, scripts, bootstrap}) {
+ const attrs = head.htmlAttributes.toComponent();
+
+ return (
+ <html {...attrs}>
+ <head>
+ {head.title.toComponent()}
+ {head.meta.toComponent()}
+ {head.link.toComponent()}
+ {head.style.toComponent()}
+ </head>
+ <body>
+ <div id="content" dangerouslySetInnerHTML={{__html: innerHTML}} />
+ {scripts.map(script => {
+ return <script key={script} src={script} />;
+ })}
+ <script type="application/payload+json" dangerouslySetInnerHTML={{__html: payload}} />
+ <script type="application/javascript" dangerouslySetInnerHTML={{__html: bootstrap}} />
+ {head.script.toComponent()}
+ </body>
+ </html>
+ );
+}
+HTML.propTypes = {
+ head: React.PropTypes.object,
+ innerHTML: React.PropTypes.string,
+ payload: React.PropTypes.string,
+ bootstrap: React.PropTypes.string,
+ scripts: React.PropTypes.arrayOf(React.PropTypes.string)
+};
+
+/**
+ * Get bootstrap code for a role
+ * @param {String} role
+ * @return {String}
+ */
+function getBootstrapCode(role) {
+ return `(function() { require("gitbook-core").bootstrap({ role: "${role}" }) })()`;
+}
+
+/**
+ * Render a view using plugins.
+ *
+ * @param {OrderedMap<String:Plugin>} plugin
+ * @param {Object} initialState
+ * @param {String} type ("ebook" or "browser")
+ * @param {String} role
+ * @return {String} html
+ */
+function render(plugins, initialState, type, role) {
+ return timing.measure(
+ 'browser.render',
+ () => {
+ // Load the plugins
+ const browserPlugins = loadPlugins(plugins, type);
+ const payload = JSON.stringify(initialState);
+ const context = GitBook.createContext(browserPlugins, initialState);
+
+ const currentFile = context.getState().file;
+
+ const scripts = plugins.toList()
+ .filter(plugin => plugin.getPackage().has(type))
+ .map(plugin => {
+ return currentFile.relative('gitbook/plugins/' + plugin.getName() + '.js');
+ })
+ .toArray();
+
+ const el = GitBook.renderWithContext(context, { role });
+
+ // We're done with the context
+ context.deactivate();
+
+ // Render inner body
+ const innerHTML = ReactDOMServer.renderToString(el);
+
+ // Get headers
+ const head = GitBook.Head.rewind();
+
+ // Render whole HTML page
+ const htmlEl = <HTML
+ head={head}
+ innerHTML={innerHTML}
+ payload={payload}
+ bootstrap={getBootstrapCode(role)}
+ scripts={[
+ currentFile.relative('gitbook/core.js')
+ ].concat(scripts)}
+ />;
+
+ const html = ReactDOMServer.renderToStaticMarkup(htmlEl);
+ return html;
+ }
+ );
+}
+
+module.exports = render;
diff --git a/lib/cli/build.js b/packages/gitbook/src/cli/build.js
index 023901e..3f5c937 100644
--- a/lib/cli/build.js
+++ b/packages/gitbook/src/cli/build.js
@@ -1,10 +1,10 @@
-var Parse = require('../parse');
-var Output = require('../output');
-var timing = require('../utils/timing');
+const Parse = require('../parse');
+const Output = require('../output');
+const timing = require('../utils/timing');
-var options = require('./options');
-var getBook = require('./getBook');
-var getOutputFolder = require('./getOutputFolder');
+const options = require('./options');
+const getBook = require('./getBook');
+const getOutputFolder = require('./getOutputFolder');
module.exports = {
@@ -15,11 +15,11 @@ module.exports = {
options.format,
options.timing
],
- exec: function(args, kwargs) {
- var book = getBook(args, kwargs);
- var outputFolder = getOutputFolder(args);
+ exec(args, kwargs) {
+ const book = getBook(args, kwargs);
+ const outputFolder = getOutputFolder(args);
- var Generator = Output.getGenerator(kwargs.format);
+ const Generator = Output.getGenerator(kwargs.format);
return Parse.parseBook(book)
.then(function(resultBook) {
diff --git a/lib/cli/buildEbook.js b/packages/gitbook/src/cli/buildEbook.js
index a87fac7..56e63f8 100644
--- a/lib/cli/buildEbook.js
+++ b/packages/gitbook/src/cli/buildEbook.js
@@ -1,13 +1,13 @@
-var path = require('path');
-var tmp = require('tmp');
+const path = require('path');
+const tmp = require('tmp');
-var Promise = require('../utils/promise');
-var fs = require('../utils/fs');
-var Parse = require('../parse');
-var Output = require('../output');
+const Promise = require('../utils/promise');
+const fs = require('../utils/fs');
+const Parse = require('../parse');
+const Output = require('../output');
-var options = require('./options');
-var getBook = require('./getBook');
+const options = require('./options');
+const getBook = require('./getBook');
module.exports = function(format) {
@@ -17,37 +17,37 @@ module.exports = function(format) {
options: [
options.log
],
- exec: function(args, kwargs) {
- var extension = '.' + format;
+ exec(args, kwargs) {
+ const extension = '.' + format;
// Output file will be stored in
- var outputFile = args[1] || ('book' + extension);
+ const outputFile = args[1] || ('book' + extension);
// Create temporary directory
- var outputFolder = tmp.dirSync().name;
+ const outputFolder = tmp.dirSync().name;
- var book = getBook(args, kwargs);
- var logger = book.getLogger();
- var Generator = Output.getGenerator('ebook');
+ const book = getBook(args, kwargs);
+ const logger = book.getLogger();
+ const Generator = Output.getGenerator('ebook');
return Parse.parseBook(book)
.then(function(resultBook) {
return Output.generate(Generator, resultBook, {
root: outputFolder,
- format: format
+ format
});
})
// Extract ebook file
.then(function(output) {
- var book = output.getBook();
- var languages = book.getLanguages();
+ const book = output.getBook();
+ const languages = book.getLanguages();
if (book.isMultilingual()) {
return Promise.forEach(languages.getList(), function(lang) {
- var langID = lang.getID();
+ const langID = lang.getID();
- var langOutputFile = path.join(
+ const langOutputFile = path.join(
path.dirname(outputFile),
path.basename(outputFile, extension) + '_' + langID + extension
);
diff --git a/packages/gitbook/src/cli/getBook.js b/packages/gitbook/src/cli/getBook.js
new file mode 100644
index 0000000..b37e49c
--- /dev/null
+++ b/packages/gitbook/src/cli/getBook.js
@@ -0,0 +1,23 @@
+const path = require('path');
+const Book = require('../models/book');
+const createNodeFS = require('../fs/node');
+
+/**
+ Return a book instance to work on from
+ command line args/kwargs
+
+ @param {Array} args
+ @param {Object} kwargs
+ @return {Book}
+*/
+function getBook(args, kwargs) {
+ const input = path.resolve(args[0] || process.cwd());
+ const logLevel = kwargs.log;
+
+ const fs = createNodeFS(input);
+ const book = Book.createForFS(fs);
+
+ return book.setLogLevel(logLevel);
+}
+
+module.exports = getBook;
diff --git a/packages/gitbook/src/cli/getOutputFolder.js b/packages/gitbook/src/cli/getOutputFolder.js
new file mode 100644
index 0000000..94f22da
--- /dev/null
+++ b/packages/gitbook/src/cli/getOutputFolder.js
@@ -0,0 +1,17 @@
+const path = require('path');
+
+/**
+ Return path to output folder
+
+ @param {Array} args
+ @return {String}
+*/
+function getOutputFolder(args) {
+ const bookRoot = path.resolve(args[0] || process.cwd());
+ const defaultOutputRoot = path.join(bookRoot, '_book');
+ const outputFolder = args[1] ? path.resolve(process.cwd(), args[1]) : defaultOutputRoot;
+
+ return outputFolder;
+}
+
+module.exports = getOutputFolder;
diff --git a/lib/cli/index.js b/packages/gitbook/src/cli/index.js
index f1fca1d..48ad117 100644
--- a/lib/cli/index.js
+++ b/packages/gitbook/src/cli/index.js
@@ -1,4 +1,4 @@
-var buildEbook = require('./buildEbook');
+const buildEbook = require('./buildEbook');
module.exports = [
require('./build'),
diff --git a/packages/gitbook/src/cli/init.js b/packages/gitbook/src/cli/init.js
new file mode 100644
index 0000000..51d6869
--- /dev/null
+++ b/packages/gitbook/src/cli/init.js
@@ -0,0 +1,17 @@
+const path = require('path');
+
+const options = require('./options');
+const initBook = require('../init');
+
+module.exports = {
+ name: 'init [book]',
+ description: 'setup and create files for chapters',
+ options: [
+ options.log
+ ],
+ exec(args, kwargs) {
+ const bookRoot = path.resolve(process.cwd(), args[0] || './');
+
+ return initBook(bookRoot);
+ }
+};
diff --git a/lib/cli/install.js b/packages/gitbook/src/cli/install.js
index c001711..6af4013 100644
--- a/lib/cli/install.js
+++ b/packages/gitbook/src/cli/install.js
@@ -1,8 +1,8 @@
-var options = require('./options');
-var getBook = require('./getBook');
+const options = require('./options');
+const getBook = require('./getBook');
-var Parse = require('../parse');
-var Plugins = require('../plugins');
+const Parse = require('../parse');
+const Plugins = require('../plugins');
module.exports = {
name: 'install [book]',
@@ -10,8 +10,8 @@ module.exports = {
options: [
options.log
],
- exec: function(args, kwargs) {
- var book = getBook(args, kwargs);
+ exec(args, kwargs) {
+ const book = getBook(args, kwargs);
return Parse.parseConfig(book)
.then(function(resultBook) {
diff --git a/lib/cli/options.js b/packages/gitbook/src/cli/options.js
index 72961ab..d643f91 100644
--- a/lib/cli/options.js
+++ b/packages/gitbook/src/cli/options.js
@@ -1,6 +1,6 @@
-var Logger = require('../utils/logger');
+const Logger = require('../utils/logger');
-var logOptions = {
+const logOptions = {
name: 'log',
description: 'Minimum log level to display',
values: Logger.LEVELS
@@ -11,14 +11,14 @@ var logOptions = {
defaults: 'info'
};
-var formatOption = {
+const formatOption = {
name: 'format',
description: 'Format to build to',
values: ['website', 'json', 'ebook'],
defaults: 'website'
};
-var timingOption = {
+const timingOption = {
name: 'timing',
description: 'Print timing debug information',
defaults: false
diff --git a/lib/cli/parse.js b/packages/gitbook/src/cli/parse.js
index 0fa509a..3d38fe7 100644
--- a/lib/cli/parse.js
+++ b/packages/gitbook/src/cli/parse.js
@@ -1,22 +1,22 @@
-var options = require('./options');
-var getBook = require('./getBook');
+const options = require('./options');
+const getBook = require('./getBook');
-var Parse = require('../parse');
+const Parse = require('../parse');
function printBook(book) {
- var logger = book.getLogger();
+ const logger = book.getLogger();
- var config = book.getConfig();
- var configFile = config.getFile();
+ const config = book.getConfig();
+ const configFile = config.getFile();
- var summary = book.getSummary();
- var summaryFile = summary.getFile();
+ const summary = book.getSummary();
+ const summaryFile = summary.getFile();
- var readme = book.getReadme();
- var readmeFile = readme.getFile();
+ const readme = book.getReadme();
+ const readmeFile = readme.getFile();
- var glossary = book.getGlossary();
- var glossaryFile = glossary.getFile();
+ const glossary = book.getGlossary();
+ const glossaryFile = glossary.getFile();
if (configFile.exists()) {
logger.info.ln('Configuration file is', configFile.getPath());
@@ -36,9 +36,9 @@ function printBook(book) {
}
function printMultingualBook(book) {
- var logger = book.getLogger();
- var languages = book.getLanguages();
- var books = book.getBooks();
+ const logger = book.getLogger();
+ const languages = book.getLanguages();
+ const books = book.getBooks();
logger.info.ln(languages.size + ' languages');
@@ -55,14 +55,14 @@ module.exports = {
options: [
options.log
],
- exec: function(args, kwargs) {
- var book = getBook(args, kwargs);
- var logger = book.getLogger();
+ exec(args, kwargs) {
+ const book = getBook(args, kwargs);
+ const logger = book.getLogger();
return Parse.parseBook(book)
.then(function(resultBook) {
- var rootFolder = book.getRoot();
- var contentFolder = book.getContentRoot();
+ const rootFolder = book.getRoot();
+ const contentFolder = book.getContentRoot();
logger.info.ln('Book located in:', rootFolder);
if (contentFolder != rootFolder) {
diff --git a/lib/cli/serve.js b/packages/gitbook/src/cli/serve.js
index 5340851..6397c2e 100644
--- a/lib/cli/serve.js
+++ b/packages/gitbook/src/cli/serve.js
@@ -1,24 +1,24 @@
/* eslint-disable no-console */
-var tinylr = require('tiny-lr');
-var open = require('open');
+const tinylr = require('tiny-lr');
+const open = require('open');
-var Parse = require('../parse');
-var Output = require('../output');
-var ConfigModifier = require('../modifiers').Config;
+const Parse = require('../parse');
+const Output = require('../output');
+const ConfigModifier = require('../modifiers').Config;
-var Promise = require('../utils/promise');
+const Promise = require('../utils/promise');
-var options = require('./options');
-var getBook = require('./getBook');
-var getOutputFolder = require('./getOutputFolder');
-var Server = require('./server');
-var watch = require('./watch');
+const options = require('./options');
+const getBook = require('./getBook');
+const getOutputFolder = require('./getOutputFolder');
+const Server = require('./server');
+const watch = require('./watch');
-var server, lrServer, lrPath;
+let server, lrServer, lrPath;
function waitForCtrlC() {
- var d = Promise.defer();
+ const d = Promise.defer();
process.on('SIGINT', function() {
d.resolve();
@@ -29,15 +29,15 @@ function waitForCtrlC() {
function generateBook(args, kwargs) {
- var port = kwargs.port;
- var outputFolder = getOutputFolder(args);
- var book = getBook(args, kwargs);
- var Generator = Output.getGenerator(kwargs.format);
- var browser = kwargs['browser'];
+ const port = kwargs.port;
+ const outputFolder = getOutputFolder(args);
+ const book = getBook(args, kwargs);
+ const Generator = Output.getGenerator(kwargs.format);
+ const browser = kwargs['browser'];
- var hasWatch = kwargs['watch'];
- var hasLiveReloading = kwargs['live'];
- var hasOpen = kwargs['open'];
+ const hasWatch = kwargs['watch'];
+ const hasLiveReloading = kwargs['live'];
+ const hasOpen = kwargs['open'];
// Stop server if running
if (server.isRunning()) console.log('Stopping server');
@@ -48,7 +48,7 @@ function generateBook(args, kwargs) {
.then(function(resultBook) {
if (hasLiveReloading) {
// Enable livereload plugin
- var config = resultBook.getConfig();
+ let config = resultBook.getConfig();
config = ConfigModifier.addPlugin(config, 'livereload');
resultBook = resultBook.set('config', config);
}
@@ -64,7 +64,7 @@ function generateBook(args, kwargs) {
return server.start(outputFolder, port);
})
.then(function() {
- console.log('Serving book on http://localhost:'+port);
+ console.log('Serving book on http://localhost:' + port);
if (lrPath && hasLiveReloading) {
// trigger livereload
@@ -76,7 +76,7 @@ function generateBook(args, kwargs) {
}
if (hasOpen) {
- open('http://localhost:'+port, browser);
+ open('http://localhost:' + port, browser);
}
})
.then(function() {
@@ -132,10 +132,10 @@ module.exports = {
options.log,
options.format
],
- exec: function(args, kwargs) {
+ exec(args, kwargs) {
server = new Server();
- var hasWatch = kwargs['watch'];
- var hasLiveReloading = kwargs['live'];
+ const hasWatch = kwargs['watch'];
+ const hasLiveReloading = kwargs['live'];
return Promise()
.then(function() {
diff --git a/packages/gitbook/src/cli/server.js b/packages/gitbook/src/cli/server.js
new file mode 100644
index 0000000..c494efc
--- /dev/null
+++ b/packages/gitbook/src/cli/server.js
@@ -0,0 +1,127 @@
+const events = require('events');
+const http = require('http');
+const send = require('send');
+const url = require('url');
+
+const Promise = require('../utils/promise');
+
+class Server extends events.EventEmitter {
+ constructor() {
+ super();
+ this.running = null;
+ this.dir = null;
+ this.port = 0;
+ this.sockets = [];
+ }
+
+ /**
+ * Return true if the server is running
+ * @return {Boolean}
+ */
+ isRunning() {
+ return !!this.running;
+ }
+
+ /**
+ * Stop the server
+ * @return {Promise}
+ */
+ stop() {
+ const that = this;
+ if (!this.isRunning()) return Promise();
+
+ const d = Promise.defer();
+ this.running.close(function(err) {
+ that.running = null;
+ that.emit('state', false);
+
+ if (err) d.reject(err);
+ else d.resolve();
+ });
+
+ for (let i = 0; i < this.sockets.length; i++) {
+ this.sockets[i].destroy();
+ }
+
+ return d.promise;
+ }
+
+ /**
+ * Start the server
+ * @return {Promise}
+ */
+ start(dir, port) {
+ const that = this;
+ let pre = Promise();
+ port = port || 8004;
+
+ if (that.isRunning()) pre = this.stop();
+ return pre
+ .then(function() {
+ const d = Promise.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() {
+ const resultURL = urlTransform(req.url, function(parsed) {
+ parsed.pathname += '/';
+ return parsed;
+ });
+
+ res.statusCode = 301;
+ res.setHeader('Location', resultURL);
+ res.end('Redirecting to ' + resultURL);
+ }
+
+ res.setHeader('X-Current-Location', 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;
+ });
+ }
+}
+
+/**
+ * urlTransform is a helper function that allows a function to transform
+ * a url string in it's parsed form and returns the new url as a string
+ *
+ * @param {String} uri
+ * @param {Function} fn
+ * @return {String}
+ */
+function urlTransform(uri, fn) {
+ return url.format(fn(url.parse(uri)));
+}
+
+module.exports = Server;
diff --git a/lib/cli/watch.js b/packages/gitbook/src/cli/watch.js
index 14434ab..e1d453c 100644
--- a/lib/cli/watch.js
+++ b/packages/gitbook/src/cli/watch.js
@@ -1,8 +1,8 @@
-var path = require('path');
-var chokidar = require('chokidar');
+const path = require('path');
+const chokidar = require('chokidar');
-var Promise = require('../utils/promise');
-var parsers = require('../parsers');
+const Promise = require('../utils/promise');
+const parsers = require('../parsers');
/**
Watch a folder and resolve promise once a file is modified
@@ -11,19 +11,19 @@ var parsers = require('../parsers');
@return {Promise}
*/
function watch(dir) {
- var d = Promise.defer();
+ const d = Promise.defer();
dir = path.resolve(dir);
- var toWatch = [
+ const toWatch = [
'book.json', 'book.js', '_layouts/**'
];
// Watch all parsable files
parsers.extensions.forEach(function(ext) {
- toWatch.push('**/*'+ext);
+ toWatch.push('**/*' + ext);
});
- var watcher = chokidar.watch(toWatch, {
+ const watcher = chokidar.watch(toWatch, {
cwd: dir,
ignored: '_book/**',
ignoreInitial: true
diff --git a/lib/constants/__tests__/configSchema.js b/packages/gitbook/src/constants/__tests__/configSchema.js
index efc99b9..df83680 100644
--- a/lib/constants/__tests__/configSchema.js
+++ b/packages/gitbook/src/constants/__tests__/configSchema.js
@@ -1,10 +1,10 @@
-var jsonschema = require('jsonschema');
-var schema = require('../configSchema');
+const jsonschema = require('jsonschema');
+const schema = require('../configSchema');
describe('configSchema', function() {
function validate(cfg) {
- var v = new jsonschema.Validator();
+ const v = new jsonschema.Validator();
return v.validate(cfg, schema, {
propertyName: 'config'
});
@@ -13,7 +13,7 @@ describe('configSchema', function() {
describe('structure', function() {
it('should accept dot in filename', function() {
- var result = validate({
+ const result = validate({
structure: {
readme: 'book-intro.adoc'
}
@@ -23,7 +23,7 @@ describe('configSchema', function() {
});
it('should accept uppercase in filename', function() {
- var result = validate({
+ const result = validate({
structure: {
readme: 'BOOK.adoc'
}
@@ -33,7 +33,7 @@ describe('configSchema', function() {
});
it('should not accept filepath', function() {
- var result = validate({
+ const result = validate({
structure: {
readme: 'folder/myFile.md'
}
diff --git a/packages/gitbook/src/constants/configDefault.js b/packages/gitbook/src/constants/configDefault.js
new file mode 100644
index 0000000..c384c6c
--- /dev/null
+++ b/packages/gitbook/src/constants/configDefault.js
@@ -0,0 +1,6 @@
+const Immutable = require('immutable');
+const jsonSchemaDefaults = require('json-schema-defaults');
+
+const schema = require('./configSchema');
+
+module.exports = Immutable.fromJS(jsonSchemaDefaults(schema));
diff --git a/lib/constants/configFiles.js b/packages/gitbook/src/constants/configFiles.js
index a67fd74..a67fd74 100644
--- a/lib/constants/configFiles.js
+++ b/packages/gitbook/src/constants/configFiles.js
diff --git a/lib/constants/configSchema.js b/packages/gitbook/src/constants/configSchema.js
index d2126c6..9aaf8cd 100644
--- a/lib/constants/configSchema.js
+++ b/packages/gitbook/src/constants/configSchema.js
@@ -1,4 +1,4 @@
-var FILENAME_REGEX = '^[a-zA-Z-._\d,\s]+$';
+const FILENAME_REGEX = '^[a-zA-Z-._\d,\s]+$';
module.exports = {
'$schema': 'http://json-schema.org/schema#',
diff --git a/packages/gitbook/src/constants/defaultBlocks.js b/packages/gitbook/src/constants/defaultBlocks.js
new file mode 100644
index 0000000..d1fe6ff
--- /dev/null
+++ b/packages/gitbook/src/constants/defaultBlocks.js
@@ -0,0 +1,5 @@
+const Immutable = require('immutable');
+
+module.exports = Immutable.Map({
+
+});
diff --git a/lib/constants/defaultFilters.js b/packages/gitbook/src/constants/defaultFilters.js
index 35025cc..c9bffe1 100644
--- a/lib/constants/defaultFilters.js
+++ b/packages/gitbook/src/constants/defaultFilters.js
@@ -1,15 +1,15 @@
-var Immutable = require('immutable');
-var moment = require('moment');
+const Immutable = require('immutable');
+const moment = require('moment');
module.exports = Immutable.Map({
// Format a date
// ex: 'MMMM Do YYYY, h:mm:ss a
- date: function(time, format) {
+ date(time, format) {
return moment(time).format(format);
},
// Relative Time
- dateFromNow: function(time) {
+ dateFromNow(time) {
return moment(time).fromNow();
}
});
diff --git a/lib/constants/defaultPlugins.js b/packages/gitbook/src/constants/defaultPlugins.js
index 6d15971..326ad3a 100644
--- a/lib/constants/defaultPlugins.js
+++ b/packages/gitbook/src/constants/defaultPlugins.js
@@ -1,7 +1,7 @@
-var Immutable = require('immutable');
-var PluginDependency = require('../models/pluginDependency');
+const Immutable = require('immutable');
+const PluginDependency = require('../models/pluginDependency');
-var pkg = require('../../package.json');
+const pkg = require('../../package.json');
/**
* Create a PluginDependency from a dependency of gitbook
@@ -9,8 +9,8 @@ var pkg = require('../../package.json');
* @return {PluginDependency}
*/
function createFromDependency(pluginName) {
- var npmID = PluginDependency.nameToNpmID(pluginName);
- var version = pkg.dependencies[npmID];
+ const npmID = PluginDependency.nameToNpmID(pluginName);
+ const version = pkg.dependencies[npmID];
return PluginDependency.create(pluginName, version);
}
@@ -24,6 +24,8 @@ module.exports = Immutable.List([
'search',
'lunr',
'sharing',
- 'fontsettings',
+ 'hints',
+ 'headings',
+ 'copy-code',
'theme-default'
]).map(createFromDependency);
diff --git a/lib/constants/extsAsciidoc.js b/packages/gitbook/src/constants/extsAsciidoc.js
index b2f4ce4..b2f4ce4 100644
--- a/lib/constants/extsAsciidoc.js
+++ b/packages/gitbook/src/constants/extsAsciidoc.js
diff --git a/lib/constants/extsMarkdown.js b/packages/gitbook/src/constants/extsMarkdown.js
index 44bf36b..44bf36b 100644
--- a/lib/constants/extsMarkdown.js
+++ b/packages/gitbook/src/constants/extsMarkdown.js
diff --git a/lib/constants/ignoreFiles.js b/packages/gitbook/src/constants/ignoreFiles.js
index aac225e..aac225e 100644
--- a/lib/constants/ignoreFiles.js
+++ b/packages/gitbook/src/constants/ignoreFiles.js
diff --git a/lib/constants/pluginAssetsFolder.js b/packages/gitbook/src/constants/pluginAssetsFolder.js
index cd44722..cd44722 100644
--- a/lib/constants/pluginAssetsFolder.js
+++ b/packages/gitbook/src/constants/pluginAssetsFolder.js
diff --git a/lib/constants/pluginHooks.js b/packages/gitbook/src/constants/pluginHooks.js
index 2d5dcaa..2d5dcaa 100644
--- a/lib/constants/pluginHooks.js
+++ b/packages/gitbook/src/constants/pluginHooks.js
diff --git a/lib/constants/pluginPrefix.js b/packages/gitbook/src/constants/pluginPrefix.js
index c7f2dd0..c7f2dd0 100644
--- a/lib/constants/pluginPrefix.js
+++ b/packages/gitbook/src/constants/pluginPrefix.js
diff --git a/lib/constants/pluginResources.js b/packages/gitbook/src/constants/pluginResources.js
index ae283bf..cc9d134 100644
--- a/lib/constants/pluginResources.js
+++ b/packages/gitbook/src/constants/pluginResources.js
@@ -1,4 +1,4 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
module.exports = Immutable.List([
'js',
diff --git a/lib/constants/templatesFolder.js b/packages/gitbook/src/constants/templatesFolder.js
index aad6a72..aad6a72 100644
--- a/lib/constants/templatesFolder.js
+++ b/packages/gitbook/src/constants/templatesFolder.js
diff --git a/lib/constants/themePrefix.js b/packages/gitbook/src/constants/themePrefix.js
index 99428de..621e85c 100644
--- a/lib/constants/themePrefix.js
+++ b/packages/gitbook/src/constants/themePrefix.js
@@ -1,4 +1,4 @@
/*
All GitBook themes plugins name start with this prefix once shorted.
*/
-module.exports = 'theme-'; \ No newline at end of file
+module.exports = 'theme-';
diff --git a/lib/fs/__tests__/mock.js b/packages/gitbook/src/fs/__tests__/mock.js
index 04bd46a..7d1ea48 100644
--- a/lib/fs/__tests__/mock.js
+++ b/packages/gitbook/src/fs/__tests__/mock.js
@@ -1,7 +1,7 @@
-var createMockFS = require('../mock');
+const createMockFS = require('../mock');
describe('MockFS', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'README.md': 'Hello World',
'SUMMARY.md': '# Summary',
'folder': {
@@ -79,4 +79,3 @@ describe('MockFS', function() {
});
});
-
diff --git a/lib/fs/mock.js b/packages/gitbook/src/fs/mock.js
index 784c533..611b2ab 100644
--- a/lib/fs/mock.js
+++ b/packages/gitbook/src/fs/mock.js
@@ -1,26 +1,26 @@
-var path = require('path');
-var is = require('is');
-var Buffer = require('buffer').Buffer;
-var Immutable = require('immutable');
+const path = require('path');
+const is = require('is');
+const Buffer = require('buffer').Buffer;
+const Immutable = require('immutable');
-var FS = require('../models/fs');
-var error = require('../utils/error');
+const FS = require('../models/fs');
+const error = require('../utils/error');
/**
- Create a fake filesystem for unit testing GitBook.
-
- @param {Map<String:String|Map>}
-*/
-function createMockFS(files) {
+ * Create a fake filesystem for unit testing GitBook.
+ * @param {Map<String:String|Map>}
+ * @return {FS}
+ */
+function createMockFS(files, root = '') {
files = Immutable.fromJS(files);
- var mtime = new Date();
+ const mtime = new Date();
function getFile(filePath) {
- var parts = path.normalize(filePath).split(path.sep);
+ const parts = path.normalize(filePath).split(path.sep);
return parts.reduce(function(list, part, i) {
if (!list) return null;
- var file;
+ let file;
if (!part || part === '.') file = list;
else file = list.get(part);
@@ -41,7 +41,7 @@ function createMockFS(files) {
}
function fsReadFile(filePath) {
- var file = getFile(filePath);
+ const file = getFile(filePath);
if (!is.string(file)) {
throw error.FileNotFoundError({
filename: filePath
@@ -52,7 +52,7 @@ function createMockFS(files) {
}
function fsStatFile(filePath) {
- var file = getFile(filePath);
+ const file = getFile(filePath);
if (!file) {
throw error.FileNotFoundError({
filename: filePath
@@ -60,12 +60,12 @@ function createMockFS(files) {
}
return {
- mtime: mtime
+ mtime
};
}
function fsReadDir(filePath) {
- var dir = getFile(filePath);
+ const dir = getFile(filePath);
if (!dir || is.string(dir)) {
throw error.FileNotFoundError({
filename: filePath
@@ -85,10 +85,10 @@ function createMockFS(files) {
return FS.create({
root: '',
- fsExists: fsExists,
- fsReadFile: fsReadFile,
- fsStatFile: fsStatFile,
- fsReadDir: fsReadDir
+ fsExists,
+ fsReadFile,
+ fsStatFile,
+ fsReadDir
});
}
diff --git a/lib/fs/node.js b/packages/gitbook/src/fs/node.js
index dfe9fae..6e28daf 100644
--- a/lib/fs/node.js
+++ b/packages/gitbook/src/fs/node.js
@@ -1,9 +1,9 @@
-var path = require('path');
-var Immutable = require('immutable');
-var fresh = require('fresh-require');
+const path = require('path');
+const Immutable = require('immutable');
+const fresh = require('fresh-require');
-var fs = require('../utils/fs');
-var FS = require('../models/fs');
+const fs = require('../utils/fs');
+const FS = require('../models/fs');
function fsReadDir(folder) {
return fs.readdir(folder)
@@ -14,7 +14,7 @@ function fsReadDir(folder) {
.map(function(file) {
if (file == '.' || file == '..') return;
- var stat = fs.statSync(path.join(folder, file));
+ const stat = fs.statSync(path.join(folder, file));
if (stat.isDirectory()) file = file + path.sep;
return file;
})
@@ -30,13 +30,13 @@ function fsLoadObject(filename) {
module.exports = function createNodeFS(root) {
return FS.create({
- root: root,
+ root,
fsExists: fs.exists,
fsReadFile: fs.readFile,
fsStatFile: fs.stat,
- fsReadDir: fsReadDir,
- fsLoadObject: fsLoadObject,
+ fsReadDir,
+ fsLoadObject,
fsReadAsStream: fs.readStream
});
};
diff --git a/lib/gitbook.js b/packages/gitbook/src/gitbook.js
index bafd3b8..5786e68 100644
--- a/lib/gitbook.js
+++ b/packages/gitbook/src/gitbook.js
@@ -1,10 +1,10 @@
-var semver = require('semver');
-var pkg = require('../package.json');
+const semver = require('semver');
+const pkg = require('../package.json');
-var VERSION = pkg.version;
-var VERSION_STABLE = VERSION.replace(/\-(\S+)/g, '');
+const VERSION = pkg.version;
+const VERSION_STABLE = VERSION.replace(/\-(\S+)/g, '');
-var START_TIME = new Date();
+const START_TIME = new Date();
/**
Verify that this gitbook version satisfies a requirement
@@ -23,6 +23,6 @@ function satisfies(condition) {
module.exports = {
version: pkg.version,
- satisfies: satisfies,
- START_TIME: START_TIME
+ satisfies,
+ START_TIME
};
diff --git a/packages/gitbook/src/index.js b/packages/gitbook/src/index.js
new file mode 100644
index 0000000..fc8f254
--- /dev/null
+++ b/packages/gitbook/src/index.js
@@ -0,0 +1,9 @@
+const common = require('./browser');
+
+module.exports = {
+ ...common,
+ initBook: require('./init'),
+ createNodeFS: require('./fs/node'),
+ Output: require('./output'),
+ commands: require('./cli')
+};
diff --git a/lib/init.js b/packages/gitbook/src/init.js
index c112d4d..bbd5f90 100644
--- a/lib/init.js
+++ b/packages/gitbook/src/init.js
@@ -1,12 +1,12 @@
-var path = require('path');
+const path = require('path');
-var createNodeFS = require('./fs/node');
-var fs = require('./utils/fs');
-var Promise = require('./utils/promise');
-var File = require('./models/file');
-var Readme = require('./models/readme');
-var Book = require('./models/book');
-var Parse = require('./parse');
+const createNodeFS = require('./fs/node');
+const fs = require('./utils/fs');
+const Promise = require('./utils/promise');
+const File = require('./models/file');
+const Readme = require('./models/readme');
+const Book = require('./models/book');
+const Parse = require('./parse');
/**
Initialize folder structure for a book
@@ -17,38 +17,38 @@ var Parse = require('./parse');
@return {Promise}
*/
function initBook(rootFolder) {
- var extension = '.md';
+ const extension = '.md';
return fs.mkdirp(rootFolder)
// Parse the summary and readme
.then(function() {
- var fs = createNodeFS(rootFolder);
- var book = Book.createForFS(fs);
+ const bookFS = createNodeFS(rootFolder);
+ const book = Book.createForFS(bookFS);
return Parse.parseReadme(book)
// Setup default readme if doesn't found one
.fail(function() {
- var readmeFile = File.createWithFilepath('README' + extension);
- var readme = Readme.create(readmeFile);
+ const readmeFile = File.createWithFilepath('README' + extension);
+ const readme = Readme.create(readmeFile);
return book.setReadme(readme);
});
})
.then(Parse.parseSummary)
.then(function(book) {
- var logger = book.getLogger();
- var summary = book.getSummary();
- var summaryFile = summary.getFile();
- var summaryFilename = summaryFile.getPath() || ('SUMMARY' + extension);
+ const logger = book.getLogger();
+ const summary = book.getSummary();
+ const summaryFile = summary.getFile();
+ const summaryFilename = summaryFile.getPath() || ('SUMMARY' + extension);
- var articles = summary.getArticlesAsList();
+ const articles = summary.getArticlesAsList();
// Write pages
return Promise.forEach(articles, function(article) {
- var articlePath = article.getPath();
- var filePath = articlePath? path.join(rootFolder, articlePath) : null;
+ const articlePath = article.getPath();
+ const filePath = articlePath ? path.join(rootFolder, articlePath) : null;
if (!filePath) {
return;
}
@@ -64,7 +64,7 @@ function initBook(rootFolder) {
// Write summary
.then(function() {
- var filePath = path.join(rootFolder, summaryFilename);
+ const filePath = path.join(rootFolder, summaryFilename);
return fs.ensureFile(filePath)
.then(function() {
diff --git a/packages/gitbook/src/json/encodeFile.js b/packages/gitbook/src/json/encodeFile.js
new file mode 100644
index 0000000..2295ac1
--- /dev/null
+++ b/packages/gitbook/src/json/encodeFile.js
@@ -0,0 +1,23 @@
+
+/**
+ * Return a JSON representation of a file
+ *
+ * @param {File} file
+ * @param {URIIndex} urls
+ * @return {JSON} json
+ */
+function encodeFileToJson(file, urls) {
+ const filePath = file.getPath();
+ if (!filePath) {
+ return undefined;
+ }
+
+ return {
+ path: filePath,
+ mtime: file.getMTime(),
+ type: file.getType(),
+ url: urls.resolveToURL(filePath)
+ };
+}
+
+module.exports = encodeFileToJson;
diff --git a/packages/gitbook/src/json/encodeGlossary.js b/packages/gitbook/src/json/encodeGlossary.js
new file mode 100644
index 0000000..d82bb62
--- /dev/null
+++ b/packages/gitbook/src/json/encodeGlossary.js
@@ -0,0 +1,22 @@
+const encodeFile = require('./encodeFile');
+const encodeGlossaryEntry = require('./encodeGlossaryEntry');
+
+/**
+ * Encode a glossary to JSON
+ *
+ * @param {Glossary} glossary
+ * @param {URIIndex} urls
+ * @return {JSON} json
+ */
+function encodeGlossary(glossary, urls) {
+ const file = glossary.getFile();
+ const entries = glossary.getEntries();
+
+ return {
+ file: encodeFile(file, urls),
+ entries: entries
+ .map(encodeGlossaryEntry).toJS()
+ };
+}
+
+module.exports = encodeGlossary;
diff --git a/lib/json/encodeGlossaryEntry.js b/packages/gitbook/src/json/encodeGlossaryEntry.js
index d163f45..52e13c3 100644
--- a/lib/json/encodeGlossaryEntry.js
+++ b/packages/gitbook/src/json/encodeGlossaryEntry.js
@@ -1,10 +1,10 @@
/**
- Encode a SummaryArticle to JSON
-
- @param {GlossaryEntry}
- @return {Object}
-*/
+ * Encode a SummaryArticle to JSON
+ *
+ * @param {GlossaryEntry} entry
+ * @return {JSON} json
+ */
function encodeGlossaryEntry(entry) {
return {
id: entry.getID(),
diff --git a/packages/gitbook/src/json/encodeLanguages.js b/packages/gitbook/src/json/encodeLanguages.js
new file mode 100644
index 0000000..809cfb2
--- /dev/null
+++ b/packages/gitbook/src/json/encodeLanguages.js
@@ -0,0 +1,29 @@
+const encodeFile = require('./encodeFile');
+
+/**
+ * Encode a languages listing to JSON
+ *
+ * @param {Languages} languages
+ * @param {String} currentLanguage
+ * @param {URIIndex} urls
+ * @return {JSON} json
+*/
+function encodeLanguages(languages, currentLanguage, urls) {
+ const file = languages.getFile();
+ const list = languages.getList();
+
+ return {
+ file: encodeFile(file, urls),
+ current: currentLanguage,
+ list: list
+ .valueSeq()
+ .map(function(lang) {
+ return {
+ id: lang.getID(),
+ title: lang.getTitle()
+ };
+ }).toJS()
+ };
+}
+
+module.exports = encodeLanguages;
diff --git a/packages/gitbook/src/json/encodePage.js b/packages/gitbook/src/json/encodePage.js
new file mode 100644
index 0000000..0671721
--- /dev/null
+++ b/packages/gitbook/src/json/encodePage.js
@@ -0,0 +1,41 @@
+const encodeSummaryArticle = require('./encodeSummaryArticle');
+
+/**
+ * Return a JSON representation of a page.
+ *
+ * @param {Page} page
+ * @param {Summary} summary
+ * @param {URIIndex} urls
+ * @return {JSON} json
+ */
+function encodePage(page, summary, urls) {
+ const file = page.getFile();
+ const attributes = page.getAttributes();
+ const article = summary.getByPath(file.getPath());
+
+ const result = {
+ content: page.getContent(),
+ dir: page.getDir(),
+ attributes: attributes.toJS()
+ };
+
+ if (article) {
+ result.title = article.getTitle();
+ result.level = article.getLevel();
+ result.depth = article.getDepth();
+
+ const nextArticle = summary.getNextArticle(article);
+ if (nextArticle) {
+ result.next = encodeSummaryArticle(nextArticle, urls, false);
+ }
+
+ const prevArticle = summary.getPrevArticle(article);
+ if (prevArticle) {
+ result.previous = encodeSummaryArticle(prevArticle, urls, false);
+ }
+ }
+
+ return result;
+}
+
+module.exports = encodePage;
diff --git a/packages/gitbook/src/json/encodeReadme.js b/packages/gitbook/src/json/encodeReadme.js
new file mode 100644
index 0000000..dff81cf
--- /dev/null
+++ b/packages/gitbook/src/json/encodeReadme.js
@@ -0,0 +1,18 @@
+const encodeFile = require('./encodeFile');
+
+/**
+ * Encode a readme to JSON.
+ *
+ * @param {Readme} readme
+ * @param {URIIndex} urls
+ * @return {JSON} json
+ */
+function encodeReadme(readme, urls) {
+ const file = readme.getFile();
+
+ return {
+ file: encodeFile(file, urls)
+ };
+}
+
+module.exports = encodeReadme;
diff --git a/packages/gitbook/src/json/encodeState.js b/packages/gitbook/src/json/encodeState.js
new file mode 100644
index 0000000..faac972
--- /dev/null
+++ b/packages/gitbook/src/json/encodeState.js
@@ -0,0 +1,42 @@
+const gitbook = require('../gitbook');
+const encodeSummary = require('./encodeSummary');
+const encodeGlossary = require('./encodeGlossary');
+const encodeReadme = require('./encodeReadme');
+const encodeLanguages = require('./encodeLanguages');
+const encodePage = require('./encodePage');
+const encodeFile = require('./encodeFile');
+
+/**
+ * Encode context to JSON from an output instance.
+ * This JSON representation is used as initial state for the redux store.
+ *
+ * @param {Output} output
+ * @param {Page} page?
+ * @return {JSON}
+ */
+function encodeStateToJSON(output, page) {
+ const book = output.getBook();
+ const urls = output.getURLIndex();
+
+ return {
+ output: {
+ name: output.getGenerator()
+ },
+ gitbook: {
+ version: gitbook.version,
+ time: gitbook.START_TIME
+ },
+
+ summary: encodeSummary(book.getSummary(), urls),
+ glossary: encodeGlossary(book.getGlossary(), urls),
+ readme: encodeReadme(book.getReadme(), urls),
+ config: book.getConfig().getValues().toJS(),
+ languages: book.isMultilingual() ?
+ encodeLanguages(book.getLanguages(), book.getLanguage(), urls) : undefined,
+
+ page: page ? encodePage(page, book.getSummary(), urls) : undefined,
+ file: page ? encodeFile(page.getFile(), urls) : undefined
+ };
+}
+
+module.exports = encodeStateToJSON;
diff --git a/packages/gitbook/src/json/encodeSummary.js b/packages/gitbook/src/json/encodeSummary.js
new file mode 100644
index 0000000..8380379
--- /dev/null
+++ b/packages/gitbook/src/json/encodeSummary.js
@@ -0,0 +1,23 @@
+const encodeFile = require('./encodeFile');
+const encodeSummaryPart = require('./encodeSummaryPart');
+
+/**
+ * Encode a summary to JSON
+ *
+ * @param {Summary} summary
+ * @param {URIIndex} urls
+ * @return {Object}
+ */
+function encodeSummary(summary, urls) {
+ const file = summary.getFile();
+ const parts = summary.getParts();
+
+ return {
+ file: encodeFile(file, urls),
+ parts: parts
+ .map(part => encodeSummaryPart(part, urls))
+ .toJS()
+ };
+}
+
+module.exports = encodeSummary;
diff --git a/packages/gitbook/src/json/encodeSummaryArticle.js b/packages/gitbook/src/json/encodeSummaryArticle.js
new file mode 100644
index 0000000..0fb6368
--- /dev/null
+++ b/packages/gitbook/src/json/encodeSummaryArticle.js
@@ -0,0 +1,30 @@
+
+/**
+ * Encode a SummaryArticle to JSON
+ *
+ * @param {SummaryArticle} article
+ * @param {URIIndex} urls
+ * @param {Boolean} recursive
+ * @return {Object}
+ */
+function encodeSummaryArticle(article, urls, recursive) {
+ let articles = undefined;
+ if (recursive !== false) {
+ articles = article.getArticles()
+ .map(innerArticle => encodeSummaryArticle(innerArticle, urls, recursive))
+ .toJS();
+ }
+
+ return {
+ title: article.getTitle(),
+ level: article.getLevel(),
+ depth: article.getDepth(),
+ anchor: article.getAnchor(),
+ url: urls.resolveToURL(article.getPath() || article.getUrl()),
+ path: article.getPath(),
+ ref: article.getRef(),
+ articles
+ };
+}
+
+module.exports = encodeSummaryArticle;
diff --git a/packages/gitbook/src/json/encodeSummaryPart.js b/packages/gitbook/src/json/encodeSummaryPart.js
new file mode 100644
index 0000000..fbcdc4c
--- /dev/null
+++ b/packages/gitbook/src/json/encodeSummaryPart.js
@@ -0,0 +1,19 @@
+const encodeSummaryArticle = require('./encodeSummaryArticle');
+
+/**
+ * Encode a SummaryPart to JSON.
+ *
+ * @param {SummaryPart} part
+ * @param {URIIndex} urls
+ * @return {JSON} json
+ */
+function encodeSummaryPart(part, urls) {
+ return {
+ title: part.getTitle(),
+ articles: part.getArticles()
+ .map(article => encodeSummaryArticle(article, urls))
+ .toJS()
+ };
+}
+
+module.exports = encodeSummaryPart;
diff --git a/packages/gitbook/src/json/index.js b/packages/gitbook/src/json/index.js
new file mode 100644
index 0000000..49ab195
--- /dev/null
+++ b/packages/gitbook/src/json/index.js
@@ -0,0 +1,10 @@
+
+module.exports = {
+ encodeState: require('./encodeState'),
+ encodeFile: require('./encodeFile'),
+ encodePage: require('./encodePage'),
+ encodeSummary: require('./encodeSummary'),
+ encodeSummaryArticle: require('./encodeSummaryArticle'),
+ encodeReadme: require('./encodeReadme'),
+ encodeLanguages: require('./encodeLanguages')
+};
diff --git a/lib/models/__tests__/config.js b/packages/gitbook/src/models/__tests__/config.js
index abad754..a865f96 100644
--- a/lib/models/__tests__/config.js
+++ b/packages/gitbook/src/models/__tests__/config.js
@@ -1,8 +1,8 @@
-var Immutable = require('immutable');
-var Config = require('../config');
+const Immutable = require('immutable');
+const Config = require('../config');
describe('Config', function() {
- var config = Config.createWithValues({
+ const config = Config.createWithValues({
hello: {
world: 1,
test: 'Hello',
@@ -12,32 +12,32 @@ describe('Config', function() {
describe('getValue', function() {
it('must return value as immutable', function() {
- var value = config.getValue('hello');
+ const value = config.getValue('hello');
expect(Immutable.Map.isMap(value)).toBeTruthy();
});
it('must return deep value', function() {
- var value = config.getValue('hello.world');
+ const value = config.getValue('hello.world');
expect(value).toBe(1);
});
it('must return default value if non existant', function() {
- var value = config.getValue('hello.nonExistant', 'defaultValue');
+ const value = config.getValue('hello.nonExistant', 'defaultValue');
expect(value).toBe('defaultValue');
});
it('must not return default value for falsy values', function() {
- var value = config.getValue('hello.isFalse', 'defaultValue');
+ const value = config.getValue('hello.isFalse', 'defaultValue');
expect(value).toBe(false);
});
});
describe('setValue', function() {
it('must set value as immutable', function() {
- var testConfig = config.setValue('hello', {
+ const testConfig = config.setValue('hello', {
'cool': 1
});
- var value = testConfig.getValue('hello');
+ const value = testConfig.getValue('hello');
expect(Immutable.Map.isMap(value)).toBeTruthy();
expect(value.size).toBe(1);
@@ -45,9 +45,9 @@ describe('Config', function() {
});
it('must set deep value', function() {
- var testConfig = config.setValue('hello.world', 2);
- var hello = testConfig.getValue('hello');
- var world = testConfig.getValue('hello.world');
+ const testConfig = config.setValue('hello.world', 2);
+ const hello = testConfig.getValue('hello');
+ const world = testConfig.getValue('hello.world');
expect(Immutable.Map.isMap(hello)).toBeTruthy();
expect(hello.size).toBe(3);
@@ -58,11 +58,11 @@ describe('Config', function() {
describe('toReducedVersion', function() {
it('must only return diffs for simple values', function() {
- var _config = Config.createWithValues({
+ const _config = Config.createWithValues({
gitbook: '3.0.0'
});
- var reducedVersion = _config.toReducedVersion();
+ const reducedVersion = _config.toReducedVersion();
expect(reducedVersion.toJS()).toEqual({
gitbook: '3.0.0'
@@ -70,13 +70,13 @@ describe('Config', function() {
});
it('must only return diffs for deep values', function() {
- var _config = Config.createWithValues({
+ const _config = Config.createWithValues({
structure: {
readme: 'intro.md'
}
});
- var reducedVersion = _config.toReducedVersion();
+ const reducedVersion = _config.toReducedVersion();
expect(reducedVersion.toJS()).toEqual({
structure: {
@@ -87,4 +87,3 @@ describe('Config', function() {
});
});
-
diff --git a/lib/models/__tests__/glossary.js b/packages/gitbook/src/models/__tests__/glossary.js
index 5bf64dc..b50338a 100644
--- a/lib/models/__tests__/glossary.js
+++ b/packages/gitbook/src/models/__tests__/glossary.js
@@ -1,9 +1,9 @@
-var File = require('../file');
-var Glossary = require('../glossary');
-var GlossaryEntry = require('../glossaryEntry');
+const File = require('../file');
+const Glossary = require('../glossary');
+const GlossaryEntry = require('../glossaryEntry');
describe('Glossary', function() {
- var glossary = Glossary.createFromEntries(File(), [
+ const glossary = Glossary.createFromEntries(File(), [
{
name: 'Hello World',
description: 'Awesome!'
@@ -16,13 +16,13 @@ describe('Glossary', function() {
describe('createFromEntries', function() {
it('must add all entries', function() {
- var entries = glossary.getEntries();
+ const entries = glossary.getEntries();
expect(entries.size).toBe(2);
});
it('must add entries as GlossaryEntries', function() {
- var entries = glossary.getEntries();
- var entry = entries.get('hello-world');
+ const entries = glossary.getEntries();
+ const entry = entries.get('hello-world');
expect(entry instanceof GlossaryEntry).toBeTruthy();
});
});
@@ -37,4 +37,3 @@ describe('Glossary', function() {
});
});
-
diff --git a/lib/models/__tests__/glossaryEntry.js b/packages/gitbook/src/models/__tests__/glossaryEntry.js
index 833115d..66ddab4 100644
--- a/lib/models/__tests__/glossaryEntry.js
+++ b/packages/gitbook/src/models/__tests__/glossaryEntry.js
@@ -1,9 +1,9 @@
-var GlossaryEntry = require('../glossaryEntry');
+const GlossaryEntry = require('../glossaryEntry');
describe('GlossaryEntry', function() {
describe('getID', function() {
it('must return a normalized ID', function() {
- var entry = new GlossaryEntry({
+ const entry = new GlossaryEntry({
name: 'Hello World'
});
@@ -12,4 +12,3 @@ describe('GlossaryEntry', function() {
});
});
-
diff --git a/lib/models/__tests__/page.js b/packages/gitbook/src/models/__tests__/page.js
index 479d276..b004121 100644
--- a/lib/models/__tests__/page.js
+++ b/packages/gitbook/src/models/__tests__/page.js
@@ -1,11 +1,11 @@
-var Immutable = require('immutable');
-var Page = require('../page');
+const Immutable = require('immutable');
+const Page = require('../page');
describe('Page', function() {
describe('toText', function() {
it('must not prepend frontmatter if no attributes', function() {
- var page = Page().merge({
+ const page = (new Page()).merge({
content: 'Hello World'
});
@@ -13,7 +13,7 @@ describe('Page', function() {
});
it('must prepend frontmatter if attributes', function() {
- var page = Page().merge({
+ const page = (new Page()).merge({
content: 'Hello World',
attributes: Immutable.fromJS({
hello: 'world'
@@ -24,5 +24,3 @@ describe('Page', function() {
});
});
});
-
-
diff --git a/lib/models/__tests__/plugin.js b/packages/gitbook/src/models/__tests__/plugin.js
index b229664..63cb58c 100644
--- a/lib/models/__tests__/plugin.js
+++ b/packages/gitbook/src/models/__tests__/plugin.js
@@ -1,15 +1,15 @@
describe('Plugin', function() {
- var Plugin = require('../plugin');
+ const Plugin = require('../plugin');
describe('createFromString', function() {
it('must parse name', function() {
- var plugin = Plugin.createFromString('hello');
+ const plugin = Plugin.createFromString('hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('*');
});
it('must parse version', function() {
- var plugin = Plugin.createFromString('hello@1.0.0');
+ const plugin = Plugin.createFromString('hello@1.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('1.0.0');
});
@@ -17,11 +17,10 @@ describe('Plugin', function() {
describe('isLoaded', function() {
it('must return false for empty plugin', function() {
- var plugin = Plugin.createFromString('hello');
+ const plugin = Plugin.createFromString('hello');
expect(plugin.isLoaded()).toBe(false);
});
});
});
-
diff --git a/lib/models/__tests__/pluginDependency.js b/packages/gitbook/src/models/__tests__/pluginDependency.js
index cb04cf2..cda0cc2 100644
--- a/lib/models/__tests__/pluginDependency.js
+++ b/packages/gitbook/src/models/__tests__/pluginDependency.js
@@ -1,29 +1,29 @@
-var Immutable = require('immutable');
-var PluginDependency = require('../pluginDependency');
+const Immutable = require('immutable');
+const PluginDependency = require('../pluginDependency');
describe('PluginDependency', function() {
describe('createFromString', function() {
it('must parse name', function() {
- var plugin = PluginDependency.createFromString('hello');
+ const plugin = PluginDependency.createFromString('hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('*');
});
it('must parse state', function() {
- var plugin = PluginDependency.createFromString('-hello');
+ const plugin = PluginDependency.createFromString('-hello');
expect(plugin.getName()).toBe('hello');
expect(plugin.isEnabled()).toBe(false);
});
describe('Version', function() {
it('must parse version', function() {
- var plugin = PluginDependency.createFromString('hello@1.0.0');
+ const plugin = PluginDependency.createFromString('hello@1.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('1.0.0');
});
it('must parse semver', function() {
- var plugin = PluginDependency.createFromString('hello@>=4.0.0');
+ const plugin = PluginDependency.createFromString('hello@>=4.0.0');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('>=4.0.0');
});
@@ -31,13 +31,13 @@ describe('PluginDependency', function() {
describe('GIT Version', function() {
it('must handle HTTPS urls', function() {
- var plugin = PluginDependency.createFromString('hello@git+https://github.com/GitbookIO/plugin-ga.git');
+ const plugin = PluginDependency.createFromString('hello@git+https://github.com/GitbookIO/plugin-ga.git');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('git+https://github.com/GitbookIO/plugin-ga.git');
});
it('must handle SSH urls', function() {
- var plugin = PluginDependency.createFromString('hello@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
+ const plugin = PluginDependency.createFromString('hello@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
expect(plugin.getName()).toBe('hello');
expect(plugin.getVersion()).toBe('git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
});
@@ -45,7 +45,7 @@ describe('PluginDependency', function() {
describe('listToArray', function() {
it('must create an array from a list of plugin dependencies', function() {
- var list = PluginDependency.listToArray(Immutable.List([
+ const list = PluginDependency.listToArray(Immutable.List([
PluginDependency.createFromString('hello@1.0.0'),
PluginDependency.createFromString('noversion'),
PluginDependency.createFromString('-disabled')
@@ -61,14 +61,14 @@ describe('PluginDependency', function() {
describe('listFromArray', function() {
it('must create an array from a list of plugin dependencies', function() {
- var arr = Immutable.fromJS([
+ const arr = Immutable.fromJS([
'hello@1.0.0',
{
'name': 'plugin-ga',
'version': 'git+ssh://samy@github.com/GitbookIO/plugin-ga.git'
}
]);
- var list = PluginDependency.listFromArray(arr);
+ const list = PluginDependency.listFromArray(arr);
expect(list.first().getName()).toBe('hello');
expect(list.first().getVersion()).toBe('1.0.0');
diff --git a/lib/models/__tests__/summary.js b/packages/gitbook/src/models/__tests__/summary.js
index 29c9330..49ed9b1 100644
--- a/lib/models/__tests__/summary.js
+++ b/packages/gitbook/src/models/__tests__/summary.js
@@ -1,9 +1,9 @@
describe('Summary', function() {
- var File = require('../file');
- var Summary = require('../summary');
+ const File = require('../file');
+ const Summary = require('../summary');
- var summary = Summary.createFromParts(File(), [
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -30,21 +30,21 @@ describe('Summary', function() {
describe('createFromEntries', function() {
it('must add all parts', function() {
- var parts = summary.getParts();
+ const parts = summary.getParts();
expect(parts.size).toBe(2);
});
});
describe('getByLevel', function() {
it('can return a Part', function() {
- var part = summary.getByLevel('1');
+ const part = summary.getByLevel('1');
expect(part).toBeDefined();
expect(part.getArticles().size).toBe(4);
});
it('can return a Part (2)', function() {
- var part = summary.getByLevel('2');
+ const part = summary.getByLevel('2');
expect(part).toBeDefined();
expect(part.getTitle()).toBe('Test');
@@ -52,7 +52,7 @@ describe('Summary', function() {
});
it('can return an Article', function() {
- var article = summary.getByLevel('1.1');
+ const article = summary.getByLevel('1.1');
expect(article).toBeDefined();
expect(article.getTitle()).toBe('My First Article');
@@ -61,21 +61,21 @@ describe('Summary', function() {
describe('getByPath', function() {
it('return correct article', function() {
- var article = summary.getByPath('README.md');
+ const article = summary.getByPath('README.md');
expect(article).toBeDefined();
expect(article.getTitle()).toBe('My First Article');
});
it('return correct article', function() {
- var article = summary.getByPath('article.md');
+ const article = summary.getByPath('article.md');
expect(article).toBeDefined();
expect(article.getTitle()).toBe('My Second Article');
});
it('return undefined if not found', function() {
- var article = summary.getByPath('NOT_EXISTING.md');
+ const article = summary.getByPath('NOT_EXISTING.md');
expect(article).toBeFalsy();
});
@@ -91,4 +91,3 @@ describe('Summary', function() {
});
});
-
diff --git a/lib/models/__tests__/summaryArticle.js b/packages/gitbook/src/models/__tests__/summaryArticle.js
index 22a7a20..506d481 100644
--- a/lib/models/__tests__/summaryArticle.js
+++ b/packages/gitbook/src/models/__tests__/summaryArticle.js
@@ -1,15 +1,15 @@
-var SummaryArticle = require('../summaryArticle');
-var File = require('../file');
+const SummaryArticle = require('../summaryArticle');
+const File = require('../file');
describe('SummaryArticle', function() {
describe('createChildLevel', function() {
it('must create the right level', function() {
- var article = SummaryArticle.create({}, '1.1');
+ const article = SummaryArticle.create({}, '1.1');
expect(article.createChildLevel()).toBe('1.1.1');
});
it('must create the right level when has articles', function() {
- var article = SummaryArticle.create({
+ const article = SummaryArticle.create({
articles: [
{
title: 'Test'
@@ -22,32 +22,31 @@ describe('SummaryArticle', function() {
describe('isFile', function() {
it('must return true when exactly the file', function() {
- var article = SummaryArticle.create({
+ const article = SummaryArticle.create({
ref: 'hello.md'
}, '1.1');
- var file = File.createWithFilepath('hello.md');
+ const file = File.createWithFilepath('hello.md');
expect(article.isFile(file)).toBe(true);
});
it('must return true when path is not normalized', function() {
- var article = SummaryArticle.create({
+ const article = SummaryArticle.create({
ref: '/hello.md'
}, '1.1');
- var file = File.createWithFilepath('hello.md');
+ const file = File.createWithFilepath('hello.md');
expect(article.isFile(file)).toBe(true);
});
it('must return false when has anchor', function() {
- var article = SummaryArticle.create({
+ const article = SummaryArticle.create({
ref: 'hello.md#world'
}, '1.1');
- var file = File.createWithFilepath('hello.md');
+ const file = File.createWithFilepath('hello.md');
expect(article.isFile(file)).toBe(false);
});
});
});
-
diff --git a/lib/models/__tests__/summaryPart.js b/packages/gitbook/src/models/__tests__/summaryPart.js
index 8ee50b6..fc9e8b5 100644
--- a/lib/models/__tests__/summaryPart.js
+++ b/packages/gitbook/src/models/__tests__/summaryPart.js
@@ -1,14 +1,14 @@
-var SummaryPart = require('../summaryPart');
+const SummaryPart = require('../summaryPart');
describe('SummaryPart', function() {
describe('createChildLevel', function() {
it('must create the right level', function() {
- var article = SummaryPart.create({}, '1');
+ const article = SummaryPart.create({}, '1');
expect(article.createChildLevel()).toBe('1.1');
});
it('must create the right level when has articles', function() {
- var article = SummaryPart.create({
+ const article = SummaryPart.create({
articles: [
{
title: 'Test'
@@ -20,4 +20,3 @@ describe('SummaryPart', function() {
});
});
-
diff --git a/packages/gitbook/src/models/__tests__/templateBlock.js b/packages/gitbook/src/models/__tests__/templateBlock.js
new file mode 100644
index 0000000..5db8a80
--- /dev/null
+++ b/packages/gitbook/src/models/__tests__/templateBlock.js
@@ -0,0 +1,218 @@
+const nunjucks = require('nunjucks');
+const Immutable = require('immutable');
+const Promise = require('../../utils/promise');
+
+describe('TemplateBlock', function() {
+ const TemplateBlock = require('../templateBlock');
+
+ describe('.create', function() {
+ it('must initialize a simple TemplateBlock from a function', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return { message: 'Hello World' };
+ });
+
+ expect(templateBlock.getName()).toBe('sayhello');
+ expect(templateBlock.getEndTag()).toBe('endsayhello');
+ expect(templateBlock.getBlocks().size).toBe(0);
+ expect(templateBlock.getExtensionName()).toBe('BlocksayhelloExtension');
+ });
+ });
+
+ describe('.toProps', function() {
+ it('must handle sync method', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return { message: 'Hello World' };
+ });
+
+ return templateBlock.toProps()
+ .then(function(props) {
+ expect(props).toEqual({ message: 'Hello World' });
+ });
+ });
+
+ it('must not fail if return a string', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return 'Hello World';
+ });
+
+ return templateBlock.toProps()
+ .then(function(props) {
+ expect(props).toEqual({ children: 'Hello World' });
+ });
+ });
+ });
+
+ describe('.getShortcuts', function() {
+ it('must return undefined if no shortcuts', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return { message: 'Hello World' };
+ });
+
+ expect(templateBlock.getShortcuts()).toNotExist();
+ });
+
+ it('.must return complete shortcut', function() {
+ const templateBlock = TemplateBlock.create('sayhello', {
+ process(block) {
+ return { message: 'Hello World' };
+ },
+ shortcuts: {
+ parsers: ['markdown'],
+ start: '$',
+ end: '-'
+ }
+ });
+
+ const shortcut = templateBlock.getShortcuts();
+
+ expect(shortcut).toBeDefined();
+ expect(shortcut.getStart()).toEqual('$');
+ expect(shortcut.getEnd()).toEqual('-');
+ expect(shortcut.getStartTag()).toEqual('sayhello');
+ expect(shortcut.getEndTag()).toEqual('endsayhello');
+ });
+ });
+
+ describe('.toNunjucksExt()', function() {
+ it('should render children correctly', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return 'Hello';
+ });
+
+ // Create a fresh Nunjucks environment
+ const env = new nunjucks.Environment(null, { autoescape: false });
+
+ // Add template block to environement
+ const Ext = templateBlock.toNunjucksExt();
+ env.addExtension(templateBlock.getExtensionName(), new Ext());
+
+ // Render a template using the block
+ const src = '{% sayhello %}{% endsayhello %}';
+ return Promise.nfcall(env.renderString.bind(env), src)
+ .then(function(res) {
+ expect(res).toBe('<xblock name="sayhello" props="{}">Hello</xblock>');
+ });
+ });
+
+ it('must handle HTML children', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return '<p>Hello, World!</p>';
+ });
+
+ // Create a fresh Nunjucks environment
+ const env = new nunjucks.Environment(null, { autoescape: false });
+
+ // Add template block to environement
+ const Ext = templateBlock.toNunjucksExt();
+ env.addExtension(templateBlock.getExtensionName(), new Ext());
+
+ // Render a template using the block
+ const src = '{% sayhello %}{% endsayhello %}';
+ return Promise.nfcall(env.renderString.bind(env), src)
+ .then(function(res) {
+ expect(res).toBe('<xblock name="sayhello" props="{}"><p>Hello, World!</p></xblock>');
+ });
+ });
+
+ it('must inline props without children', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return {
+ message: block.kwargs.tag + ' ' + block.kwargs.name
+ };
+ });
+
+ // Create a fresh Nunjucks environment
+ const env = new nunjucks.Environment(null, { autoescape: false });
+
+ // Add template block to environement
+ const Ext = templateBlock.toNunjucksExt();
+ env.addExtension(templateBlock.getExtensionName(), new Ext());
+
+ // Render a template using the block
+ const src = '{% sayhello name="Samy", tag="p" %}{% endsayhello %}';
+ return Promise.nfcall(env.renderString.bind(env), src)
+ .then(function(res) {
+ expect(res).toBe('<xblock name="sayhello" props="{&quot;message&quot;:&quot;p Samy&quot;}"></xblock>');
+ });
+ });
+
+ it('must accept an async function', function() {
+ const templateBlock = TemplateBlock.create('sayhello', function(block) {
+ return Promise()
+ .delay(1)
+ .then(function() {
+ return {
+ children: 'Hello ' + block.children
+ };
+ });
+ });
+
+ // Create a fresh Nunjucks environment
+ const env = new nunjucks.Environment(null, { autoescape: false });
+
+ // Add template block to environement
+ const Ext = templateBlock.toNunjucksExt();
+ env.addExtension(templateBlock.getExtensionName(), new Ext());
+
+ // Render a template using the block
+ const src = '{% sayhello %}Samy{% endsayhello %}';
+ return Promise.nfcall(env.renderString.bind(env), src)
+ .then(function(res) {
+ expect(res).toBe('<xblock name="sayhello" props="{}">Hello Samy</xblock>');
+ });
+ });
+
+ it('must handle nested blocks', function() {
+ const templateBlock = new TemplateBlock({
+ name: 'yoda',
+ blocks: Immutable.List(['start', 'end']),
+ process(block) {
+ const nested = {};
+
+ block.blocks.forEach(function(blk) {
+ nested[blk.name] = blk.children.trim();
+ });
+
+ return '<p class="yoda">' + nested.end + ' ' + nested.start + '</p>';
+ }
+ });
+
+ // Create a fresh Nunjucks environment
+ const env = new nunjucks.Environment(null, { autoescape: false });
+
+ // Add template block to environement
+ const Ext = templateBlock.toNunjucksExt();
+ env.addExtension(templateBlock.getExtensionName(), new Ext());
+
+ // Render a template using the block
+ const src = '{% yoda %}{% start %}this sentence should be{% end %}inverted{% endyoda %}';
+ return Promise.nfcall(env.renderString.bind(env), src)
+ .then(function(res) {
+ expect(res).toBe('<xblock name="yoda" props="{}"><p class="yoda">inverted this sentence should be</p></xblock>');
+ });
+ });
+
+ it('must handle multiple inline blocks', function() {
+ const templateBlock = new TemplateBlock({
+ name: 'math',
+ process(block) {
+ return '<math>' + block.children + '</math>';
+ }
+ });
+
+ // Create a fresh Nunjucks environment
+ const env = new nunjucks.Environment(null, { autoescape: false });
+
+ // Add template block to environement
+ const Ext = templateBlock.toNunjucksExt();
+ env.addExtension(templateBlock.getExtensionName(), new Ext());
+
+ // Render a template using the block after replacing shortcuts
+ const src = 'There should be two inline blocks as a result: {% math %}a = b{% endmath %} and {% math %}c = d{% endmath %}';
+ return Promise.nfcall(env.renderString.bind(env), src)
+ .then(function(res) {
+ expect(res).toBe('There should be two inline blocks as a result: <xblock name="math" props="{}"><math>a = b</math></xblock> and <xblock name="math" props="{}"><math>c = d</math></xblock>');
+ });
+ });
+ });
+});
diff --git a/lib/models/__tests__/templateEngine.js b/packages/gitbook/src/models/__tests__/templateEngine.js
index 6f18b18..30cd543 100644
--- a/lib/models/__tests__/templateEngine.js
+++ b/packages/gitbook/src/models/__tests__/templateEngine.js
@@ -1,40 +1,40 @@
describe('TemplateBlock', function() {
- var TemplateEngine = require('../templateEngine');
+ const TemplateEngine = require('../templateEngine');
describe('create', function() {
it('must initialize with a list of filters', function() {
- var engine = TemplateEngine.create({
+ const engine = TemplateEngine.create({
filters: {
- hello: function(name) {
+ hello(name) {
return 'Hello ' + name + '!';
}
}
});
- var env = engine.toNunjucks();
- var res = env.renderString('{{ "Luke"|hello }}');
+ const env = engine.toNunjucks();
+ const res = env.renderString('{{ "Luke"|hello }}');
expect(res).toBe('Hello Luke!');
});
it('must initialize with a list of globals', function() {
- var engine = TemplateEngine.create({
+ const engine = TemplateEngine.create({
globals: {
- hello: function(name) {
+ hello(name) {
return 'Hello ' + name + '!';
}
}
});
- var env = engine.toNunjucks();
- var res = env.renderString('{{ hello("Luke") }}');
+ const env = engine.toNunjucks();
+ const res = env.renderString('{{ hello("Luke") }}');
expect(res).toBe('Hello Luke!');
});
it('must pass context to filters and blocks', function() {
- var engine = TemplateEngine.create({
+ const engine = TemplateEngine.create({
filters: {
- hello: function(name) {
+ hello(name) {
return 'Hello ' + name + ' ' + this.lastName + '!';
}
},
@@ -42,10 +42,10 @@ describe('TemplateBlock', function() {
lastName: 'Skywalker'
}
});
- var env = engine.toNunjucks();
- var res = env.renderString('{{ "Luke"|hello }}');
+ const env = engine.toNunjucks();
+ const res = env.renderString('{{ "Luke"|hello }}');
expect(res).toBe('Hello Luke Skywalker!');
});
});
-}); \ No newline at end of file
+});
diff --git a/packages/gitbook/src/models/__tests__/uriIndex.js b/packages/gitbook/src/models/__tests__/uriIndex.js
new file mode 100644
index 0000000..f3be40b
--- /dev/null
+++ b/packages/gitbook/src/models/__tests__/uriIndex.js
@@ -0,0 +1,84 @@
+const URIIndex = require('../uriIndex');
+
+describe('URIIndex', () => {
+ let index;
+
+ before(() => {
+ index = new URIIndex({
+ 'README.md': 'index.html',
+ 'world.md': 'world.html',
+ 'hello/README.md': 'hello/index.html',
+ 'hello/test.md': 'hello/test.html'
+ });
+ });
+
+ describe('.resolve', () => {
+ it('should resolve a basic file path', () => {
+ expect(index.resolve('README.md')).toBe('index.html');
+ });
+
+ it('should resolve a nested file path', () => {
+ expect(index.resolve('hello/test.md')).toBe('hello/test.html');
+ });
+
+ it('should normalize path', () => {
+ expect(index.resolve('./hello//test.md')).toBe('hello/test.html');
+ });
+
+ it('should not fail for non existing entries', () => {
+ expect(index.resolve('notfound.md')).toBe('notfound.md');
+ });
+
+ it('should not fail for absolute url', () => {
+ expect(index.resolve('http://google.fr')).toBe('http://google.fr');
+ });
+
+ it('should preserve hash', () => {
+ expect(index.resolve('hello/test.md#myhash')).toBe('hello/test.html#myhash');
+ });
+ });
+
+ describe('.resolveToURL', () => {
+ it('should resolve a basic file path with directory index', () => {
+ expect(index.resolveToURL('README.md')).toBe('./');
+ });
+
+ it('should resolve a basic file path with directory index', () => {
+ expect(index.resolveToURL('hello/README.md')).toBe('hello/');
+ });
+ });
+
+ describe('.resolveFrom', () => {
+ it('should resolve correctly in same directory', () => {
+ expect(index.resolveFrom('README.md', 'world.md')).toBe('world.html');
+ });
+
+ it('should resolve correctly for a nested path', () => {
+ expect(index.resolveFrom('README.md', 'hello/README.md')).toBe('hello/index.html');
+ });
+
+ it('should resolve correctly for a nested path (2)', () => {
+ expect(index.resolveFrom('hello/README.md', 'test.md')).toBe('test.html');
+ });
+
+ it('should resolve correctly for a nested path (3)', () => {
+ expect(index.resolveFrom('hello/README.md', '../README.md')).toBe('../index.html');
+ });
+
+ it('should preserve hash', () => {
+ expect(index.resolveFrom('README.md', 'hello/README.md#myhash')).toBe('hello/index.html#myhash');
+ });
+
+ it('should not fail for absolute url', () => {
+ expect(index.resolveFrom('README.md', 'http://google.fr')).toBe('http://google.fr');
+ });
+ });
+
+ describe('.append', () => {
+ it('should normalize the filename', () => {
+ const newIndex = index.append('append//sometest.md', 'append/sometest.html');
+ expect(newIndex.resolve('append/sometest.md')).toBe('append/sometest.html');
+ });
+ });
+
+});
diff --git a/packages/gitbook/src/models/book.js b/packages/gitbook/src/models/book.js
new file mode 100644
index 0000000..4668154
--- /dev/null
+++ b/packages/gitbook/src/models/book.js
@@ -0,0 +1,357 @@
+const path = require('path');
+const { Record, OrderedMap } = require('immutable');
+
+const Logger = require('../utils/logger');
+
+const FS = require('./fs');
+const Config = require('./config');
+const Readme = require('./readme');
+const Summary = require('./summary');
+const Glossary = require('./glossary');
+const Languages = require('./languages');
+const Ignore = require('./ignore');
+
+const DEFAULTS = {
+ // Logger for output message
+ logger: new Logger(),
+ // Filesystem binded to the book scope to read files/directories
+ fs: new FS(),
+ // Ignore files parser
+ ignore: new Ignore(),
+ // Structure files
+ config: new Config(),
+ readme: new Readme(),
+ summary: new Summary(),
+ glossary: new Glossary(),
+ languages: new Languages(),
+ // ID of the language for language books
+ language: String(),
+ // List of children, if multilingual (String -> Book)
+ books: new OrderedMap()
+};
+
+class Book extends Record(DEFAULTS) {
+ getLogger() {
+ return this.get('logger');
+ }
+
+ getFS() {
+ return this.get('fs');
+ }
+
+ getIgnore() {
+ return this.get('ignore');
+ }
+
+ getConfig() {
+ return this.get('config');
+ }
+
+ getReadme() {
+ return this.get('readme');
+ }
+
+ getSummary() {
+ return this.get('summary');
+ }
+
+ getGlossary() {
+ return this.get('glossary');
+ }
+
+ getLanguages() {
+ return this.get('languages');
+ }
+
+ getBooks() {
+ return this.get('books');
+ }
+
+ getLanguage() {
+ return this.get('language');
+ }
+
+ /**
+ * Return FS instance to access the content
+ * @return {FS}
+ */
+ getContentFS() {
+ const fs = this.getFS();
+ const config = this.getConfig();
+ const rootFolder = config.getValue('root');
+
+ if (rootFolder) {
+ return FS.reduceScope(fs, rootFolder);
+ }
+
+ return fs;
+ }
+
+ /**
+ * Return root of the book
+ *
+ * @return {String}
+ */
+ getRoot() {
+ const fs = this.getFS();
+ return fs.getRoot();
+ }
+
+ /**
+ * Return root for content of the book
+ *
+ * @return {String}
+ */
+ getContentRoot() {
+ const fs = this.getContentFS();
+ return fs.getRoot();
+ }
+
+ /**
+ * Check if a file is ignore (should not being parsed, etc)
+ *
+ * @param {String} ref
+ * @return {Page|undefined}
+ */
+ isFileIgnored(filename) {
+ const ignore = this.getIgnore();
+ const language = this.getLanguage();
+
+ // Ignore is always relative to the root of the main book
+ if (language) {
+ filename = path.join(language, filename);
+ }
+
+ return ignore.isFileIgnored(filename);
+ }
+
+ /**
+ * Check if a content file is ignore (should not being parsed, etc)
+ *
+ * @param {String} ref
+ * @return {Page|undefined}
+ */
+ isContentFileIgnored(filename) {
+ const config = this.getConfig();
+ const rootFolder = config.getValue('root');
+
+ if (rootFolder) {
+ filename = path.join(rootFolder, filename);
+ }
+
+ return this.isFileIgnored(filename);
+ }
+
+ /**
+ * Return a page from a book by its path
+ *
+ * @param {String} ref
+ * @return {Page|undefined}
+ */
+ getPage(ref) {
+ return this.getPages().get(ref);
+ }
+
+ /**
+ * Is this book the parent of language's books
+ * @return {Boolean}
+ */
+ isMultilingual() {
+ return (this.getLanguages().getCount() > 0);
+ }
+
+ /**
+ * Return true if book is associated to a language
+ * @return {Boolean}
+ */
+ isLanguageBook() {
+ return Boolean(this.getLanguage());
+ }
+
+ /**
+ * Return a languages book
+ * @param {String} language
+ * @return {Book}
+ */
+ getLanguageBook(language) {
+ const books = this.getBooks();
+ return books.get(language);
+ }
+
+ /**
+ * Add a new language book
+ *
+ * @param {String} language
+ * @param {Book} book
+ * @return {Book}
+ */
+ addLanguageBook(language, book) {
+ let books = this.getBooks();
+ books = books.set(language, book);
+
+ return this.set('books', books);
+ }
+
+ /**
+ * Set the summary for this book
+ *
+ * @param {Summary}
+ * @return {Book}
+ */
+ setSummary(summary) {
+ return this.set('summary', summary);
+ }
+
+ /**
+ * Set the readme for this book
+ *
+ * @param {Readme}
+ * @return {Book}
+ */
+ setReadme(readme) {
+ return this.set('readme', readme);
+ }
+
+ /**
+ * Set the configuration for this book
+ *
+ * @param {Config}
+ * @return {Book}
+ */
+ setConfig(config) {
+ return this.set('config', config);
+ }
+
+ /**
+ * Set the ignore instance for this book
+ *
+ @param {Ignore}
+ * @return {Book}
+ */
+ setIgnore(ignore) {
+ return this.set('ignore', ignore);
+ }
+
+ /**
+ * Change log level
+ *
+ * @param {String} level
+ * @return {Book}
+ */
+ setLogLevel(level) {
+ this.getLogger().setLevel(level);
+ return this;
+ }
+
+ /**
+ * Infers the default extension for files
+ * @return {String}
+ */
+ getDefaultExt() {
+ // Inferring sources
+ const clues = [
+ this.getReadme(),
+ this.getSummary(),
+ this.getGlossary()
+ ];
+
+ // List their extensions
+ const exts = clues.map(function(clue) {
+ const file = clue.getFile();
+ if (file.exists()) {
+ return file.getParser().getExtensions().first();
+ } else {
+ return null;
+ }
+ });
+ // Adds the general default extension
+ exts.push('.md');
+
+ // Choose the first non null
+ return exts.find(function(e) { return e !== null; });
+ }
+
+ /**
+ * Infer the default path for a Readme.
+ * @param {Boolean} [absolute=false] False for a path relative to
+ * this book's content root
+ * @return {String}
+ */
+ getDefaultReadmePath(absolute) {
+ const defaultPath = 'README' + this.getDefaultExt();
+ if (absolute) {
+ return path.join(this.getContentRoot(), defaultPath);
+ } else {
+ return defaultPath;
+ }
+ }
+
+ /**
+ * Infer the default path for a Summary.
+ * @param {Boolean} [absolute=false] False for a path relative to
+ * this book's content root
+ * @return {String}
+ */
+ getDefaultSummaryPath(absolute) {
+ const defaultPath = 'SUMMARY' + this.getDefaultExt();
+ if (absolute) {
+ return path.join(this.getContentRoot(), defaultPath);
+ } else {
+ return defaultPath;
+ }
+ }
+
+ /**
+ * Infer the default path for a Glossary.
+ * @param {Boolean} [absolute=false] False for a path relative to
+ * this book's content root
+ * @return {String}
+ */
+ getDefaultGlossaryPath(absolute) {
+ const defaultPath = 'GLOSSARY' + this.getDefaultExt();
+ if (absolute) {
+ return path.join(this.getContentRoot(), defaultPath);
+ } else {
+ return defaultPath;
+ }
+ }
+
+ /**
+ * Create a language book from a parent
+ *
+ * @param {Book} parent
+ * @param {String} language
+ * @return {Book}
+ */
+ static createFromParent(parent, language) {
+ const ignore = parent.getIgnore();
+ let config = parent.getConfig();
+
+ // Set language in configuration
+ config = config.setValue('language', language);
+
+ return new Book({
+ // Inherits config. logegr and list of ignored files
+ logger: parent.getLogger(),
+ config,
+ ignore,
+
+ language,
+ fs: FS.reduceScope(parent.getContentFS(), language)
+ });
+ }
+
+ /**
+ * Create a book using a filesystem
+ *
+ * @param {FS} fs
+ * @return {Book}
+ */
+ static createForFS(fs) {
+ return new Book({
+ fs
+ });
+ }
+}
+
+module.exports = Book;
diff --git a/lib/models/config.js b/packages/gitbook/src/models/config.js
index 6de52f9..6a0be5e 100644
--- a/lib/models/config.js
+++ b/packages/gitbook/src/models/config.js
@@ -1,12 +1,12 @@
-var is = require('is');
-var Immutable = require('immutable');
+const is = require('is');
+const Immutable = require('immutable');
-var File = require('./file');
-var PluginDependency = require('./pluginDependency');
-var configDefault = require('../constants/configDefault');
-var reducedObject = require('../utils/reducedObject');
+const File = require('./file');
+const PluginDependency = require('./pluginDependency');
+const configDefault = require('../constants/configDefault');
+const reducedObject = require('../utils/reducedObject');
-var Config = Immutable.Record({
+const Config = Immutable.Record({
file: File(),
values: configDefault
}, 'Config');
@@ -51,7 +51,7 @@ Config.prototype.setFile = function(file) {
* @return {Mixed}
*/
Config.prototype.getValue = function(keyPath, def) {
- var values = this.getValues();
+ const values = this.getValues();
keyPath = Config.keyToKeyPath(keyPath);
if (!values.hasIn(keyPath)) {
@@ -72,7 +72,7 @@ Config.prototype.setValue = function(keyPath, value) {
value = Immutable.fromJS(value);
- var values = this.getValues();
+ let values = this.getValues();
values = values.setIn(keyPath, value);
return this.set('values', values);
@@ -83,7 +83,7 @@ Config.prototype.setValue = function(keyPath, value) {
* @return {List<PluginDependency>}
*/
Config.prototype.getPluginDependencies = function() {
- var plugins = this.getValue('plugins');
+ const plugins = this.getValue('plugins');
if (is.string(plugins)) {
return PluginDependency.listFromString(plugins);
@@ -98,7 +98,7 @@ Config.prototype.getPluginDependencies = function() {
* @return {PluginDependency}
*/
Config.prototype.getPluginDependency = function(name) {
- var plugins = this.getPluginDependencies();
+ const plugins = this.getPluginDependencies();
return plugins.find(function(dep) {
return dep.getName() === name;
@@ -111,7 +111,7 @@ Config.prototype.getPluginDependency = function(name) {
* @return {Config}
*/
Config.prototype.setPluginDependencies = function(deps) {
- var plugins = PluginDependency.listToArray(deps);
+ const plugins = PluginDependency.listToArray(deps);
return this.setValue('plugins', plugins);
};
@@ -135,7 +135,7 @@ Config.prototype.updateValues = function(values) {
* @returns {Config}
*/
Config.prototype.mergeValues = function(values) {
- var currentValues = this.getValues();
+ let currentValues = this.getValues();
values = Immutable.fromJS(values);
currentValues = currentValues.mergeDeep(values);
@@ -151,7 +151,7 @@ Config.prototype.mergeValues = function(values) {
*/
Config.create = function(file, values) {
return new Config({
- file: file,
+ file,
values: Immutable.fromJS(values)
});
};
diff --git a/lib/models/file.js b/packages/gitbook/src/models/file.js
index 8ddd4af..84828ce 100644
--- a/lib/models/file.js
+++ b/packages/gitbook/src/models/file.js
@@ -1,9 +1,9 @@
-var path = require('path');
-var Immutable = require('immutable');
+const path = require('path');
+const Immutable = require('immutable');
-var parsers = require('../parsers');
+const parsers = require('../parsers');
-var File = Immutable.Record({
+const File = Immutable.Record({
// Path of the file, relative to the FS
path: String(),
@@ -34,7 +34,7 @@ File.prototype.exists = function() {
@return {String}
*/
File.prototype.getType = function() {
- var parser = this.getParser();
+ const parser = this.getParser();
if (parser) {
return parser.getName();
} else {
diff --git a/lib/models/fs.js b/packages/gitbook/src/models/fs.js
index 16bd4ea..7afbfbd 100644
--- a/lib/models/fs.js
+++ b/packages/gitbook/src/models/fs.js
@@ -1,13 +1,13 @@
-var path = require('path');
-var Immutable = require('immutable');
-var stream = require('stream');
+const path = require('path');
+const Immutable = require('immutable');
+const stream = require('stream');
-var File = require('./file');
-var Promise = require('../utils/promise');
-var error = require('../utils/error');
-var PathUtil = require('../utils/path');
+const File = require('./file');
+const Promise = require('../utils/promise');
+const error = require('../utils/error');
+const PathUtil = require('../utils/path');
-var FS = Immutable.Record({
+const FS = Immutable.Record({
root: String(),
fsExists: Function(),
@@ -35,27 +35,25 @@ FS.prototype.getRoot = function() {
@return {Boolean}
*/
FS.prototype.isInScope = function(filename) {
- var rootPath = this.getRoot();
+ const rootPath = this.getRoot();
filename = path.join(rootPath, filename);
return PathUtil.isInRoot(rootPath, filename);
};
/**
- Resolve a file in this FS
-
- @param {String}
- @return {String}
-*/
-FS.prototype.resolve = function() {
- var rootPath = this.getRoot();
- var args = Array.prototype.slice.call(arguments);
- var filename = path.join.apply(path, [rootPath].concat(args));
+ * Resolve a file in this FS
+ * @param {String}
+ * @return {String}
+ */
+FS.prototype.resolve = function(...args) {
+ const rootPath = this.getRoot();
+ let filename = path.join(rootPath, ...args);
filename = path.normalize(filename);
if (!this.isInScope(filename)) {
throw error.FileOutOfScopeError({
- filename: filename,
+ filename,
root: this.root
});
}
@@ -64,47 +62,44 @@ FS.prototype.resolve = function() {
};
/**
- Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
-
- @param {String} filename
- @return {Promise<Boolean>}
-*/
+ * Check if a file exists, run a Promise(true) if that's the case, Promise(false) otherwise
+ * @param {String} filename
+ * @return {Promise<Boolean>}
+ */
FS.prototype.exists = function(filename) {
- var that = this;
+ const that = this;
return Promise()
.then(function() {
filename = that.resolve(filename);
- var exists = that.get('fsExists');
+ const exists = that.get('fsExists');
return exists(filename);
});
};
/**
- Read a file and returns a promise with the content as a buffer
-
- @param {String} filename
- @return {Promise<Buffer>}
-*/
+ * Read a file and returns a promise with the content as a buffer
+ * @param {String} filename
+ * @return {Promise<Buffer>}
+ */
FS.prototype.read = function(filename) {
- var that = this;
+ const that = this;
return Promise()
.then(function() {
filename = that.resolve(filename);
- var read = that.get('fsReadFile');
+ const read = that.get('fsReadFile');
return read(filename);
});
};
/**
- Read a file as a string (utf-8)
-
- @param {String} filename
- @return {Promise<String>}
-*/
+ * Read a file as a string (utf-8)
+ * @param {String} filename
+ * @return {Promise<String>}
+ */
FS.prototype.readAsString = function(filename, encoding) {
encoding = encoding || 'utf8';
@@ -115,15 +110,14 @@ FS.prototype.readAsString = function(filename, encoding) {
};
/**
- Read file as a stream
-
- @param {String} filename
- @return {Promise<Stream>}
-*/
+ * Read file as a stream
+ * @param {String} filename
+ * @return {Promise<Stream>}
+ */
FS.prototype.readAsStream = function(filename) {
- var that = this;
- var filepath = that.resolve(filename);
- var fsReadAsStream = this.get('fsReadAsStream');
+ const that = this;
+ const filepath = that.resolve(filename);
+ const fsReadAsStream = this.get('fsReadAsStream');
if (fsReadAsStream) {
return Promise(fsReadAsStream(filepath));
@@ -131,7 +125,7 @@ FS.prototype.readAsStream = function(filename) {
return this.read(filename)
.then(function(buf) {
- var bufferStream = new stream.PassThrough();
+ const bufferStream = new stream.PassThrough();
bufferStream.end(buf);
return bufferStream;
@@ -139,18 +133,17 @@ FS.prototype.readAsStream = function(filename) {
};
/**
- Read stat infos about a file
-
- @param {String} filename
- @return {Promise<File>}
-*/
+ * Read stat infos about a file
+ * @param {String} filename
+ * @return {Promise<File>}
+ */
FS.prototype.statFile = function(filename) {
- var that = this;
+ const that = this;
return Promise()
.then(function() {
- var filepath = that.resolve(filename);
- var stat = that.get('fsStatFile');
+ const filepath = that.resolve(filename);
+ const stat = that.get('fsStatFile');
return stat(filepath);
})
@@ -160,19 +153,19 @@ FS.prototype.statFile = function(filename) {
};
/**
- List files/directories in a directory.
- Directories ends with '/'
+ * List files/directories in a directory.
+ * Directories ends with '/'
- @param {String} dirname
- @return {Promise<List<String>>}
-*/
+ * @param {String} dirname
+ * @return {Promise<List<String>>}
+ */
FS.prototype.readDir = function(dirname) {
- var that = this;
+ const that = this;
return Promise()
.then(function() {
- var dirpath = that.resolve(dirname);
- var readDir = that.get('fsReadDir');
+ const dirpath = that.resolve(dirname);
+ const readDir = that.get('fsReadDir');
return readDir(dirpath);
})
@@ -182,12 +175,12 @@ FS.prototype.readDir = function(dirname) {
};
/**
- List only files in a diretcory
- Directories ends with '/'
-
- @param {String} dirname
- @return {Promise<List<String>>}
-*/
+ * List only files in a diretcory
+ * Directories ends with '/'
+ *
+ * @param {String} dirname
+ * @return {Promise<List<String>>}
+ */
FS.prototype.listFiles = function(dirname) {
return this.readDir(dirname)
.then(function(files) {
@@ -196,21 +189,21 @@ FS.prototype.listFiles = function(dirname) {
};
/**
- List all files in a directory
-
- @param {String} dirName
- @param {Function(dirName)} filterFn: call it for each file/directory to test if it should stop iterating
- @return {Promise<List<String>>}
-*/
+ * List all files in a directory
+ *
+ * @param {String} dirName
+ * @param {Function(dirName)} filterFn: call it for each file/directory to test if it should stop iterating
+ * @return {Promise<List<String>>}
+ */
FS.prototype.listAllFiles = function(dirName, filterFn) {
- var that = this;
+ const that = this;
dirName = dirName || '.';
return this.readDir(dirName)
.then(function(files) {
return Promise.reduce(files, function(out, file) {
- var isDirectory = pathIsFolder(file);
- var newDirName = path.join(dirName, file);
+ const isDirectory = pathIsFolder(file);
+ const newDirName = path.join(dirName, file);
if (filterFn && filterFn(newDirName) === false) {
return out;
@@ -229,13 +222,13 @@ FS.prototype.listAllFiles = function(dirName, filterFn) {
};
/**
- Find a file in a folder (case insensitive)
- Return the found filename
-
- @param {String} dirname
- @param {String} filename
- @return {Promise<String>}
-*/
+ * Find a file in a folder (case insensitive)
+ * Return the found filename
+ *
+ * @param {String} dirname
+ * @param {String} filename
+ * @return {Promise<String>}
+ */
FS.prototype.findFile = function(dirname, filename) {
return this.listFiles(dirname)
.then(function(files) {
@@ -246,20 +239,20 @@ FS.prototype.findFile = function(dirname, filename) {
};
/**
- Load a JSON file
- By default, fs only supports JSON
-
- @param {String} filename
- @return {Promise<Object>}
-*/
+ * Load a JSON file
+ * By default, fs only supports JSON
+ *
+ * @param {String} filename
+ * @return {Promise<Object>}
+ */
FS.prototype.loadAsObject = function(filename) {
- var that = this;
- var fsLoadObject = this.get('fsLoadObject');
+ const that = this;
+ const fsLoadObject = this.get('fsLoadObject');
return this.exists(filename)
.then(function(exists) {
if (!exists) {
- var err = new Error('Module doesn\'t exist');
+ const err = new Error('Module doesn\'t exist');
err.code = 'MODULE_NOT_FOUND';
throw err;
@@ -277,22 +270,22 @@ FS.prototype.loadAsObject = function(filename) {
};
/**
- Create a FS instance
-
- @param {Object} def
- @return {FS}
-*/
+ * Create a FS instance
+ *
+ * @param {Object} def
+ * @return {FS}
+ */
FS.create = function create(def) {
return new FS(def);
};
/**
- Create a new FS instance with a reduced scope
-
- @param {FS} fs
- @param {String} scope
- @return {FS}
-*/
+ * Create a new FS instance with a reduced scope
+ *
+ * @param {FS} fs
+ * @param {String} scope
+ * @return {FS}
+ */
FS.reduceScope = function reduceScope(fs, scope) {
return fs.set('root', path.join(fs.getRoot(), scope));
};
@@ -300,8 +293,8 @@ FS.reduceScope = function reduceScope(fs, scope) {
// .readdir return files/folder as a list of string, folder ending with '/'
function pathIsFolder(filename) {
- var lastChar = filename[filename.length - 1];
+ const lastChar = filename[filename.length - 1];
return lastChar == '/' || lastChar == '\\';
}
-module.exports = FS; \ No newline at end of file
+module.exports = FS;
diff --git a/lib/models/glossary.js b/packages/gitbook/src/models/glossary.js
index 0033248..e269b14 100644
--- a/lib/models/glossary.js
+++ b/packages/gitbook/src/models/glossary.js
@@ -1,11 +1,11 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var error = require('../utils/error');
-var File = require('./file');
-var GlossaryEntry = require('./glossaryEntry');
-var parsers = require('../parsers');
+const error = require('../utils/error');
+const File = require('./file');
+const GlossaryEntry = require('./glossaryEntry');
+const parsers = require('../parsers');
-var Glossary = Immutable.Record({
+const Glossary = Immutable.Record({
file: File(),
entries: Immutable.OrderedMap()
});
@@ -25,8 +25,8 @@ Glossary.prototype.getEntries = function() {
@return {GlossaryEntry}
*/
Glossary.prototype.getEntry = function(name) {
- var entries = this.getEntries();
- var id = GlossaryEntry.nameToID(name);
+ const entries = this.getEntries();
+ const id = GlossaryEntry.nameToID(name);
return entries.get(id);
};
@@ -37,10 +37,10 @@ Glossary.prototype.getEntry = function(name) {
@return {Promise<String>}
*/
Glossary.prototype.toText = function(parser) {
- var file = this.getFile();
- var entries = this.getEntries();
+ const file = this.getFile();
+ const entries = this.getEntries();
- parser = parser? parsers.getByExt(parser) : file.getParser();
+ parser = parser ? parsers.getByExt(parser) : file.getParser();
if (!parser) {
throw error.FileNotParsableError({
@@ -60,8 +60,8 @@ Glossary.prototype.toText = function(parser) {
@return {Glossary}
*/
Glossary.addEntry = function addEntry(glossary, entry) {
- var id = entry.getID();
- var entries = glossary.getEntries();
+ const id = entry.getID();
+ let entries = glossary.getEntries();
entries = entries.set(id, entry);
return glossary.set('entries', entries);
@@ -75,9 +75,9 @@ Glossary.addEntry = function addEntry(glossary, entry) {
@return {Glossary}
*/
Glossary.addEntryByName = function addEntryByName(glossary, name, description) {
- var entry = new GlossaryEntry({
- name: name,
- description: description
+ const entry = new GlossaryEntry({
+ name,
+ description
});
return Glossary.addEntry(glossary, entry);
@@ -100,7 +100,7 @@ Glossary.createFromEntries = function createFromEntries(file, entries) {
});
return new Glossary({
- file: file,
+ file,
entries: Immutable.OrderedMap(entries)
});
};
diff --git a/lib/models/glossaryEntry.js b/packages/gitbook/src/models/glossaryEntry.js
index 10791db..b36b276 100644
--- a/lib/models/glossaryEntry.js
+++ b/packages/gitbook/src/models/glossaryEntry.js
@@ -1,11 +1,11 @@
-var Immutable = require('immutable');
-var slug = require('github-slugid');
+const Immutable = require('immutable');
+const slug = require('github-slugid');
/*
A definition represents an entry in the glossary
*/
-var GlossaryEntry = Immutable.Record({
+const GlossaryEntry = Immutable.Record({
name: String(),
description: String()
});
diff --git a/packages/gitbook/src/models/ignore.js b/packages/gitbook/src/models/ignore.js
new file mode 100644
index 0000000..547f6b4
--- /dev/null
+++ b/packages/gitbook/src/models/ignore.js
@@ -0,0 +1,43 @@
+const { Record } = require('immutable');
+const IgnoreMutable = require('ignore');
+
+/*
+ Immutable version of node-ignore
+*/
+
+const DEFAULTS = {
+ ignore: new IgnoreMutable()
+};
+
+class Ignore extends Record(DEFAULTS) {
+ getIgnore() {
+ return this.get('ignore');
+ }
+
+ /**
+ * Test if a file is ignored by these rules.
+ * @param {String} filePath
+ * @return {Boolean} isIgnored
+ */
+ isFileIgnored(filename) {
+ const ignore = this.getIgnore();
+ return ignore.filter([filename]).length == 0;
+ }
+
+ /**
+ * Add rules.
+ * @param {String}
+ * @return {Ignore}
+ */
+ add(rule) {
+ const ignore = this.getIgnore();
+ const newIgnore = new IgnoreMutable();
+
+ newIgnore.add(ignore);
+ newIgnore.add(rule);
+
+ return this.set('ignore', newIgnore);
+ }
+}
+
+module.exports = Ignore;
diff --git a/lib/models/language.js b/packages/gitbook/src/models/language.js
index dcefbf6..1413091 100644
--- a/lib/models/language.js
+++ b/packages/gitbook/src/models/language.js
@@ -1,7 +1,7 @@
-var path = require('path');
-var Immutable = require('immutable');
+const path = require('path');
+const Immutable = require('immutable');
-var Language = Immutable.Record({
+const Language = Immutable.Record({
title: String(),
path: String()
});
diff --git a/lib/models/languages.js b/packages/gitbook/src/models/languages.js
index 42f05f9..9540546 100644
--- a/lib/models/languages.js
+++ b/packages/gitbook/src/models/languages.js
@@ -1,9 +1,9 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var File = require('./file');
-var Language = require('./language');
+const File = require('./file');
+const Language = require('./language');
-var Languages = Immutable.Record({
+const Languages = Immutable.Record({
file: File(),
list: Immutable.OrderedMap()
});
@@ -52,7 +52,7 @@ Languages.prototype.getCount = function() {
@return {Language}
*/
Languages.createFromList = function(file, langs) {
- var list = Immutable.OrderedMap();
+ let list = Immutable.OrderedMap();
langs.forEach(function(lang) {
lang = Language({
@@ -63,8 +63,8 @@ Languages.createFromList = function(file, langs) {
});
return Languages({
- file: file,
- list: list
+ file,
+ list
});
};
diff --git a/packages/gitbook/src/models/output.js b/packages/gitbook/src/models/output.js
new file mode 100644
index 0000000..a63be17
--- /dev/null
+++ b/packages/gitbook/src/models/output.js
@@ -0,0 +1,112 @@
+const { Record, OrderedMap, Map, List } = require('immutable');
+
+const Git = require('../utils/git');
+const LocationUtils = require('../utils/location');
+const Book = require('./book');
+const URIIndex = require('./uriIndex');
+
+const DEFAULTS = {
+ book: new Book(),
+ // Name of the generator being used
+ generator: String(),
+ // Map of plugins to use (String -> Plugin)
+ plugins: OrderedMap(),
+ // Map pages to generation (String -> Page)
+ pages: OrderedMap(),
+ // List of file that are not pages in the book (String)
+ assets: List(),
+ // Option for the generation
+ options: Map(),
+ // Internal state for the generation
+ state: Map(),
+ // Index of urls
+ urls: new URIIndex(),
+ // Git repositories manager
+ git: new Git()
+};
+
+class Output extends Record(DEFAULTS) {
+ getBook() {
+ return this.get('book');
+ }
+
+ getGenerator() {
+ return this.get('generator');
+ }
+
+ getPlugins() {
+ return this.get('plugins');
+ }
+
+ getPages() {
+ return this.get('pages');
+ }
+
+ getOptions() {
+ return this.get('options');
+ }
+
+ getAssets() {
+ return this.get('assets');
+ }
+
+ getState() {
+ return this.get('state');
+ }
+
+ getURLIndex() {
+ return this.get('urls');
+ }
+
+ /**
+ * Return a page byt its file path
+ *
+ * @param {String} filePath
+ * @return {Page|undefined}
+ */
+ getPage(filePath) {
+ filePath = LocationUtils.normalize(filePath);
+
+ const pages = this.getPages();
+ return pages.get(filePath);
+ }
+
+ /**
+ * Get root folder for output.
+ * @return {String}
+ */
+ getRoot() {
+ return this.getOptions().get('root');
+ }
+
+ /**
+ * Update state of output
+ *
+ * @param {Map} newState
+ * @return {Output}
+ */
+ setState(newState) {
+ return this.set('state', newState);
+ }
+
+ /**
+ * Update options
+ *
+ * @param {Map} newOptions
+ * @return {Output}
+ */
+ setOptions(newOptions) {
+ return this.set('options', newOptions);
+ }
+
+ /**
+ * Return logegr for this output (same as book)
+ *
+ * @return {Logger}
+ */
+ getLogger() {
+ return this.getBook().getLogger();
+ }
+}
+
+module.exports = Output;
diff --git a/packages/gitbook/src/models/page.js b/packages/gitbook/src/models/page.js
new file mode 100644
index 0000000..e2ab977
--- /dev/null
+++ b/packages/gitbook/src/models/page.js
@@ -0,0 +1,69 @@
+const { Record, Map } = require('immutable');
+const yaml = require('js-yaml');
+
+const File = require('./file');
+
+const DEFAULTS = {
+ file: File(),
+ // Attributes extracted from the YAML header
+ attributes: Map(),
+ // Content of the page
+ content: String(),
+ // Direction of the text
+ dir: String('ltr')
+};
+
+class Page extends Record(DEFAULTS) {
+ getFile() {
+ return this.get('file');
+ }
+
+ getAttributes() {
+ return this.get('attributes');
+ }
+
+ getContent() {
+ return this.get('content');
+ }
+
+ getDir() {
+ return this.get('dir');
+ }
+
+ /**
+ * Return page as text
+ * @return {String}
+ */
+ toText() {
+ const attrs = this.getAttributes();
+ const content = this.getContent();
+
+ if (attrs.size === 0) {
+ return content;
+ }
+
+ const frontMatter = '---\n' + yaml.safeDump(attrs.toJS(), { skipInvalid: true }) + '---\n\n';
+ return (frontMatter + content);
+ }
+
+ /**
+ * Return path of the page
+ * @return {String}
+ */
+ getPath() {
+ return this.getFile().getPath();
+ }
+
+ /**
+ * Create a page for a file
+ * @param {File} file
+ * @return {Page}
+ */
+ static createForFile(file) {
+ return new Page({
+ file
+ });
+ }
+}
+
+module.exports = Page;
diff --git a/lib/models/parser.js b/packages/gitbook/src/models/parser.js
index d64542f..3769dd3 100644
--- a/lib/models/parser.js
+++ b/packages/gitbook/src/models/parser.js
@@ -1,7 +1,7 @@
-var Immutable = require('immutable');
-var Promise = require('../utils/promise');
+const Immutable = require('immutable');
+const Promise = require('../utils/promise');
-var Parser = Immutable.Record({
+const Parser = Immutable.Record({
name: String(),
// List of extensions that can be processed using this parser
@@ -27,22 +27,22 @@ Parser.prototype.getExtensions = function() {
// PARSE
Parser.prototype.parseReadme = function(content) {
- var readme = this.get('readme');
+ const readme = this.get('readme');
return Promise(readme(content));
};
Parser.prototype.parseSummary = function(content) {
- var summary = this.get('summary');
+ const summary = this.get('summary');
return Promise(summary(content));
};
Parser.prototype.parseGlossary = function(content) {
- var glossary = this.get('glossary');
+ const glossary = this.get('glossary');
return Promise(glossary(content));
};
Parser.prototype.preparePage = function(content) {
- var page = this.get('page');
+ const page = this.get('page');
if (!page.prepare) {
return Promise(content);
}
@@ -51,39 +51,39 @@ Parser.prototype.preparePage = function(content) {
};
Parser.prototype.parsePage = function(content) {
- var page = this.get('page');
+ const page = this.get('page');
return Promise(page(content));
};
Parser.prototype.parseInline = function(content) {
- var inline = this.get('inline');
+ const inline = this.get('inline');
return Promise(inline(content));
};
Parser.prototype.parseLanguages = function(content) {
- var langs = this.get('langs');
+ const langs = this.get('langs');
return Promise(langs(content));
};
Parser.prototype.parseInline = function(content) {
- var inline = this.get('inline');
+ const inline = this.get('inline');
return Promise(inline(content));
};
// TO TEXT
Parser.prototype.renderLanguages = function(content) {
- var langs = this.get('langs');
+ const langs = this.get('langs');
return Promise(langs.toText(content));
};
Parser.prototype.renderSummary = function(content) {
- var summary = this.get('summary');
+ const summary = this.get('summary');
return Promise(summary.toText(content));
};
Parser.prototype.renderGlossary = function(content) {
- var glossary = this.get('glossary');
+ const glossary = this.get('glossary');
return Promise(glossary.toText(content));
};
@@ -94,7 +94,7 @@ Parser.prototype.renderGlossary = function(content) {
@return {Boolean}
*/
Parser.prototype.matchExtension = function(ext) {
- var exts = this.getExtensions();
+ const exts = this.getExtensions();
return exts.includes(ext.toLowerCase());
};
@@ -108,7 +108,7 @@ Parser.prototype.matchExtension = function(ext) {
*/
Parser.create = function(name, extensions, module) {
return new Parser({
- name: name,
+ name,
extensions: Immutable.List(extensions),
readme: module.readme,
langs: module.langs,
diff --git a/packages/gitbook/src/models/plugin.js b/packages/gitbook/src/models/plugin.js
new file mode 100644
index 0000000..f2491f2
--- /dev/null
+++ b/packages/gitbook/src/models/plugin.js
@@ -0,0 +1,149 @@
+const { Record, Map } = require('immutable');
+
+const TemplateBlock = require('./templateBlock');
+const PluginDependency = require('./pluginDependency');
+const THEME_PREFIX = require('../constants/themePrefix');
+
+const DEFAULT_VERSION = '*';
+
+const DEFAULTS = {
+ name: String(),
+ // Requirement version (ex: ">1.0.0")
+ version: String(DEFAULT_VERSION),
+ // Path to load this plugin
+ path: String(),
+ // Depth of this plugin in the dependency tree
+ depth: Number(0),
+ // Parent depending on this plugin
+ parent: String(),
+ // Content of the "package.json"
+ package: Map(),
+ // Content of the package itself
+ content: Map()
+};
+
+class Plugin extends Record(DEFAULTS) {
+ getName() {
+ return this.get('name');
+ }
+
+ getPath() {
+ return this.get('path');
+ }
+
+ getVersion() {
+ return this.get('version');
+ }
+
+ getPackage() {
+ return this.get('package');
+ }
+
+ getContent() {
+ return this.get('content');
+ }
+
+ getDepth() {
+ return this.get('depth');
+ }
+
+ getParent() {
+ return this.get('parent');
+ }
+
+ /**
+ * Return the ID on NPM for this plugin
+ * @return {String}
+ */
+ getNpmID() {
+ return PluginDependency.nameToNpmID(this.getName());
+ }
+
+ /**
+ * Check if a plugin is loaded
+ * @return {Boolean}
+ */
+ isLoaded() {
+ return Boolean(this.getPackage().size > 0);
+ }
+
+ /**
+ * Check if a plugin is a theme given its name
+ * @return {Boolean}
+ */
+ isTheme() {
+ const name = this.getName();
+ return (name && name.indexOf(THEME_PREFIX) === 0);
+ }
+
+ /**
+ * Return map of hooks
+ * @return {Map<String:Function>}
+ */
+ getHooks() {
+ return this.getContent().get('hooks') || Map();
+ }
+
+ /**
+ * Return map of filters
+ * @return {Map<String:Function>}
+ */
+ getFilters() {
+ return this.getContent().get('filters');
+ }
+
+ /**
+ * Return map of blocks
+ * @return {Map<String:TemplateBlock>}
+ */
+ getBlocks() {
+ let blocks = this.getContent().get('blocks');
+ blocks = blocks || Map();
+
+ return blocks
+ .map(function(block, blockName) {
+ return TemplateBlock.create(blockName, block);
+ });
+ }
+
+ /**
+ * Return a specific hook
+ * @param {String} name
+ * @return {Function|undefined}
+ */
+ getHook(name) {
+ return this.getHooks().get(name);
+ }
+
+ /**
+ * Create a plugin from a string
+ * @param {String}
+ * @return {Plugin}
+ */
+ static createFromString(s) {
+ const parts = s.split('@');
+ const name = parts[0];
+ const version = parts.slice(1).join('@');
+
+ return new Plugin({
+ name,
+ version: version || DEFAULT_VERSION
+ });
+ }
+
+ /**
+ * Create a plugin from a dependency
+ * @param {PluginDependency}
+ * @return {Plugin}
+ */
+ static createFromDep(dep) {
+ return new Plugin({
+ name: dep.getName(),
+ version: dep.getVersion()
+ });
+ }
+}
+
+Plugin.nameToNpmID = PluginDependency.nameToNpmID;
+
+module.exports = Plugin;
diff --git a/lib/models/pluginDependency.js b/packages/gitbook/src/models/pluginDependency.js
index 8866294..4e5d464 100644
--- a/lib/models/pluginDependency.js
+++ b/packages/gitbook/src/models/pluginDependency.js
@@ -1,15 +1,15 @@
-var is = require('is');
-var semver = require('semver');
-var Immutable = require('immutable');
+const is = require('is');
+const semver = require('semver');
+const Immutable = require('immutable');
-var PREFIX = require('../constants/pluginPrefix');
-var DEFAULT_VERSION = '*';
+const PREFIX = require('../constants/pluginPrefix');
+const DEFAULT_VERSION = '*';
/*
* PluginDependency represents the informations about a plugin
* stored in config.plugins
*/
-var PluginDependency = Immutable.Record({
+const PluginDependency = Immutable.Record({
name: String(),
// Requirement version (ex: ">1.0.0")
@@ -71,7 +71,7 @@ PluginDependency.create = function(name, version, enabled) {
}
return new PluginDependency({
- name: name,
+ name,
version: version || DEFAULT_VERSION,
enabled: Boolean(enabled)
});
@@ -83,10 +83,10 @@ PluginDependency.create = function(name, version, enabled) {
* @return {Plugin|undefined}
*/
PluginDependency.createFromString = function(s) {
- var parts = s.split('@');
- var name = parts[0];
- var version = parts.slice(1).join('@');
- var enabled = true;
+ const parts = s.split('@');
+ let name = parts[0];
+ const version = parts.slice(1).join('@');
+ let enabled = true;
if (name[0] === '-') {
enabled = false;
@@ -94,9 +94,9 @@ PluginDependency.createFromString = function(s) {
}
return new PluginDependency({
- name: name,
+ name,
version: version || DEFAULT_VERSION,
- enabled: enabled
+ enabled
});
};
@@ -106,7 +106,7 @@ PluginDependency.createFromString = function(s) {
* @return {List<PluginDependency>}
*/
PluginDependency.listFromString = function(s) {
- var parts = s.split(',');
+ const parts = s.split(',');
return PluginDependency.listFromArray(parts);
};
@@ -140,7 +140,7 @@ PluginDependency.listFromArray = function(arr) {
PluginDependency.listToArray = function(list) {
return list
.map(function(dep) {
- var result = '';
+ let result = '';
if (!dep.isEnabled()) {
result += '-';
diff --git a/lib/models/readme.js b/packages/gitbook/src/models/readme.js
index c655c82..0fb52b4 100644
--- a/lib/models/readme.js
+++ b/packages/gitbook/src/models/readme.js
@@ -1,8 +1,8 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var File = require('./file');
+const File = require('./file');
-var Readme = Immutable.Record({
+const Readme = Immutable.Record({
file: File(),
title: String(),
description: String()
@@ -31,7 +31,7 @@ Readme.create = function(file, def) {
def = def || {};
return new Readme({
- file: file,
+ file,
title: def.title || '',
description: def.description || ''
});
diff --git a/lib/models/summary.js b/packages/gitbook/src/models/summary.js
index 70f0535..edc202e 100644
--- a/lib/models/summary.js
+++ b/packages/gitbook/src/models/summary.js
@@ -1,14 +1,14 @@
-var is = require('is');
-var Immutable = require('immutable');
+const is = require('is');
+const Immutable = require('immutable');
-var error = require('../utils/error');
-var LocationUtils = require('../utils/location');
-var File = require('./file');
-var SummaryPart = require('./summaryPart');
-var SummaryArticle = require('./summaryArticle');
-var parsers = require('../parsers');
+const error = require('../utils/error');
+const LocationUtils = require('../utils/location');
+const File = require('./file');
+const SummaryPart = require('./summaryPart');
+const SummaryArticle = require('./summaryArticle');
+const parsers = require('../parsers');
-var Summary = Immutable.Record({
+const Summary = Immutable.Record({
file: File(),
parts: Immutable.List()
}, 'Summary');
@@ -28,7 +28,7 @@ Summary.prototype.getParts = function() {
@return {Part}
*/
Summary.prototype.getPart = function(i) {
- var parts = this.getParts();
+ const parts = this.getParts();
return parts.get(i);
};
@@ -41,7 +41,7 @@ Summary.prototype.getPart = function(i) {
@return {Article|Part}
*/
Summary.prototype.getArticle = function(iter, partIter) {
- var parts = this.getParts();
+ const parts = this.getParts();
return parts.reduce(function(result, part) {
if (result) return result;
@@ -74,7 +74,7 @@ Summary.prototype.getByLevel = function(level) {
*/
Summary.prototype.getByPath = function(filePath) {
return this.getArticle(function(article) {
- var articlePath = article.getPath();
+ const articlePath = article.getPath();
return (
articlePath &&
@@ -101,8 +101,8 @@ Summary.prototype.getFirstArticle = function() {
@return {Article}
*/
Summary.prototype.getNextArticle = function(current) {
- var level = is.string(current)? current : current.getLevel();
- var wasPrev = false;
+ const level = is.string(current) ? current : current.getLevel();
+ let wasPrev = false;
return this.getArticle(function(article) {
if (wasPrev) return true;
@@ -119,8 +119,8 @@ Summary.prototype.getNextArticle = function(current) {
@return {Article}
*/
Summary.prototype.getPrevArticle = function(current) {
- var level = is.string(current)? current : current.getLevel();
- var prev = undefined;
+ const level = is.string(current) ? current : current.getLevel();
+ let prev = undefined;
this.getArticle(function(article) {
if (article.getLevel() == level) {
@@ -140,18 +140,18 @@ Summary.prototype.getPrevArticle = function(current) {
@param {String|Article} current
@return {Article|Part|Null}
*/
-Summary.prototype.getParent = function (level) {
+Summary.prototype.getParent = function(level) {
// Coerce to level
- level = is.string(level)? level : level.getLevel();
+ level = is.string(level) ? level : level.getLevel();
// Get parent level
- var parentLevel = getParentLevel(level);
+ const parentLevel = getParentLevel(level);
if (!parentLevel) {
return null;
}
// Get parent of the position
- var parentArticle = this.getByLevel(parentLevel);
+ const parentArticle = this.getByLevel(parentLevel);
return parentArticle || null;
};
@@ -162,10 +162,10 @@ Summary.prototype.getParent = function (level) {
@return {Promise<String>}
*/
Summary.prototype.toText = function(parseExt) {
- var file = this.getFile();
- var parts = this.getParts();
+ const file = this.getFile();
+ const parts = this.getParts();
- var parser = parseExt? parsers.getByExt(parseExt) : file.getParser();
+ const parser = parseExt ? parsers.getByExt(parseExt) : file.getParser();
if (!parser) {
throw error.FileNotParsableError({
@@ -184,7 +184,7 @@ Summary.prototype.toText = function(parseExt) {
@return {List<Article>}
*/
Summary.prototype.getArticlesAsList = function() {
- var accu = [];
+ const accu = [];
this.getArticle(function(article) {
accu.push(article);
@@ -209,7 +209,7 @@ Summary.createFromParts = function createFromParts(file, parts) {
});
return new Summary({
- file: file,
+ file,
parts: new Immutable.List(parts)
});
};
@@ -221,7 +221,7 @@ Summary.createFromParts = function createFromParts(file, parts) {
@return {String}
*/
function getParentLevel(level) {
- var parts = level.split('.');
+ const parts = level.split('.');
return parts.slice(0, -1).join('.');
}
diff --git a/lib/models/summaryArticle.js b/packages/gitbook/src/models/summaryArticle.js
index 6da8d1d..919e6b9 100644
--- a/lib/models/summaryArticle.js
+++ b/packages/gitbook/src/models/summaryArticle.js
@@ -1,12 +1,12 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var location = require('../utils/location');
+const location = require('../utils/location');
/*
An article represents an entry in the Summary / table of Contents
*/
-var SummaryArticle = Immutable.Record({
+const SummaryArticle = Immutable.Record({
level: String(),
title: String(),
ref: String(),
@@ -50,14 +50,14 @@ SummaryArticle.prototype.getPath = function() {
return undefined;
}
- var ref = this.getRef();
+ const ref = this.getRef();
if (!ref) {
return undefined;
}
- var parts = ref.split('#');
+ const parts = ref.split('#');
- var pathname = (parts.length > 1? parts.slice(0, -1).join('#') : ref);
+ const pathname = (parts.length > 1 ? parts.slice(0, -1).join('#') : ref);
// Normalize path to remove ('./', '/...', etc)
return location.flatten(pathname);
@@ -69,7 +69,7 @@ SummaryArticle.prototype.getPath = function() {
* @return {String}
*/
SummaryArticle.prototype.getUrl = function() {
- return this.isExternal()? this.getRef() : undefined;
+ return this.isExternal() ? this.getRef() : undefined;
};
/**
@@ -78,10 +78,10 @@ SummaryArticle.prototype.getUrl = function() {
* @return {String}
*/
SummaryArticle.prototype.getAnchor = function() {
- var ref = this.getRef();
- var parts = ref.split('#');
+ const ref = this.getRef();
+ const parts = ref.split('#');
- var anchor = (parts.length > 1? '#' + parts[parts.length - 1] : undefined);
+ const anchor = (parts.length > 1 ? '#' + parts[parts.length - 1] : undefined);
return anchor;
};
@@ -91,9 +91,9 @@ SummaryArticle.prototype.getAnchor = function() {
* @return {String}
*/
SummaryArticle.prototype.createChildLevel = function() {
- var level = this.getLevel();
- var subArticles = this.getArticles();
- var childLevel = level + '.' + (subArticles.size + 1);
+ const level = this.getLevel();
+ const subArticles = this.getArticles();
+ const childLevel = level + '.' + (subArticles.size + 1);
return childLevel;
};
@@ -127,8 +127,8 @@ SummaryArticle.prototype.isFile = function(file) {
* @return {Boolean}
*/
SummaryArticle.prototype.isReadme = function(book) {
- var readme = book.getFile? book : book.getReadme();
- var file = readme.getFile();
+ const readme = book.getFile ? book : book.getReadme();
+ const file = readme.getFile();
return this.isFile(file);
};
@@ -149,7 +149,7 @@ SummaryArticle.prototype.isExternal = function() {
* @return {SummaryArticle}
*/
SummaryArticle.create = function(def, level) {
- var articles = (def.articles || []).map(function(article, i) {
+ const articles = (def.articles || []).map(function(article, i) {
if (article instanceof SummaryArticle) {
return article;
}
@@ -157,7 +157,7 @@ SummaryArticle.create = function(def, level) {
});
return new SummaryArticle({
- level: level,
+ level,
title: def.title,
ref: def.ref || def.path || '',
articles: Immutable.List(articles)
@@ -172,7 +172,7 @@ SummaryArticle.create = function(def, level) {
* @return {Article}
*/
SummaryArticle.findArticle = function(base, iter) {
- var articles = base.getArticles();
+ const articles = base.getArticles();
return articles.reduce(function(result, article) {
if (result) return result;
diff --git a/lib/models/summaryPart.js b/packages/gitbook/src/models/summaryPart.js
index f0e6f57..0bb5369 100644
--- a/lib/models/summaryPart.js
+++ b/packages/gitbook/src/models/summaryPart.js
@@ -1,12 +1,12 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var SummaryArticle = require('./summaryArticle');
+const SummaryArticle = require('./summaryArticle');
/*
A part represents a section in the Summary / table of Contents
*/
-var SummaryPart = Immutable.Record({
+const SummaryPart = Immutable.Record({
level: String(),
title: String(),
articles: Immutable.List()
@@ -30,9 +30,9 @@ SummaryPart.prototype.getArticles = function() {
* @return {String}
*/
SummaryPart.prototype.createChildLevel = function() {
- var level = this.getLevel();
- var subArticles = this.getArticles();
- var childLevel = level + '.' + (subArticles.size + 1);
+ const level = this.getLevel();
+ const subArticles = this.getArticles();
+ const childLevel = level + '.' + (subArticles.size + 1);
return childLevel;
};
@@ -44,7 +44,7 @@ SummaryPart.prototype.createChildLevel = function() {
* @return {SummaryPart}
*/
SummaryPart.create = function(def, level) {
- var articles = (def.articles || []).map(function(article, i) {
+ const articles = (def.articles || []).map(function(article, i) {
if (article instanceof SummaryArticle) {
return article;
}
diff --git a/packages/gitbook/src/models/templateBlock.js b/packages/gitbook/src/models/templateBlock.js
new file mode 100644
index 0000000..61c006f
--- /dev/null
+++ b/packages/gitbook/src/models/templateBlock.js
@@ -0,0 +1,253 @@
+const is = require('is');
+const extend = require('extend');
+const { Record, List, Map } = require('immutable');
+const escape = require('escape-html');
+
+const Promise = require('../utils/promise');
+const TemplateShortcut = require('./templateShortcut');
+
+const NODE_ENDARGS = '%%endargs%%';
+const HTML_TAGNAME = 'xblock';
+
+const DEFAULTS = {
+ // Name of block, also the start tag
+ name: String(),
+ // End tag, default to "end<name>"
+ end: String(),
+ // Function to process the block content
+ process: Function(),
+ // List of String, for inner block tags
+ blocks: List(),
+ // List of shortcuts to replace with this block
+ shortcuts: Map()
+};
+
+class TemplateBlock extends Record(DEFAULTS) {
+ getName() {
+ return this.get('name');
+ }
+
+ getEndTag() {
+ return this.get('end') || ('end' + this.getName());
+ }
+
+ getProcess() {
+ return this.get('process');
+ }
+
+ getBlocks() {
+ return this.get('blocks');
+ }
+
+
+ /**
+ * Return shortcuts associated with this block or undefined
+ * @return {TemplateShortcut|undefined}
+ */
+ getShortcuts() {
+ const shortcuts = this.get('shortcuts');
+ if (shortcuts.size === 0) {
+ return undefined;
+ }
+
+ return TemplateShortcut.createForBlock(this, shortcuts);
+ }
+
+ /**
+ * Return name for the nunjucks extension
+ * @return {String}
+ */
+ getExtensionName() {
+ return 'Block' + this.getName() + 'Extension';
+ }
+
+ /**
+ * Return a nunjucks extension to represents this block
+ * @return {Nunjucks.Extension}
+ */
+ toNunjucksExt(mainContext = {}) {
+ const that = this;
+ const name = this.getName();
+ const endTag = this.getEndTag();
+ const blocks = this.getBlocks().toJS();
+
+ function Ext() {
+ this.tags = [name];
+
+ this.parse = (parser, nodes) => {
+ let lastBlockName = null;
+ let lastBlockArgs = null;
+ const allBlocks = blocks.concat([endTag]);
+
+ // Parse first block
+ const tok = parser.nextToken();
+ lastBlockArgs = parser.parseSignature(null, true);
+ parser.advanceAfterBlockEnd(tok.value);
+
+ const args = new nodes.NodeList();
+ const bodies = [];
+ const blockNamesNode = new nodes.Array(tok.lineno, tok.colno);
+ const blockArgCounts = new nodes.Array(tok.lineno, tok.colno);
+
+ // Parse while we found "end<block>"
+ do {
+ // Read body
+ const currentBody = parser.parseUntilBlocks(...allBlocks);
+
+ // Handle body with previous block name and args
+ blockNamesNode.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockName));
+ blockArgCounts.addChild(new nodes.Literal(args.lineno, args.colno, lastBlockArgs.children.length));
+ bodies.push(currentBody);
+
+ // Append arguments of this block as arguments of the run function
+ lastBlockArgs.children.forEach(function(child) {
+ args.addChild(child);
+ });
+
+ // Read new block
+ lastBlockName = parser.nextToken().value;
+
+ // Parse signature and move to the end of the block
+ if (lastBlockName != endTag) {
+ lastBlockArgs = parser.parseSignature(null, true);
+ }
+
+ parser.advanceAfterBlockEnd(lastBlockName);
+ } while (lastBlockName != endTag);
+
+ args.addChild(blockNamesNode);
+ args.addChild(blockArgCounts);
+ args.addChild(new nodes.Literal(args.lineno, args.colno, NODE_ENDARGS));
+
+ return new nodes.CallExtensionAsync(this, 'run', args, bodies);
+ };
+
+ this.run = (context, ...fnArgs) => {
+ let args;
+ const blocks = [];
+ let bodies = [];
+
+ // Extract callback
+ const callback = fnArgs.pop();
+
+ // Detect end of arguments
+ const endArgIndex = fnArgs.indexOf(NODE_ENDARGS);
+
+ // Extract arguments and bodies
+ args = fnArgs.slice(0, endArgIndex);
+ bodies = fnArgs.slice(endArgIndex + 1);
+
+ // Extract block counts
+ const blockArgCounts = args.pop();
+ const blockNames = args.pop();
+
+ // Recreate list of blocks
+ blockNames.forEach((blkName, i) => {
+ const countArgs = blockArgCounts[i];
+ const blockBody = bodies.shift();
+
+ const blockArgs = countArgs > 0 ? args.slice(0, countArgs) : [];
+ args = args.slice(countArgs);
+ const blockKwargs = extractKwargs(blockArgs);
+
+ blocks.push({
+ name: blkName,
+ children: blockBody(),
+ args: blockArgs,
+ kwargs: blockKwargs
+ });
+ });
+
+ const mainBlock = blocks.shift();
+ mainBlock.blocks = blocks;
+
+ Promise()
+ .then(function() {
+ const ctx = extend({
+ ctx: context
+ }, mainContext);
+
+ return that.toProps(mainBlock, ctx);
+ })
+ .then(function(props) {
+ return that.toHTML(props);
+ })
+ .nodeify(callback);
+ };
+ }
+
+ return Ext;
+ }
+
+ /**
+ * Apply a block an return the props
+ *
+ * @param {Object} inner
+ * @param {Object} context
+ * @return {Promise<Props>}
+ */
+ toProps(inner, context) {
+ const processFn = this.getProcess();
+
+ inner = inner || {};
+ inner.args = inner.args || [];
+ inner.kwargs = inner.kwargs || {};
+ inner.blocks = inner.blocks || [];
+
+ return Promise()
+ .then(() => processFn.call(context, inner))
+ .then(props => {
+ if (is.string(props)) {
+ return { children: props };
+ }
+
+ return props;
+ });
+ }
+
+ /**
+ * Convert a block props to HTML. This HTML is then being
+ * parsed by gitbook-core during rendering, and binded to the right react components.
+ *
+ * @param {Object} props
+ * @return {String}
+ */
+ toHTML(props) {
+ const { children, ...innerProps } = props;
+ const payload = escape(JSON.stringify(innerProps));
+
+ return (
+ `<${HTML_TAGNAME} name="${this.name}" props="${payload}">${children || ''}</${HTML_TAGNAME}>`
+ );
+ }
+
+ /**
+ * Create a template block from a function or an object
+ * @param {String} blockName
+ * @param {Object} block
+ * @return {TemplateBlock}
+ */
+ static create(blockName, block) {
+ if (is.fn(block)) {
+ block = new Map({
+ process: block
+ });
+ }
+
+ block = new TemplateBlock(block);
+ block = block.set('name', blockName);
+ return block;
+ }
+}
+
+/**
+ * Extract kwargs from an arguments array
+ * @param {Array} args
+ * @return {Object}
+ */
+function extractKwargs(args) {
+ const last = args[args.length - 1];
+ return (is.object(last) && last.__keywords) ? args.pop() : {};
+}
+
+module.exports = TemplateBlock;
diff --git a/packages/gitbook/src/models/templateEngine.js b/packages/gitbook/src/models/templateEngine.js
new file mode 100644
index 0000000..0d0dcb6
--- /dev/null
+++ b/packages/gitbook/src/models/templateEngine.js
@@ -0,0 +1,133 @@
+const nunjucks = require('nunjucks');
+const { Record, Map, List } = require('immutable');
+
+const DEFAULTS = {
+ // List of {TemplateBlock}
+ blocks: List(),
+ // Map of Extension
+ extensions: Map(),
+ // Map of filters: {String} name -> {Function} fn
+ filters: Map(),
+ // Map of globals: {String} name -> {Mixed}
+ globals: Map(),
+ // Context for filters / blocks
+ context: Object(),
+ // Nunjucks loader
+ loader: nunjucks.FileSystemLoader('views')
+};
+
+class TemplateEngine extends Record(DEFAULTS) {
+ getBlocks() {
+ return this.get('blocks');
+ }
+
+ getGlobals() {
+ return this.get('globals');
+ }
+
+ getFilters() {
+ return this.get('filters');
+ }
+
+ getShortcuts() {
+ return this.get('shortcuts');
+ }
+
+ getLoader() {
+ return this.get('loader');
+ }
+
+ getContext() {
+ return this.get('context');
+ }
+
+ getExtensions() {
+ return this.get('extensions');
+ }
+
+ /**
+ * Return a block by its name (or undefined).
+ * @param {String} name
+ * @return {TemplateBlock} block?
+ */
+ getBlock(name) {
+ const blocks = this.getBlocks();
+ return blocks.find(function(block) {
+ return block.getName() === name;
+ });
+ }
+
+ /**
+ * Return a nunjucks environment from this configuration
+ * @return {Nunjucks.Environment} env
+ */
+ toNunjucks() {
+ const loader = this.getLoader();
+ const blocks = this.getBlocks();
+ const filters = this.getFilters();
+ const globals = this.getGlobals();
+ const extensions = this.getExtensions();
+ const context = this.getContext();
+
+ const env = new nunjucks.Environment(
+ loader,
+ {
+ // Escaping is done after by the asciidoc/markdown parser
+ autoescape: false,
+
+ // Syntax
+ tags: {
+ blockStart: '{%',
+ blockEnd: '%}',
+ variableStart: '{{',
+ variableEnd: '}}',
+ commentStart: '{###',
+ commentEnd: '###}'
+ }
+ }
+ );
+
+ // Add filters
+ filters.forEach(function(filterFn, filterName) {
+ env.addFilter(filterName, filterFn.bind(context));
+ });
+
+ // Add blocks
+ blocks.forEach(function(block) {
+ const extName = block.getExtensionName();
+ const Ext = block.toNunjucksExt(context);
+
+ env.addExtension(extName, new Ext());
+ });
+
+ // Add globals
+ globals.forEach(function(globalValue, globalName) {
+ env.addGlobal(globalName, globalValue);
+ });
+
+ // Add other extensions
+ extensions.forEach(function(ext, extName) {
+ env.addExtension(extName, ext);
+ });
+
+ return env;
+ }
+
+ /**
+ * Create a template engine.
+ * @param {Object} def
+ * @return {TemplateEngine} engine
+ */
+ static create(def) {
+ return new TemplateEngine({
+ blocks: List(def.blocks || []),
+ extensions: Map(def.extensions || {}),
+ filters: Map(def.filters || {}),
+ globals: Map(def.globals || {}),
+ context: def.context,
+ loader: def.loader
+ });
+ }
+}
+
+module.exports = TemplateEngine;
diff --git a/lib/models/templateShortcut.js b/packages/gitbook/src/models/templateShortcut.js
index 309fa6d..b6e1ed9 100644
--- a/lib/models/templateShortcut.js
+++ b/packages/gitbook/src/models/templateShortcut.js
@@ -1,11 +1,11 @@
-var Immutable = require('immutable');
-var is = require('is');
+const Immutable = require('immutable');
+const is = require('is');
/*
A TemplateShortcut is defined in plugin's template blocks
to replace content with a templating block using delimiters.
*/
-var TemplateShortcut = Immutable.Record({
+const TemplateShortcut = Immutable.Record({
// List of parser names accepting this shortcut
parsers: Immutable.Map(),
@@ -47,7 +47,7 @@ TemplateShortcut.prototype.acceptParser = function(parser) {
parser = parser.getName();
}
- var parserNames = this.get('parsers');
+ const parserNames = this.get('parsers');
return parserNames.includes(parser);
};
diff --git a/packages/gitbook/src/models/uriIndex.js b/packages/gitbook/src/models/uriIndex.js
new file mode 100644
index 0000000..eecdc54
--- /dev/null
+++ b/packages/gitbook/src/models/uriIndex.js
@@ -0,0 +1,159 @@
+const path = require('path');
+const url = require('url');
+const { Record, Map } = require('immutable');
+const LocationUtils = require('../utils/location');
+
+/*
+ The URIIndex stores a map of filename to url.
+ To resolve urls for each article.
+ */
+
+const DEFAULTS = {
+ uris: Map(),
+ directoryIndex: Boolean(true)
+};
+
+/**
+ * Modify an url path while preserving the hash
+ * @param {String} input
+ * @param {Function<String>} transform
+ * @return {String} output
+ */
+function transformURLPath(input, transform) {
+ // Split anchor
+ const parsed = url.parse(input);
+ input = parsed.pathname || '';
+
+ input = transform(input);
+
+ // Add back anchor
+ input = input + (parsed.hash || '');
+
+ return input;
+}
+
+class URIIndex extends Record(DEFAULTS) {
+ constructor(index) {
+ super({
+ uris: Map(index)
+ .mapKeys(key => LocationUtils.normalize(key))
+ });
+ }
+
+ /**
+ * Append a file to the index
+ * @param {String} filePath
+ * @param {String} url
+ * @return {URIIndex}
+ */
+ append(filePath, uri) {
+ const { uris } = this;
+ filePath = LocationUtils.normalize(filePath);
+
+ return this.merge({
+ uris: uris.set(filePath, uri)
+ });
+ }
+
+ /**
+ * Resolve an absolute file path to an url.
+ *
+ * @param {String} filePath
+ * @return {String} url
+ */
+ resolve(filePath) {
+ if (LocationUtils.isExternal(filePath)) {
+ return filePath;
+ }
+
+ return transformURLPath(filePath, (href) => {
+ const { uris } = this;
+ href = LocationUtils.normalize(href);
+
+ return uris.get(href, href);
+ });
+ }
+
+ /**
+ * Resolve a filename to an url, considering that the link to "filePath"
+ * in the file "originPath".
+ *
+ * For example if we are generating doc/README.md and we have a link "/READNE.md":
+ * index.resolveFrom('doc/README.md', '/README.md') === '../index.html'
+ *
+ * @param {String} originPath
+ * @param {String} filePath
+ * @return {String} url
+ */
+ resolveFrom(originPath, filePath) {
+ if (LocationUtils.isExternal(filePath)) {
+ return filePath;
+ }
+
+ const originURL = this.resolve(originPath);
+ const originDir = path.dirname(originPath);
+ const originOutDir = path.dirname(originURL);
+
+ return transformURLPath(filePath, (href) => {
+ if (!href) {
+ return href;
+ }
+ // Calcul absolute path for this
+ href = LocationUtils.toAbsolute(href, originDir, '.');
+
+ // Resolve file
+ href = this.resolve(href);
+
+ // Convert back to relative
+ href = LocationUtils.relative(originOutDir, href);
+
+ return href;
+ });
+ }
+
+ /**
+ * Normalize an url
+ * @param {String} uri
+ * @return {String} uri
+ */
+ normalizeURL(uri) {
+ const { directoryIndex } = this;
+
+ if (!directoryIndex || LocationUtils.isExternal(uri)) {
+ return uri;
+ }
+
+ return transformURLPath(uri, (pathname) => {
+ if (path.basename(pathname) == 'index.html') {
+ pathname = path.dirname(pathname) + '/';
+ }
+
+ return pathname;
+ });
+ }
+
+ /**
+ * Resolve an entry to an url
+ * @param {String} filePath
+ * @return {String}
+ */
+ resolveToURL(filePath) {
+ const uri = this.resolve(filePath);
+ return this.normalizeURL(uri);
+ }
+
+ /**
+ * Resolve an entry to an url
+ *
+ * @param {String} originPath
+ * @param {String} filePath
+ * @return {String} url
+ */
+ resolveToURLFrom(originPath, filePath) {
+ const uri = this.resolveFrom(originPath, filePath);
+ return this.normalizeURL(uri);
+ }
+
+}
+
+module.exports = URIIndex;
diff --git a/lib/modifiers/config/__tests__/addPlugin.js b/packages/gitbook/src/modifiers/config/__tests__/addPlugin.js
index 61082c9..65fd8f9 100644
--- a/lib/modifiers/config/__tests__/addPlugin.js
+++ b/packages/gitbook/src/modifiers/config/__tests__/addPlugin.js
@@ -1,13 +1,13 @@
-var addPlugin = require('../addPlugin');
-var Config = require('../../../models/config');
+const addPlugin = require('../addPlugin');
+const Config = require('../../../models/config');
describe('addPlugin', function() {
- var config = Config.createWithValues({
+ const config = Config.createWithValues({
plugins: ['hello', 'world', '-disabled']
});
it('should have correct state of dependencies', function() {
- var disabledDep = config.getPluginDependency('disabled');
+ const disabledDep = config.getPluginDependency('disabled');
expect(disabledDep).toBeDefined();
expect(disabledDep.getVersion()).toEqual('*');
@@ -15,18 +15,17 @@ describe('addPlugin', function() {
});
it('should add the plugin to the list', function() {
- var newConfig = addPlugin(config, 'test');
+ const newConfig = addPlugin(config, 'test');
- var testDep = newConfig.getPluginDependency('test');
+ const testDep = newConfig.getPluginDependency('test');
expect(testDep).toBeDefined();
expect(testDep.getVersion()).toEqual('*');
expect(testDep.isEnabled()).toBeTruthy();
- var disabledDep = newConfig.getPluginDependency('disabled');
+ const disabledDep = newConfig.getPluginDependency('disabled');
expect(disabledDep).toBeDefined();
expect(disabledDep.getVersion()).toEqual('*');
expect(disabledDep.isEnabled()).toBeFalsy();
});
});
-
diff --git a/lib/modifiers/config/__tests__/removePlugin.js b/packages/gitbook/src/modifiers/config/__tests__/removePlugin.js
index 253cc39..5450b30 100644
--- a/lib/modifiers/config/__tests__/removePlugin.js
+++ b/packages/gitbook/src/modifiers/config/__tests__/removePlugin.js
@@ -1,33 +1,32 @@
-var removePlugin = require('../removePlugin');
-var Config = require('../../../models/config');
+const removePlugin = require('../removePlugin');
+const Config = require('../../../models/config');
describe('removePlugin', function() {
- var config = Config.createWithValues({
+ const config = Config.createWithValues({
plugins: ['hello', 'world', '-disabled']
});
it('should remove the plugin from the list', function() {
- var newConfig = removePlugin(config, 'hello');
+ const newConfig = removePlugin(config, 'hello');
- var testDep = newConfig.getPluginDependency('hello');
+ const testDep = newConfig.getPluginDependency('hello');
expect(testDep).toNotBeDefined();
});
it('should remove the disabled plugin from the list', function() {
- var newConfig = removePlugin(config, 'disabled');
+ const newConfig = removePlugin(config, 'disabled');
- var testDep = newConfig.getPluginDependency('disabled');
+ const testDep = newConfig.getPluginDependency('disabled');
expect(testDep).toNotBeDefined();
});
it('should disable default plugin', function() {
- var newConfig = removePlugin(config, 'search');
+ const newConfig = removePlugin(config, 'search');
- var disabledDep = newConfig.getPluginDependency('search');
+ const disabledDep = newConfig.getPluginDependency('search');
expect(disabledDep).toBeDefined();
expect(disabledDep.getVersion()).toEqual('*');
expect(disabledDep.isEnabled()).toBeFalsy();
});
});
-
diff --git a/lib/modifiers/config/__tests__/togglePlugin.js b/packages/gitbook/src/modifiers/config/__tests__/togglePlugin.js
index 4127853..6d23ae0 100644
--- a/lib/modifiers/config/__tests__/togglePlugin.js
+++ b/packages/gitbook/src/modifiers/config/__tests__/togglePlugin.js
@@ -1,28 +1,27 @@
-var togglePlugin = require('../togglePlugin');
-var Config = require('../../../models/config');
+const togglePlugin = require('../togglePlugin');
+const Config = require('../../../models/config');
describe('togglePlugin', function() {
- var config = Config.createWithValues({
+ const config = Config.createWithValues({
plugins: ['hello', 'world', '-disabled']
});
it('should enable plugin', function() {
- var newConfig = togglePlugin(config, 'disabled');
+ const newConfig = togglePlugin(config, 'disabled');
- var testDep = newConfig.getPluginDependency('disabled');
+ const testDep = newConfig.getPluginDependency('disabled');
expect(testDep).toBeDefined();
expect(testDep.getVersion()).toEqual('*');
expect(testDep.isEnabled()).toBeTruthy();
});
it('should disable plugin', function() {
- var newConfig = togglePlugin(config, 'world');
+ const newConfig = togglePlugin(config, 'world');
- var testDep = newConfig.getPluginDependency('world');
+ const testDep = newConfig.getPluginDependency('world');
expect(testDep).toBeDefined();
expect(testDep.getVersion()).toEqual('*');
expect(testDep.isEnabled()).toBeFalsy();
});
});
-
diff --git a/lib/modifiers/config/addPlugin.js b/packages/gitbook/src/modifiers/config/addPlugin.js
index b8d4ea1..e9ed259 100644
--- a/lib/modifiers/config/addPlugin.js
+++ b/packages/gitbook/src/modifiers/config/addPlugin.js
@@ -1,6 +1,6 @@
-var PluginDependency = require('../../models/pluginDependency');
-var togglePlugin = require('./togglePlugin');
-var isDefaultPlugin = require('./isDefaultPlugin');
+const PluginDependency = require('../../models/pluginDependency');
+const togglePlugin = require('./togglePlugin');
+const isDefaultPlugin = require('./isDefaultPlugin');
/**
* Add a plugin to a book's configuration
@@ -15,8 +15,8 @@ function addPlugin(config, pluginName, version) {
return togglePlugin(config, pluginName, true);
}
- var deps = config.getPluginDependencies();
- var dep = PluginDependency.create(pluginName, version);
+ let deps = config.getPluginDependencies();
+ const dep = PluginDependency.create(pluginName, version);
deps = deps.push(dep);
return config.setPluginDependencies(deps);
diff --git a/lib/modifiers/config/editPlugin.js b/packages/gitbook/src/modifiers/config/editPlugin.js
index a792acd..dd7fd11 100644
--- a/lib/modifiers/config/editPlugin.js
+++ b/packages/gitbook/src/modifiers/config/editPlugin.js
@@ -7,7 +7,7 @@
* @return {Config}
*/
function editPlugin(config, pluginName, pluginConfig) {
- return config.setValue('pluginsConfig.'+pluginName, pluginConfig);
+ return config.setValue('pluginsConfig.' + pluginName, pluginConfig);
}
module.exports = editPlugin;
diff --git a/lib/modifiers/config/getPluginConfig.js b/packages/gitbook/src/modifiers/config/getPluginConfig.js
index ae76de8..ed7d6ea 100644
--- a/lib/modifiers/config/getPluginConfig.js
+++ b/packages/gitbook/src/modifiers/config/getPluginConfig.js
@@ -5,11 +5,11 @@
* @return {Object}
*/
function getPluginConfig(config, pluginName) {
- var pluginsConfig = config.getValues().get('pluginsConfig');
+ const pluginsConfig = config.getValues().get('pluginsConfig');
if (pluginsConfig === undefined) {
return {};
}
- var pluginConf = pluginsConfig.get(pluginName);
+ const pluginConf = pluginsConfig.get(pluginName);
if (pluginConf === undefined) {
return {};
} else {
diff --git a/lib/modifiers/config/hasPlugin.js b/packages/gitbook/src/modifiers/config/hasPlugin.js
index 9aab4f2..9aab4f2 100644
--- a/lib/modifiers/config/hasPlugin.js
+++ b/packages/gitbook/src/modifiers/config/hasPlugin.js
diff --git a/lib/modifiers/config/index.js b/packages/gitbook/src/modifiers/config/index.js
index b3de0b0..b3de0b0 100644
--- a/lib/modifiers/config/index.js
+++ b/packages/gitbook/src/modifiers/config/index.js
diff --git a/lib/modifiers/config/isDefaultPlugin.js b/packages/gitbook/src/modifiers/config/isDefaultPlugin.js
index 63a141d..096e21a 100644
--- a/lib/modifiers/config/isDefaultPlugin.js
+++ b/packages/gitbook/src/modifiers/config/isDefaultPlugin.js
@@ -1,5 +1,5 @@
-var DEFAULT_PLUGINS = require('../../constants/defaultPlugins');
-var hasPlugin = require('./hasPlugin');
+const DEFAULT_PLUGINS = require('../../constants/defaultPlugins');
+const hasPlugin = require('./hasPlugin');
/**
* Test if a plugin is a default one
diff --git a/lib/modifiers/config/removePlugin.js b/packages/gitbook/src/modifiers/config/removePlugin.js
index ec06d1e..c80ab84 100644
--- a/lib/modifiers/config/removePlugin.js
+++ b/packages/gitbook/src/modifiers/config/removePlugin.js
@@ -1,5 +1,5 @@
-var togglePlugin = require('./togglePlugin');
-var isDefaultPlugin = require('./isDefaultPlugin');
+const togglePlugin = require('./togglePlugin');
+const isDefaultPlugin = require('./isDefaultPlugin');
/**
* Remove a plugin from a book's configuration
@@ -8,7 +8,7 @@ var isDefaultPlugin = require('./isDefaultPlugin');
* @return {Config}
*/
function removePlugin(config, pluginName) {
- var deps = config.getPluginDependencies();
+ let deps = config.getPluginDependencies();
// For default plugin, we have to disable it instead of removing from the list
if (isDefaultPlugin(pluginName)) {
diff --git a/lib/modifiers/config/togglePlugin.js b/packages/gitbook/src/modifiers/config/togglePlugin.js
index a49e3b9..12a6dec 100644
--- a/lib/modifiers/config/togglePlugin.js
+++ b/packages/gitbook/src/modifiers/config/togglePlugin.js
@@ -1,6 +1,6 @@
-var PluginDependency = require('../../models/pluginDependency');
-var hasPlugin = require('./hasPlugin');
-var isDefaultPlugin = require('./isDefaultPlugin');
+const PluginDependency = require('../../models/pluginDependency');
+const hasPlugin = require('./hasPlugin');
+const isDefaultPlugin = require('./isDefaultPlugin');
/**
* Enable/disable a plugin dependency
@@ -10,7 +10,7 @@ var isDefaultPlugin = require('./isDefaultPlugin');
* @return {Config}
*/
function togglePlugin(config, pluginName, state) {
- var deps = config.getPluginDependencies();
+ let deps = config.getPluginDependencies();
// For default plugin, we should ensure it's listed first
if (isDefaultPlugin(pluginName) && !hasPlugin(deps, pluginName)) {
diff --git a/lib/modifiers/index.js b/packages/gitbook/src/modifiers/index.js
index ad24604..ad24604 100644
--- a/lib/modifiers/index.js
+++ b/packages/gitbook/src/modifiers/index.js
diff --git a/packages/gitbook/src/modifiers/summary/__tests__/editArticle.js b/packages/gitbook/src/modifiers/summary/__tests__/editArticle.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/gitbook/src/modifiers/summary/__tests__/editArticle.js
diff --git a/lib/modifiers/summary/__tests__/editPartTitle.js b/packages/gitbook/src/modifiers/summary/__tests__/editPartTitle.js
index d1b916b..aa14a34 100644
--- a/lib/modifiers/summary/__tests__/editPartTitle.js
+++ b/packages/gitbook/src/modifiers/summary/__tests__/editPartTitle.js
@@ -1,9 +1,9 @@
-var Summary = require('../../../models/summary');
-var File = require('../../../models/file');
+const Summary = require('../../../models/summary');
+const File = require('../../../models/file');
describe('editPartTitle', function() {
- var editPartTitle = require('../editPartTitle');
- var summary = Summary.createFromParts(File(), [
+ const editPartTitle = require('../editPartTitle');
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -22,23 +22,22 @@ describe('editPartTitle', function() {
]);
it('should correctly set title of first part', function() {
- var newSummary = editPartTitle(summary, 0, 'Hello World');
- var part = newSummary.getPart(0);
+ const newSummary = editPartTitle(summary, 0, 'Hello World');
+ const part = newSummary.getPart(0);
expect(part.getTitle()).toBe('Hello World');
});
it('should correctly set title of second part', function() {
- var newSummary = editPartTitle(summary, 1, 'Hello');
- var part = newSummary.getPart(1);
+ const newSummary = editPartTitle(summary, 1, 'Hello');
+ const part = newSummary.getPart(1);
expect(part.getTitle()).toBe('Hello');
});
it('should not fail if part doesn\'t exist', function() {
- var newSummary = editPartTitle(summary, 3, 'Hello');
+ const newSummary = editPartTitle(summary, 3, 'Hello');
expect(newSummary.getParts().size).toBe(2);
});
});
-
diff --git a/lib/modifiers/summary/__tests__/insertArticle.js b/packages/gitbook/src/modifiers/summary/__tests__/insertArticle.js
index 1ee1c8a..d5ae9bc 100644
--- a/lib/modifiers/summary/__tests__/insertArticle.js
+++ b/packages/gitbook/src/modifiers/summary/__tests__/insertArticle.js
@@ -1,10 +1,10 @@
-var Summary = require('../../../models/summary');
-var SummaryArticle = require('../../../models/summaryArticle');
-var File = require('../../../models/file');
+const Summary = require('../../../models/summary');
+const SummaryArticle = require('../../../models/summaryArticle');
+const File = require('../../../models/file');
describe('insertArticle', function() {
- var insertArticle = require('../insertArticle');
- var summary = Summary.createFromParts(File(), [
+ const insertArticle = require('../insertArticle');
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -43,14 +43,14 @@ describe('insertArticle', function() {
]);
it('should insert an article at a given level', function() {
- var article = SummaryArticle.create({
+ const article = SummaryArticle.create({
title: 'Inserted'
}, 'fake.level');
- var newSummary = insertArticle(summary, article, '2.1.1');
+ const newSummary = insertArticle(summary, article, '2.1.1');
- var inserted = newSummary.getByLevel('2.1.1');
- var nextOne = newSummary.getByLevel('2.1.2');
+ const inserted = newSummary.getByLevel('2.1.1');
+ const nextOne = newSummary.getByLevel('2.1.2');
expect(inserted.getTitle()).toBe('Inserted');
expect(inserted.getLevel()).toBe('2.1.1');
@@ -60,14 +60,14 @@ describe('insertArticle', function() {
});
it('should insert an article in last position', function() {
- var article = SummaryArticle.create({
+ const article = SummaryArticle.create({
title: 'Inserted'
}, 'fake.level');
- var newSummary = insertArticle(summary, article, '2.2');
+ const newSummary = insertArticle(summary, article, '2.2');
- var inserted = newSummary.getByLevel('2.2');
- var previousOne = newSummary.getByLevel('2.1');
+ const inserted = newSummary.getByLevel('2.2');
+ const previousOne = newSummary.getByLevel('2.1');
expect(inserted.getTitle()).toBe('Inserted');
expect(inserted.getLevel()).toBe('2.2');
diff --git a/lib/modifiers/summary/__tests__/insertPart.js b/packages/gitbook/src/modifiers/summary/__tests__/insertPart.js
index 11c2cbc..5112931 100644
--- a/lib/modifiers/summary/__tests__/insertPart.js
+++ b/packages/gitbook/src/modifiers/summary/__tests__/insertPart.js
@@ -1,11 +1,11 @@
-var Summary = require('../../../models/summary');
-var SummaryPart = require('../../../models/summaryPart');
+const Summary = require('../../../models/summary');
+const SummaryPart = require('../../../models/summaryPart');
-var File = require('../../../models/file');
+const File = require('../../../models/file');
describe('insertPart', function() {
- var insertPart = require('../insertPart');
- var summary = Summary.createFromParts(File(), [
+ const insertPart = require('../insertPart');
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -31,29 +31,29 @@ describe('insertPart', function() {
]);
it('should insert an part at a given level', function() {
- var part = SummaryPart.create({
+ const part = SummaryPart.create({
title: 'Inserted'
}, 'meaningless.level');
- var newSummary = insertPart(summary, part, 1);
+ const newSummary = insertPart(summary, part, 1);
- var inserted = newSummary.getPart(1);
+ const inserted = newSummary.getPart(1);
expect(inserted.getTitle()).toBe('Inserted');
expect(newSummary.getParts().count()).toBe(3);
- var otherArticle = newSummary.getByLevel('3.1');
+ const otherArticle = newSummary.getByLevel('3.1');
expect(otherArticle.getTitle()).toBe('2.1');
expect(otherArticle.getLevel()).toBe('3.1');
});
it('should insert an part in last position', function() {
- var part = SummaryPart.create({
+ const part = SummaryPart.create({
title: 'Inserted'
}, 'meaningless.level');
- var newSummary = insertPart(summary, part, 2);
+ const newSummary = insertPart(summary, part, 2);
- var inserted = newSummary.getPart(2);
+ const inserted = newSummary.getPart(2);
expect(inserted.getTitle()).toBe('Inserted');
expect(newSummary.getParts().count()).toBe(3);
});
diff --git a/lib/modifiers/summary/__tests__/mergeAtLevel.js b/packages/gitbook/src/modifiers/summary/__tests__/mergeAtLevel.js
index e2635ec..e0d4a62 100644
--- a/lib/modifiers/summary/__tests__/mergeAtLevel.js
+++ b/packages/gitbook/src/modifiers/summary/__tests__/mergeAtLevel.js
@@ -1,10 +1,10 @@
-var Immutable = require('immutable');
-var Summary = require('../../../models/summary');
-var File = require('../../../models/file');
+const Immutable = require('immutable');
+const Summary = require('../../../models/summary');
+const File = require('../../../models/file');
describe('mergeAtLevel', function() {
- var mergeAtLevel = require('../mergeAtLevel');
- var summary = Summary.createFromParts(File(), [
+ const mergeAtLevel = require('../mergeAtLevel');
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -24,9 +24,9 @@ describe('mergeAtLevel', function() {
]);
it('should edit a part', function() {
- var beforeChildren = summary.getByLevel('1').getArticles();
- var newSummary = mergeAtLevel(summary, '1', {title: 'Part O'});
- var edited = newSummary.getByLevel('1');
+ const beforeChildren = summary.getByLevel('1').getArticles();
+ const newSummary = mergeAtLevel(summary, '1', {title: 'Part O'});
+ const edited = newSummary.getByLevel('1');
expect(edited.getTitle()).toBe('Part O');
// Same children
@@ -34,9 +34,9 @@ describe('mergeAtLevel', function() {
});
it('should edit a part', function() {
- var beforePath = summary.getByLevel('1.2').getPath();
- var newSummary = mergeAtLevel(summary, '1.2', {title: 'Renamed article'});
- var edited = newSummary.getByLevel('1.2');
+ const beforePath = summary.getByLevel('1.2').getPath();
+ const newSummary = mergeAtLevel(summary, '1.2', {title: 'Renamed article'});
+ const edited = newSummary.getByLevel('1.2');
expect(edited.getTitle()).toBe('Renamed article');
// Same children
diff --git a/lib/modifiers/summary/__tests__/moveArticle.js b/packages/gitbook/src/modifiers/summary/__tests__/moveArticle.js
index aed0b94..a7d111b 100644
--- a/lib/modifiers/summary/__tests__/moveArticle.js
+++ b/packages/gitbook/src/modifiers/summary/__tests__/moveArticle.js
@@ -1,10 +1,10 @@
-var Immutable = require('immutable');
-var Summary = require('../../../models/summary');
-var File = require('../../../models/file');
+const Immutable = require('immutable');
+const Summary = require('../../../models/summary');
+const File = require('../../../models/file');
describe('moveArticle', function() {
- var moveArticle = require('../moveArticle');
- var summary = Summary.createFromParts(File(), [
+ const moveArticle = require('../moveArticle');
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -43,24 +43,24 @@ describe('moveArticle', function() {
]);
it('should move an article to the same place', function() {
- var newSummary = moveArticle(summary, '2.1', '2.1');
+ const newSummary = moveArticle(summary, '2.1', '2.1');
expect(Immutable.is(summary, newSummary)).toBe(true);
});
it('should move an article to an previous level', function() {
- var newSummary = moveArticle(summary, '2.2', '2.1');
- var moved = newSummary.getByLevel('2.1');
- var other = newSummary.getByLevel('2.2');
+ const newSummary = moveArticle(summary, '2.2', '2.1');
+ const moved = newSummary.getByLevel('2.1');
+ const other = newSummary.getByLevel('2.2');
expect(moved.getTitle()).toBe('2.2');
expect(other.getTitle()).toBe('2.1');
});
it('should move an article to a next level', function() {
- var newSummary = moveArticle(summary, '2.1', '2.2');
- var moved = newSummary.getByLevel('2.1');
- var other = newSummary.getByLevel('2.2');
+ const newSummary = moveArticle(summary, '2.1', '2.2');
+ const moved = newSummary.getByLevel('2.1');
+ const other = newSummary.getByLevel('2.2');
expect(moved.getTitle()).toBe('2.2');
expect(other.getTitle()).toBe('2.1');
diff --git a/lib/modifiers/summary/__tests__/moveArticleAfter.js b/packages/gitbook/src/modifiers/summary/__tests__/moveArticleAfter.js
index c380575..446d8a4 100644
--- a/lib/modifiers/summary/__tests__/moveArticleAfter.js
+++ b/packages/gitbook/src/modifiers/summary/__tests__/moveArticleAfter.js
@@ -1,10 +1,10 @@
-var Immutable = require('immutable');
-var Summary = require('../../../models/summary');
-var File = require('../../../models/file');
+const Immutable = require('immutable');
+const Summary = require('../../../models/summary');
+const File = require('../../../models/file');
describe('moveArticleAfter', function() {
- var moveArticleAfter = require('../moveArticleAfter');
- var summary = Summary.createFromParts(File(), [
+ const moveArticleAfter = require('../moveArticleAfter');
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -43,28 +43,28 @@ describe('moveArticleAfter', function() {
]);
it('moving right after itself should be invariant', function() {
- var newSummary = moveArticleAfter(summary, '2.1', '2.1');
+ const newSummary = moveArticleAfter(summary, '2.1', '2.1');
expect(Immutable.is(summary, newSummary)).toBe(true);
});
it('moving after previous one should be invariant too', function() {
- var newSummary = moveArticleAfter(summary, '2.1', '2.0');
+ const newSummary = moveArticleAfter(summary, '2.1', '2.0');
expect(Immutable.is(summary, newSummary)).toBe(true);
});
it('should move an article after a previous level', function() {
- var newSummary = moveArticleAfter(summary, '2.2', '2.0');
- var moved = newSummary.getByLevel('2.1');
+ const newSummary = moveArticleAfter(summary, '2.2', '2.0');
+ const moved = newSummary.getByLevel('2.1');
expect(moved.getTitle()).toBe('2.2');
expect(newSummary.getByLevel('2.2').getTitle()).toBe('2.1');
});
it('should move an article after a previous and less deep level', function() {
- var newSummary = moveArticleAfter(summary, '2.1.1', '2.0');
- var moved = newSummary.getByLevel('2.1');
+ const newSummary = moveArticleAfter(summary, '2.1.1', '2.0');
+ const moved = newSummary.getByLevel('2.1');
expect(moved.getTitle()).toBe('2.1.1');
expect(newSummary.getByLevel('2.2.1').getTitle()).toBe('2.1.2');
@@ -72,8 +72,8 @@ describe('moveArticleAfter', function() {
});
it('should move an article after a next level', function() {
- var newSummary = moveArticleAfter(summary, '2.1', '2.2');
- var moved = newSummary.getByLevel('2.2');
+ const newSummary = moveArticleAfter(summary, '2.1', '2.2');
+ const moved = newSummary.getByLevel('2.2');
expect(moved.getTitle()).toBe('2.1');
expect(newSummary.getByLevel('2.1').getTitle()).toBe('2.2');
diff --git a/lib/modifiers/summary/__tests__/removeArticle.js b/packages/gitbook/src/modifiers/summary/__tests__/removeArticle.js
index b45fb49..14587ca 100644
--- a/lib/modifiers/summary/__tests__/removeArticle.js
+++ b/packages/gitbook/src/modifiers/summary/__tests__/removeArticle.js
@@ -1,9 +1,9 @@
-var Summary = require('../../../models/summary');
-var File = require('../../../models/file');
+const Summary = require('../../../models/summary');
+const File = require('../../../models/file');
describe('removeArticle', function() {
- var removeArticle = require('../removeArticle');
- var summary = Summary.createFromParts(File(), [
+ const removeArticle = require('../removeArticle');
+ const summary = Summary.createFromParts(File(), [
{
articles: [
{
@@ -42,10 +42,10 @@ describe('removeArticle', function() {
]);
it('should remove an article at a given level', function() {
- var newSummary = removeArticle(summary, '2.1.1');
+ const newSummary = removeArticle(summary, '2.1.1');
- var removed = newSummary.getByLevel('2.1.1');
- var nextOne = newSummary.getByLevel('2.1.2');
+ const removed = newSummary.getByLevel('2.1.1');
+ const nextOne = newSummary.getByLevel('2.1.2');
expect(removed.getTitle()).toBe('2.1.2');
expect(nextOne).toBe(null);
diff --git a/lib/modifiers/summary/editArticleRef.js b/packages/gitbook/src/modifiers/summary/editArticleRef.js
index 7106960..c5c1868 100644
--- a/lib/modifiers/summary/editArticleRef.js
+++ b/packages/gitbook/src/modifiers/summary/editArticleRef.js
@@ -1,4 +1,4 @@
-var mergeAtLevel = require('./mergeAtLevel');
+const mergeAtLevel = require('./mergeAtLevel');
/**
Edit the ref of an article
diff --git a/lib/modifiers/summary/editArticleTitle.js b/packages/gitbook/src/modifiers/summary/editArticleTitle.js
index 4edee83..f55c97e 100644
--- a/lib/modifiers/summary/editArticleTitle.js
+++ b/packages/gitbook/src/modifiers/summary/editArticleTitle.js
@@ -1,4 +1,4 @@
-var mergeAtLevel = require('./mergeAtLevel');
+const mergeAtLevel = require('./mergeAtLevel');
/**
Edit title of an article
diff --git a/lib/modifiers/summary/editPartTitle.js b/packages/gitbook/src/modifiers/summary/editPartTitle.js
index b79ac1e..ace7058 100644
--- a/lib/modifiers/summary/editPartTitle.js
+++ b/packages/gitbook/src/modifiers/summary/editPartTitle.js
@@ -7,9 +7,9 @@
@return {Summary}
*/
function editPartTitle(summary, index, newTitle) {
- var parts = summary.getParts();
+ let parts = summary.getParts();
- var part = parts.get(index);
+ let part = parts.get(index);
if (!part) {
return summary;
}
diff --git a/lib/modifiers/summary/index.js b/packages/gitbook/src/modifiers/summary/index.js
index f91fdb6..f91fdb6 100644
--- a/lib/modifiers/summary/index.js
+++ b/packages/gitbook/src/modifiers/summary/index.js
diff --git a/lib/modifiers/summary/indexArticleLevels.js b/packages/gitbook/src/modifiers/summary/indexArticleLevels.js
index f311f74..03c26c7 100644
--- a/lib/modifiers/summary/indexArticleLevels.js
+++ b/packages/gitbook/src/modifiers/summary/indexArticleLevels.js
@@ -8,7 +8,7 @@
*/
function indexArticleLevels(article, baseLevel) {
baseLevel = baseLevel || article.getLevel();
- var articles = article.getArticles();
+ let articles = article.getArticles();
articles = articles.map(function(inner, i) {
return indexArticleLevels(inner, baseLevel + '.' + (i + 1));
@@ -16,7 +16,7 @@ function indexArticleLevels(article, baseLevel) {
return article.merge({
level: baseLevel,
- articles: articles
+ articles
});
}
diff --git a/lib/modifiers/summary/indexLevels.js b/packages/gitbook/src/modifiers/summary/indexLevels.js
index 604e9ff..deb76da 100644
--- a/lib/modifiers/summary/indexLevels.js
+++ b/packages/gitbook/src/modifiers/summary/indexLevels.js
@@ -1,4 +1,4 @@
-var indexPartLevels = require('./indexPartLevels');
+const indexPartLevels = require('./indexPartLevels');
/**
Index all levels in the summary
@@ -7,7 +7,7 @@ var indexPartLevels = require('./indexPartLevels');
@return {Summary}
*/
function indexLevels(summary) {
- var parts = summary.getParts();
+ let parts = summary.getParts();
parts = parts.map(indexPartLevels);
return summary.set('parts', parts);
diff --git a/lib/modifiers/summary/indexPartLevels.js b/packages/gitbook/src/modifiers/summary/indexPartLevels.js
index d19c70a..6e48778 100644
--- a/lib/modifiers/summary/indexPartLevels.js
+++ b/packages/gitbook/src/modifiers/summary/indexPartLevels.js
@@ -1,4 +1,4 @@
-var indexArticleLevels = require('./indexArticleLevels');
+const indexArticleLevels = require('./indexArticleLevels');
/**
Index levels in a part
@@ -8,8 +8,8 @@ var indexArticleLevels = require('./indexArticleLevels');
@return {Part}
*/
function indexPartLevels(part, index) {
- var baseLevel = String(index + 1);
- var articles = part.getArticles();
+ const baseLevel = String(index + 1);
+ let articles = part.getArticles();
articles = articles.map(function(inner, i) {
return indexArticleLevels(inner, baseLevel + '.' + (i + 1));
@@ -17,7 +17,7 @@ function indexPartLevels(part, index) {
return part.merge({
level: baseLevel,
- articles: articles
+ articles
});
}
diff --git a/lib/modifiers/summary/insertArticle.js b/packages/gitbook/src/modifiers/summary/insertArticle.js
index 3a084b3..537f548 100644
--- a/lib/modifiers/summary/insertArticle.js
+++ b/packages/gitbook/src/modifiers/summary/insertArticle.js
@@ -1,7 +1,7 @@
-var is = require('is');
-var SummaryArticle = require('../../models/summaryArticle');
-var mergeAtLevel = require('./mergeAtLevel');
-var indexArticleLevels = require('./indexArticleLevels');
+const is = require('is');
+const SummaryArticle = require('../../models/summaryArticle');
+const mergeAtLevel = require('./mergeAtLevel');
+const indexArticleLevels = require('./indexArticleLevels');
/**
Returns a new Summary with the article at the given level, with
@@ -14,16 +14,16 @@ var indexArticleLevels = require('./indexArticleLevels');
*/
function insertArticle(summary, article, level) {
article = SummaryArticle(article);
- level = is.string(level)? level : level.getLevel();
+ level = is.string(level) ? level : level.getLevel();
- var parent = summary.getParent(level);
+ let parent = summary.getParent(level);
if (!parent) {
return summary;
}
// Find the index to insert at
- var articles = parent.getArticles();
- var index = getLeafIndex(level);
+ let articles = parent.getArticles();
+ const index = getLeafIndex(level);
// Insert the article at the right index
articles = articles.insert(index, article);
@@ -40,7 +40,7 @@ function insertArticle(summary, article, level) {
@return {Number} The index of this level within its parent's children
*/
function getLeafIndex(level) {
- var arr = level.split('.').map(function (char) {
+ const arr = level.split('.').map(function(char) {
return parseInt(char, 10);
});
return arr[arr.length - 1] - 1;
diff --git a/lib/modifiers/summary/insertPart.js b/packages/gitbook/src/modifiers/summary/insertPart.js
index 199cba7..ea99f89 100644
--- a/lib/modifiers/summary/insertPart.js
+++ b/packages/gitbook/src/modifiers/summary/insertPart.js
@@ -1,5 +1,5 @@
-var SummaryPart = require('../../models/summaryPart');
-var indexLevels = require('./indexLevels');
+const SummaryPart = require('../../models/summaryPart');
+const indexLevels = require('./indexLevels');
/**
Returns a new Summary with a part inserted at given index
@@ -12,7 +12,7 @@ var indexLevels = require('./indexLevels');
function insertPart(summary, part, index) {
part = SummaryPart(part);
- var parts = summary.getParts().insert(index, part);
+ const parts = summary.getParts().insert(index, part);
return indexLevels(summary.set('parts', parts));
}
diff --git a/lib/modifiers/summary/mergeAtLevel.js b/packages/gitbook/src/modifiers/summary/mergeAtLevel.js
index 9a95ffc..ea01763 100644
--- a/lib/modifiers/summary/mergeAtLevel.js
+++ b/packages/gitbook/src/modifiers/summary/mergeAtLevel.js
@@ -9,14 +9,14 @@
*/
function editArticleInList(articles, level, newArticle) {
return articles.map(function(article) {
- var articleLevel = article.getLevel();
+ const articleLevel = article.getLevel();
if (articleLevel === level) {
// it is the article to edit
return article.merge(newArticle);
} else if (level.indexOf(articleLevel) === 0) {
// it is a parent
- var articles = editArticleInList(article.getArticles(), level, newArticle);
+ const articles = editArticleInList(article.getArticles(), level, newArticle);
return article.set('articles', articles);
} else {
// This is not the article you are looking for
@@ -35,7 +35,7 @@ function editArticleInList(articles, level, newArticle) {
@return {Part}
*/
function editArticleInPart(part, level, newArticle) {
- var articles = part.getArticles();
+ let articles = part.getArticles();
articles = editArticleInList(articles, level, newArticle);
return part.set('articles', articles);
@@ -51,16 +51,16 @@ function editArticleInPart(part, level, newArticle) {
@return {Summary}
*/
function mergeAtLevel(summary, level, newValue) {
- var levelParts = level.split('.');
- var partIndex = Number(levelParts[0]) -1;
+ const levelParts = level.split('.');
+ const partIndex = Number(levelParts[0]) - 1;
- var parts = summary.getParts();
- var part = parts.get(partIndex);
+ let parts = summary.getParts();
+ let part = parts.get(partIndex);
if (!part) {
return summary;
}
- var isEditingPart = levelParts.length < 2;
+ const isEditingPart = levelParts.length < 2;
if (isEditingPart) {
part = part.merge(newValue);
} else {
diff --git a/lib/modifiers/summary/moveArticle.js b/packages/gitbook/src/modifiers/summary/moveArticle.js
index 5cb1868..29d4748 100644
--- a/lib/modifiers/summary/moveArticle.js
+++ b/packages/gitbook/src/modifiers/summary/moveArticle.js
@@ -1,6 +1,6 @@
-var is = require('is');
-var removeArticle = require('./removeArticle');
-var insertArticle = require('./insertArticle');
+const is = require('is');
+const removeArticle = require('./removeArticle');
+const insertArticle = require('./insertArticle');
/**
Returns a new summary, with the given article removed from its
@@ -13,12 +13,12 @@ var insertArticle = require('./insertArticle');
*/
function moveArticle(summary, origin, target) {
// Coerce to level
- var originLevel = is.string(origin)? origin : origin.getLevel();
- var targetLevel = is.string(target)? target : target.getLevel();
- var article = summary.getByLevel(originLevel);
+ const originLevel = is.string(origin) ? origin : origin.getLevel();
+ const targetLevel = is.string(target) ? target : target.getLevel();
+ const article = summary.getByLevel(originLevel);
// Remove first
- var removed = removeArticle(summary, originLevel);
+ const removed = removeArticle(summary, originLevel);
return insertArticle(removed, article, targetLevel);
}
diff --git a/lib/modifiers/summary/moveArticleAfter.js b/packages/gitbook/src/modifiers/summary/moveArticleAfter.js
index e268f73..a1ed28f 100644
--- a/lib/modifiers/summary/moveArticleAfter.js
+++ b/packages/gitbook/src/modifiers/summary/moveArticleAfter.js
@@ -1,6 +1,6 @@
-var is = require('is');
-var removeArticle = require('./removeArticle');
-var insertArticle = require('./insertArticle');
+const is = require('is');
+const removeArticle = require('./removeArticle');
+const insertArticle = require('./insertArticle');
/**
Returns a new summary, with the an article moved after another
@@ -14,20 +14,20 @@ var insertArticle = require('./insertArticle');
*/
function moveArticleAfter(summary, origin, afterTarget) {
// Coerce to level
- var originLevel = is.string(origin)? origin : origin.getLevel();
- var afterTargetLevel = is.string(afterTarget)? afterTarget : afterTarget.getLevel();
- var article = summary.getByLevel(originLevel);
+ const originLevel = is.string(origin) ? origin : origin.getLevel();
+ const afterTargetLevel = is.string(afterTarget) ? afterTarget : afterTarget.getLevel();
+ const article = summary.getByLevel(originLevel);
- var targetLevel = increment(afterTargetLevel);
+ const targetLevel = increment(afterTargetLevel);
if (targetLevel < origin) {
// Remove first
- var removed = removeArticle(summary, originLevel);
+ const removed = removeArticle(summary, originLevel);
// Insert then
return insertArticle(removed, article, targetLevel);
} else {
// Insert right after first
- var inserted = insertArticle(summary, article, targetLevel);
+ const inserted = insertArticle(summary, article, targetLevel);
// Remove old one
return removeArticle(inserted, originLevel);
}
@@ -38,7 +38,7 @@ function moveArticleAfter(summary, origin, afterTarget) {
@return {Array<Number>}
*/
function levelToArray(l) {
- return l.split('.').map(function (char) {
+ return l.split('.').map(function(char) {
return parseInt(char, 10);
});
}
diff --git a/lib/modifiers/summary/removeArticle.js b/packages/gitbook/src/modifiers/summary/removeArticle.js
index 8a30d0a..0c4cd33 100644
--- a/lib/modifiers/summary/removeArticle.js
+++ b/packages/gitbook/src/modifiers/summary/removeArticle.js
@@ -1,6 +1,6 @@
-var is = require('is');
-var mergeAtLevel = require('./mergeAtLevel');
-var indexArticleLevels = require('./indexArticleLevels');
+const is = require('is');
+const mergeAtLevel = require('./mergeAtLevel');
+const indexArticleLevels = require('./indexArticleLevels');
/**
Remove an article from a level.
@@ -11,13 +11,13 @@ var indexArticleLevels = require('./indexArticleLevels');
*/
function removeArticle(summary, level) {
// Coerce to level
- level = is.string(level)? level : level.getLevel();
+ level = is.string(level) ? level : level.getLevel();
- var parent = summary.getParent(level);
+ let parent = summary.getParent(level);
- var articles = parent.getArticles();
+ let articles = parent.getArticles();
// Find the index to remove
- var index = articles.findIndex(function(art) {
+ const index = articles.findIndex(function(art) {
return art.getLevel() === level;
});
if (index === -1) {
diff --git a/lib/modifiers/summary/removePart.js b/packages/gitbook/src/modifiers/summary/removePart.js
index 2f8affc..30502dc 100644
--- a/lib/modifiers/summary/removePart.js
+++ b/packages/gitbook/src/modifiers/summary/removePart.js
@@ -1,4 +1,4 @@
-var indexLevels = require('./indexLevels');
+const indexLevels = require('./indexLevels');
/**
Remove a part at given index
@@ -8,7 +8,7 @@ var indexLevels = require('./indexLevels');
@return {Summary}
*/
function removePart(summary, index) {
- var parts = summary.getParts().remove(index);
+ const parts = summary.getParts().remove(index);
return indexLevels(summary.set('parts', parts));
}
diff --git a/lib/modifiers/summary/unshiftArticle.js b/packages/gitbook/src/modifiers/summary/unshiftArticle.js
index d1ebc05..c5810f0 100644
--- a/lib/modifiers/summary/unshiftArticle.js
+++ b/packages/gitbook/src/modifiers/summary/unshiftArticle.js
@@ -1,7 +1,7 @@
-var SummaryArticle = require('../../models/summaryArticle');
-var SummaryPart = require('../../models/summaryPart');
+const SummaryArticle = require('../../models/summaryArticle');
+const SummaryPart = require('../../models/summaryPart');
-var indexLevels = require('./indexLevels');
+const indexLevels = require('./indexLevels');
/**
Insert an article at the beginning of summary
@@ -13,10 +13,10 @@ var indexLevels = require('./indexLevels');
function unshiftArticle(summary, article) {
article = SummaryArticle(article);
- var parts = summary.getParts();
- var part = parts.get(0) || SummaryPart();
+ let parts = summary.getParts();
+ let part = parts.get(0) || SummaryPart();
- var articles = part.getArticles();
+ let articles = part.getArticles();
articles = articles.unshift(article);
part = part.set('articles', articles);
diff --git a/lib/output/__tests__/createMock.js b/packages/gitbook/src/output/__tests__/createMock.js
index f21c544..09b93da 100644
--- a/lib/output/__tests__/createMock.js
+++ b/packages/gitbook/src/output/__tests__/createMock.js
@@ -1,10 +1,10 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var Output = require('../../models/output');
-var Book = require('../../models/book');
-var parseBook = require('../../parse/parseBook');
-var createMockFS = require('../../fs/mock');
-var preparePlugins = require('../preparePlugins');
+const Output = require('../../models/output');
+const Book = require('../../models/book');
+const parseBook = require('../../parse/parseBook');
+const createMockFS = require('../../fs/mock');
+const preparePlugins = require('../preparePlugins');
/**
* Create an output using a generator
@@ -16,9 +16,9 @@ var preparePlugins = require('../preparePlugins');
* @return {Promise<Output>}
*/
function createMockOutput(generator, files, options) {
- var fs = createMockFS(files);
- var book = Book.createForFS(fs);
- var state = generator.State? generator.State({}) : Immutable.Map();
+ const fs = createMockFS(files);
+ let book = Book.createForFS(fs);
+ const state = generator.State ? generator.State({}) : Immutable.Map();
book = book.setLogLevel('disabled');
options = generator.Options(options);
@@ -27,8 +27,8 @@ function createMockOutput(generator, files, options) {
.then(function(resultBook) {
return new Output({
book: resultBook,
- options: options,
- state: state,
+ options,
+ state,
generator: generator.name
});
})
diff --git a/lib/output/__tests__/ebook.js b/packages/gitbook/src/output/__tests__/ebook.js
index 9266e9f..8b7096c 100644
--- a/lib/output/__tests__/ebook.js
+++ b/packages/gitbook/src/output/__tests__/ebook.js
@@ -1,5 +1,5 @@
-var generateMock = require('./generateMock');
-var EbookGenerator = require('../ebook');
+const generateMock = require('./generateMock');
+const EbookGenerator = require('../ebook');
describe('EbookGenerator', function() {
@@ -13,4 +13,3 @@ describe('EbookGenerator', function() {
});
});
});
-
diff --git a/lib/output/__tests__/generateMock.js b/packages/gitbook/src/output/__tests__/generateMock.js
index 691ee2d..6ae1de2 100644
--- a/lib/output/__tests__/generateMock.js
+++ b/packages/gitbook/src/output/__tests__/generateMock.js
@@ -1,9 +1,9 @@
-var tmp = require('tmp');
+const tmp = require('tmp');
-var Book = require('../../models/book');
-var createMockFS = require('../../fs/mock');
-var parseBook = require('../../parse/parseBook');
-var generateBook = require('../generateBook');
+const Book = require('../../models/book');
+const createMockFS = require('../../fs/mock');
+const parseBook = require('../../parse/parseBook');
+const generateBook = require('../generateBook');
/**
* Generate a book using a generator
@@ -16,20 +16,20 @@ var generateBook = require('../generateBook');
* @return {Promise<String>}
*/
function generateMock(Generator, files) {
- var fs = createMockFS(files);
- var book = Book.createForFS(fs);
- var dir;
+ const fs = createMockFS(files);
+ let book = Book.createForFS(fs);
+ let dir;
try {
dir = tmp.dirSync();
- } catch(err) {
+ } catch (err) {
throw err;
}
book = book.setLogLevel('disabled');
return parseBook(book)
- .then(function(resultBook) {
+ .then((resultBook) => {
return generateBook(Generator, resultBook, {
root: dir.name
});
diff --git a/lib/output/__tests__/json.js b/packages/gitbook/src/output/__tests__/json.js
index 12ab567..d4992ec 100644
--- a/lib/output/__tests__/json.js
+++ b/packages/gitbook/src/output/__tests__/json.js
@@ -1,5 +1,5 @@
-var generateMock = require('./generateMock');
-var JSONGenerator = require('../json');
+const generateMock = require('./generateMock');
+const JSONGenerator = require('../json');
describe('JSONGenerator', function() {
diff --git a/lib/output/__tests__/website.js b/packages/gitbook/src/output/__tests__/website.js
index 1f8c3c0..4c10f1e 100644
--- a/lib/output/__tests__/website.js
+++ b/packages/gitbook/src/output/__tests__/website.js
@@ -1,22 +1,32 @@
-var fs = require('fs');
-var generateMock = require('./generateMock');
-var WebsiteGenerator = require('../website');
+const generateMock = require('./generateMock');
+const WebsiteGenerator = require('../website');
-describe('WebsiteGenerator', function() {
+describe('WebsiteGenerator', () => {
- it('should generate an index.html', function() {
+ it('should generate an index.html', () => {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World'
})
- .then(function(folder) {
+ .then((folder) => {
+ expect(folder).toHaveFile('index.html');
+ });
+ });
+
+ it('should generate an index.html for custom README', () => {
+ return generateMock(WebsiteGenerator, {
+ 'CustomReadme.md': 'Hello World',
+ 'book.json': '{ "structure": { "readme": "CustomReadme.md" } }'
+ })
+ .then((folder) => {
expect(folder).toHaveFile('index.html');
+ expect(folder).toNotHaveFile('CustomReadme.html');
});
});
- describe('Glossary', function() {
- var folder;
+ describe('Glossary', () => {
+ let folder;
- before(function() {
+ before(() => {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
'SUMMARY.md': '* [Deep](folder/page.md)',
@@ -25,43 +35,30 @@ describe('WebsiteGenerator', function() {
},
'GLOSSARY.md': '# Glossary\n\n## Hello\n\nHello World'
})
- .then(function(_folder) {
+ .then((_folder) => {
folder = _folder;
});
});
- it('should generate a GLOSSARY.html', function() {
+ it('should generate a GLOSSARY.html', () => {
expect(folder).toHaveFile('GLOSSARY.html');
});
- it('should correctly resolve glossary links in README', function() {
- var html = fs.readFileSync(folder + '/index.html', 'utf8');
- expect(html).toHaveDOMElement('.page-inner a[href="GLOSSARY.html#hello"]');
- });
-
- it('should correctly resolve glossary links in directory', function() {
- var html = fs.readFileSync(folder + '/folder/page.html', 'utf8');
- expect(html).toHaveDOMElement('.page-inner a[href="../GLOSSARY.html#hello"]');
- });
-
- it('should accept a custom glossary file', function() {
+ it('should accept a custom glossary file', () => {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
'book.json': '{ "structure": { "glossary": "custom.md" } }',
'custom.md': '# Glossary\n\n## Hello\n\nHello World'
})
- .then(function(folder) {
- expect(folder).toHaveFile('custom.html');
- expect(folder).toNotHaveFile('GLOSSARY.html');
-
- var html = fs.readFileSync(folder + '/index.html', 'utf8');
- expect(html).toHaveDOMElement('.page-inner a[href="custom.html#hello"]');
+ .then((result) => {
+ expect(result).toHaveFile('custom.html');
+ expect(result).toNotHaveFile('GLOSSARY.html');
});
});
});
- it('should copy asset files', function() {
+ it('should copy asset files', () => {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
'myJsFile.js': 'var a = "test";',
@@ -69,14 +66,14 @@ describe('WebsiteGenerator', function() {
'AnotherAssetFile.md': '# Even md'
}
})
- .then(function(folder) {
+ .then((folder) => {
expect(folder).toHaveFile('index.html');
expect(folder).toHaveFile('myJsFile.js');
expect(folder).toHaveFile('folder/AnotherAssetFile.md');
});
});
- it('should generate an index.html for AsciiDoc', function() {
+ it('should generate an index.html for AsciiDoc', () => {
return generateMock(WebsiteGenerator, {
'README.adoc': 'Hello World'
})
@@ -85,7 +82,7 @@ describe('WebsiteGenerator', function() {
});
});
- it('should generate an HTML file for each articles', function() {
+ it('should generate an HTML file for each articles', () => {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
'SUMMARY.md': '# Summary\n\n* [Page](test/page.md)',
@@ -99,7 +96,7 @@ describe('WebsiteGenerator', function() {
});
});
- it('should not generate file if entry file doesn\'t exist', function() {
+ it('should not generate file if entry file doesn\'t exist', () => {
return generateMock(WebsiteGenerator, {
'README.md': 'Hello World',
'SUMMARY.md': '# Summary\n\n* [Page 1](page.md)\n* [Page 2](test/page.md)',
@@ -107,14 +104,14 @@ describe('WebsiteGenerator', function() {
'page.md': 'Hello 2'
}
})
- .then(function(folder) {
+ .then((folder) => {
expect(folder).toHaveFile('index.html');
expect(folder).toNotHaveFile('page.html');
expect(folder).toHaveFile('test/page.html');
});
});
- it('should generate a multilingual book', function() {
+ it('should generate a multilingual book', () => {
return generateMock(WebsiteGenerator, {
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
'en': {
@@ -124,7 +121,7 @@ describe('WebsiteGenerator', function() {
'README.md': 'Bonjour'
}
})
- .then(function(folder) {
+ .then((folder) => {
// It should generate languages
expect(folder).toHaveFile('en/index.html');
expect(folder).toHaveFile('fr/index.html');
@@ -134,11 +131,10 @@ describe('WebsiteGenerator', function() {
expect(folder).toNotHaveFile('fr/README.md');
// Should copy assets only once
- expect(folder).toHaveFile('gitbook/style.css');
- expect(folder).toNotHaveFile('en/gitbook/style.css');
+ expect(folder).toHaveFile('gitbook/core.js');
+ expect(folder).toNotHaveFile('en/gitbook/core.js');
expect(folder).toHaveFile('index.html');
});
});
});
-
diff --git a/lib/output/callHook.js b/packages/gitbook/src/output/callHook.js
index 4914e52..34c16ab 100644
--- a/lib/output/callHook.js
+++ b/packages/gitbook/src/output/callHook.js
@@ -1,6 +1,6 @@
-var Promise = require('../utils/promise');
-var timing = require('../utils/timing');
-var Api = require('../api');
+const Promise = require('../utils/promise');
+const timing = require('../utils/timing');
+const Api = require('../api');
function defaultGetArgument() {
return undefined;
@@ -11,25 +11,25 @@ function defaultHandleResult(output, result) {
}
/**
- Call a "global" hook for an output
-
- @param {String} name
- @param {Function(Output) -> Mixed} getArgument
- @param {Function(Output, result) -> Output} handleResult
- @param {Output} output
- @return {Promise<Output>}
-*/
+ * Call a "global" hook for an output. Hooks are functions exported by plugins.
+ *
+ * @param {String} name
+ * @param {Function(Output) -> Mixed} getArgument
+ * @param {Function(Output, result) -> Output} handleResult
+ * @param {Output} output
+ * @return {Promise<Output>}
+ */
function callHook(name, getArgument, handleResult, output) {
getArgument = getArgument || defaultGetArgument;
handleResult = handleResult || defaultHandleResult;
- var logger = output.getLogger();
- var plugins = output.getPlugins();
+ const logger = output.getLogger();
+ const plugins = output.getPlugins();
logger.debug.ln('calling hook "' + name + '"');
// Create the JS context for plugins
- var context = Api.encodeGlobal(output);
+ const context = Api.encodeGlobal(output);
return timing.measure(
'call.hook.' + name,
@@ -40,7 +40,7 @@ function callHook(name, getArgument, handleResult, output) {
// Call the hooks in serie
.then(function(arg) {
return Promise.reduce(plugins, function(prev, plugin) {
- var hook = plugin.getHook(name);
+ const hook = plugin.getHook(name);
if (!hook) {
return prev;
}
diff --git a/lib/output/callPageHook.js b/packages/gitbook/src/output/callPageHook.js
index c66cef0..0c7adfa 100644
--- a/lib/output/callPageHook.js
+++ b/packages/gitbook/src/output/callPageHook.js
@@ -1,14 +1,14 @@
-var Api = require('../api');
-var callHook = require('./callHook');
+const Api = require('../api');
+const callHook = require('./callHook');
/**
- Call a hook for a specific page
-
- @param {String} name
- @param {Output} output
- @param {Page} page
- @return {Promise<Page>}
-*/
+ * Call a hook for a specific page.
+ *
+ * @param {String} name
+ * @param {Output} output
+ * @param {Page} page
+ * @return {Promise<Page>}
+ */
function callPageHook(name, output, page) {
return callHook(
name,
diff --git a/packages/gitbook/src/output/createTemplateEngine.js b/packages/gitbook/src/output/createTemplateEngine.js
new file mode 100644
index 0000000..f405f36
--- /dev/null
+++ b/packages/gitbook/src/output/createTemplateEngine.js
@@ -0,0 +1,48 @@
+const Templating = require('../templating');
+const TemplateEngine = require('../models/templateEngine');
+
+const Api = require('../api');
+const Plugins = require('../plugins');
+
+const defaultBlocks = require('../constants/defaultBlocks');
+const defaultFilters = require('../constants/defaultFilters');
+
+/**
+ * Create template engine for an output.
+ * It adds default filters/blocks, then add the ones from plugins.
+ *
+ * This template engine is used to compile pages.
+ *
+ * @param {Output} output
+ * @return {TemplateEngine}
+ */
+function createTemplateEngine(output) {
+ const { git } = output;
+ const plugins = output.getPlugins();
+ const book = output.getBook();
+ const rootFolder = book.getContentRoot();
+ const logger = book.getLogger();
+
+ let filters = Plugins.listFilters(plugins);
+ let blocks = Plugins.listBlocks(plugins);
+
+ // Extend with default
+ blocks = defaultBlocks.merge(blocks);
+ filters = defaultFilters.merge(filters);
+
+ // Create loader
+ const transformFn = Templating.replaceShortcuts.bind(null, blocks);
+ const loader = new Templating.ConrefsLoader(rootFolder, transformFn, logger, git);
+
+ // Create API context
+ const context = Api.encodeGlobal(output);
+
+ return new TemplateEngine({
+ filters,
+ blocks,
+ loader,
+ context
+ });
+}
+
+module.exports = createTemplateEngine;
diff --git a/lib/output/ebook/getConvertOptions.js b/packages/gitbook/src/output/ebook/getConvertOptions.js
index bc80493..b37c68e 100644
--- a/lib/output/ebook/getConvertOptions.js
+++ b/packages/gitbook/src/output/ebook/getConvertOptions.js
@@ -1,8 +1,8 @@
-var extend = require('extend');
+const extend = require('extend');
-var Promise = require('../../utils/promise');
-var getPDFTemplate = require('./getPDFTemplate');
-var getCoverPath = require('./getCoverPath');
+const Promise = require('../../utils/promise');
+const getPDFTemplate = require('./getPDFTemplate');
+const getCoverPath = require('./getCoverPath');
/**
Generate options for ebook-convert
@@ -11,16 +11,16 @@ var getCoverPath = require('./getCoverPath');
@return {Promise<Object>}
*/
function getConvertOptions(output) {
- var options = output.getOptions();
- var format = options.get('format');
+ const options = output.getOptions();
+ const format = options.get('format');
- var book = output.getBook();
- var config = book.getConfig();
+ const book = output.getBook();
+ const config = book.getConfig();
return Promise()
.then(function() {
- var coverPath = getCoverPath(output);
- var options = {
+ const coverPath = getCoverPath(output);
+ let options = {
'--cover': coverPath,
'--title': config.getValue('title'),
'--comments': config.getValue('description'),
@@ -36,7 +36,7 @@ function getConvertOptions(output) {
'--max-levels': '1',
'--no-chapters-in-toc': true,
'--breadth-first': true,
- '--dont-split-on-page-breaks': format === 'epub'? true : undefined
+ '--dont-split-on-page-breaks': format === 'epub' ? true : undefined
};
if (format !== 'pdf') {
@@ -48,7 +48,7 @@ function getConvertOptions(output) {
getPDFTemplate(output, 'footer')
])
.spread(function(headerTpl, footerTpl) {
- var pdfOptions = config.getValue('pdf').toJS();
+ const pdfOptions = config.getValue('pdf').toJS();
return options = extend(options, {
'--chapter-mark': String(pdfOptions.chapterMark),
diff --git a/lib/output/ebook/getCoverPath.js b/packages/gitbook/src/output/ebook/getCoverPath.js
index ab6b579..cf18c8d 100644
--- a/lib/output/ebook/getCoverPath.js
+++ b/packages/gitbook/src/output/ebook/getCoverPath.js
@@ -1,5 +1,5 @@
-var path = require('path');
-var fs = require('../../utils/fs');
+const path = require('path');
+const fs = require('../../utils/fs');
/**
Resolve path to cover file to use
@@ -8,13 +8,13 @@ var fs = require('../../utils/fs');
@return {String}
*/
function getCoverPath(output) {
- var outputRoot = output.getRoot();
- var book = output.getBook();
- var config = book.getConfig();
- var coverName = config.getValue('cover', 'cover.jpg');
+ const outputRoot = output.getRoot();
+ const book = output.getBook();
+ const config = book.getConfig();
+ const coverName = config.getValue('cover', 'cover.jpg');
// Resolve to absolute
- var cover = fs.pickFile(outputRoot, coverName);
+ let cover = fs.pickFile(outputRoot, coverName);
if (cover) {
return cover;
}
diff --git a/packages/gitbook/src/output/ebook/getPDFTemplate.js b/packages/gitbook/src/output/ebook/getPDFTemplate.js
new file mode 100644
index 0000000..53c7a82
--- /dev/null
+++ b/packages/gitbook/src/output/ebook/getPDFTemplate.js
@@ -0,0 +1,36 @@
+const juice = require('juice');
+
+const JSONUtils = require('../../json');
+const render = require('../../browser/render');
+const Promise = require('../../utils/promise');
+
+/**
+ * Generate PDF header/footer templates
+ *
+ * @param {Output} output
+ * @param {String} type ("footer" or "header")
+ * @return {String} html
+ */
+function getPDFTemplate(output, type) {
+ const outputRoot = output.getRoot();
+ const plugins = output.getPlugins();
+
+ // Generate initial state
+ const initialState = JSONUtils.encodeState(output);
+ initialState.page = {
+ num: '_PAGENUM_',
+ title: '_SECTION_'
+ };
+
+ // Render the theme
+ const html = render(plugins, initialState, 'ebook', `pdf:${type}`);
+
+ // Inline CSS
+ return Promise.nfcall(juice.juiceResources, html, {
+ webResources: {
+ relativeTo: outputRoot
+ }
+ });
+}
+
+module.exports = getPDFTemplate;
diff --git a/lib/output/ebook/index.js b/packages/gitbook/src/output/ebook/index.js
index 786a10a..c5c07c2 100644
--- a/lib/output/ebook/index.js
+++ b/packages/gitbook/src/output/ebook/index.js
@@ -1,5 +1,5 @@
-var extend = require('extend');
-var WebsiteGenerator = require('../website');
+const extend = require('extend');
+const WebsiteGenerator = require('../website');
module.exports = extend({}, WebsiteGenerator, {
name: 'ebook',
diff --git a/packages/gitbook/src/output/ebook/onFinish.js b/packages/gitbook/src/output/ebook/onFinish.js
new file mode 100644
index 0000000..7db757f
--- /dev/null
+++ b/packages/gitbook/src/output/ebook/onFinish.js
@@ -0,0 +1,85 @@
+const path = require('path');
+
+const JSONUtils = require('../../json');
+const Promise = require('../../utils/promise');
+const error = require('../../utils/error');
+const command = require('../../utils/command');
+const writeFile = require('../helper/writeFile');
+const render = require('../../browser/render');
+
+const getConvertOptions = require('./getConvertOptions');
+const SUMMARY_FILE = 'SUMMARY.html';
+
+/**
+ * Write the SUMMARY.html
+ *
+ * @param {Output} output
+ * @return {Output} output
+ */
+function writeSummary(output) {
+ const plugins = output.getPlugins();
+
+ // Generate initial state
+ const initialState = JSONUtils.encodeState(output);
+
+ // Render using React
+ const html = render(plugins, initialState, 'ebook', 'ebook:summary');
+
+ return writeFile(output, SUMMARY_FILE, html);
+}
+
+/**
+ * Generate the ebook file as "index.pdf"
+ *
+ * @param {Output} output
+ * @return {Output} output
+ */
+function runEbookConvert(output) {
+ const logger = output.getLogger();
+ const options = output.getOptions();
+ const format = options.get('format');
+ const outputFolder = output.getRoot();
+
+ if (!format) {
+ return Promise(output);
+ }
+
+ return getConvertOptions(output)
+ .then(function(options) {
+ const cmd = [
+ 'ebook-convert',
+ path.resolve(outputFolder, SUMMARY_FILE),
+ path.resolve(outputFolder, 'index.' + format),
+ command.optionsToShellArgs(options)
+ ].join(' ');
+
+ return command.exec(cmd)
+ .progress(function(data) {
+ logger.debug(data);
+ })
+ .fail(function(err) {
+ if (err.code == 127) {
+ throw error.RequireInstallError({
+ cmd: 'ebook-convert',
+ install: 'Install it from Calibre: https://calibre-ebook.com'
+ });
+ }
+
+ throw error.EbookError(err);
+ });
+ })
+ .thenResolve(output);
+}
+
+/**
+ * Finish the generation, generates the SUMMARY.html
+ *
+ * @param {Output} output
+ * @return {Output} output
+ */
+function onFinish(output) {
+ return writeSummary(output)
+ .then(runEbookConvert);
+}
+
+module.exports = onFinish;
diff --git a/lib/output/ebook/onPage.js b/packages/gitbook/src/output/ebook/onPage.js
index b7b9b42..a7c2137 100644
--- a/lib/output/ebook/onPage.js
+++ b/packages/gitbook/src/output/ebook/onPage.js
@@ -1,14 +1,15 @@
-var WebsiteGenerator = require('../website');
-var Modifiers = require('../modifiers');
+const WebsiteGenerator = require('../website');
+const Modifiers = require('../modifiers');
/**
- Write a page for ebook output
-
- @param {Output} output
- @param {Output}
-*/
+ * Write a page for ebook output. It renders it just as the website generator
+ * except that it inline assets.
+ *
+ * @param {Output} output
+ * @param {Output} output
+ */
function onPage(output, page) {
- var options = output.getOptions();
+ const options = output.getOptions();
// Inline assets
return Modifiers.modifyHTML(page, [
diff --git a/packages/gitbook/src/output/ebook/options.js b/packages/gitbook/src/output/ebook/options.js
new file mode 100644
index 0000000..d192fd2
--- /dev/null
+++ b/packages/gitbook/src/output/ebook/options.js
@@ -0,0 +1,14 @@
+const Immutable = require('immutable');
+
+const Options = Immutable.Record({
+ // Root folder for the output
+ root: String(),
+ // Prefix for generation
+ prefix: String('ebook'),
+ // Format to generate using ebook-convert
+ format: String(),
+ // Force use of absolute urls ("index.html" instead of "/")
+ directoryIndex: Boolean(false)
+});
+
+module.exports = Options;
diff --git a/lib/output/generateAssets.js b/packages/gitbook/src/output/generateAssets.js
index 7a6e104..f926492 100644
--- a/lib/output/generateAssets.js
+++ b/packages/gitbook/src/output/generateAssets.js
@@ -1,15 +1,15 @@
-var Promise = require('../utils/promise');
+const Promise = require('../utils/promise');
/**
- Output all assets using a generator
-
- @param {Generator} generator
- @param {Output} output
- @return {Promise<Output>}
-*/
+ * Output all assets using a generator
+ *
+ * @param {Generator} generator
+ * @param {Output} output
+ * @return {Promise<Output>}
+ */
function generateAssets(generator, output) {
- var assets = output.getAssets();
- var logger = output.getLogger();
+ const assets = output.getAssets();
+ const logger = output.getLogger();
// Is generator ignoring assets?
if (!generator.onAsset) {
diff --git a/lib/output/generateBook.js b/packages/gitbook/src/output/generateBook.js
index 46712bd..0e2c230 100644
--- a/lib/output/generateBook.js
+++ b/packages/gitbook/src/output/generateBook.js
@@ -1,16 +1,16 @@
-var path = require('path');
-var Immutable = require('immutable');
+const path = require('path');
+const Immutable = require('immutable');
-var Output = require('../models/output');
-var Promise = require('../utils/promise');
-var fs = require('../utils/fs');
+const Output = require('../models/output');
+const Promise = require('../utils/promise');
+const fs = require('../utils/fs');
-var callHook = require('./callHook');
-var preparePlugins = require('./preparePlugins');
-var preparePages = require('./preparePages');
-var prepareAssets = require('./prepareAssets');
-var generateAssets = require('./generateAssets');
-var generatePages = require('./generatePages');
+const callHook = require('./callHook');
+const preparePlugins = require('./preparePlugins');
+const preparePages = require('./preparePages');
+const prepareAssets = require('./prepareAssets');
+const generateAssets = require('./generateAssets');
+const generatePages = require('./generatePages');
/**
* Process an output to generate the book
@@ -29,15 +29,15 @@ function processOutput(generator, startOutput) {
callHook.bind(null,
'config',
function(output) {
- var book = output.getBook();
- var config = book.getConfig();
- var values = config.getValues();
+ const book = output.getBook();
+ const config = book.getConfig();
+ const values = config.getValues();
return values.toJS();
},
function(output, result) {
- var book = output.getBook();
- var config = book.getConfig();
+ let book = output.getBook();
+ let config = book.getConfig();
config = config.updateValues(result);
book = book.set('config', config);
@@ -58,7 +58,7 @@ function processOutput(generator, startOutput) {
)
)
- .then(function(output) {
+ .then((output) => {
if (!generator.onInit) {
return output;
}
@@ -69,29 +69,29 @@ function processOutput(generator, startOutput) {
.then(generateAssets.bind(null, generator))
.then(generatePages.bind(null, generator))
- .tap(function(output) {
- var book = output.getBook();
+ .tap((output) => {
+ const book = output.getBook();
if (!book.isMultilingual()) {
return;
}
- var logger = book.getLogger();
- var books = book.getBooks();
- var outputRoot = output.getRoot();
- var plugins = output.getPlugins();
- var state = output.getState();
- var options = output.getOptions();
+ const logger = book.getLogger();
+ const books = book.getBooks();
+ const outputRoot = output.getRoot();
+ const plugins = output.getPlugins();
+ const state = output.getState();
+ const options = output.getOptions();
return Promise.forEach(books, function(langBook) {
// Inherits plugins list, options and state
- var langOptions = options.set('root', path.join(outputRoot, langBook.getLanguage()));
- var langOutput = new Output({
+ const langOptions = options.set('root', path.join(outputRoot, langBook.getLanguage()));
+ const langOutput = new Output({
book: langBook,
options: langOptions,
- state: state,
+ state,
generator: generator.name,
- plugins: plugins
+ plugins
});
logger.info.ln('');
@@ -111,7 +111,7 @@ function processOutput(generator, startOutput) {
)
)
- .then(function(output) {
+ .then((output) => {
if (!generator.onFinish) {
return output;
}
@@ -154,35 +154,35 @@ function processOutput(generator, startOutput) {
*/
function generateBook(generator, book, options) {
options = generator.Options(options);
- var state = generator.State? generator.State({}) : Immutable.Map();
- var start = Date.now();
+ const state = generator.State ? generator.State({}) : Immutable.Map();
+ const start = Date.now();
return Promise(
new Output({
- book: book,
- options: options,
- state: state,
+ book,
+ options,
+ state,
generator: generator.name
})
)
// Cleanup output folder
- .then(function(output) {
- var logger = output.getLogger();
- var rootFolder = output.getRoot();
+ .then((output) => {
+ const logger = output.getLogger();
+ const rootFolder = output.getRoot();
logger.debug.ln('cleanup folder "' + rootFolder + '"');
return fs.ensureFolder(rootFolder)
.thenResolve(output);
})
- .then(processOutput.bind(null, generator))
+ .then(output => processOutput(generator, output))
// Log duration and end message
- .then(function(output) {
- var logger = output.getLogger();
- var end = Date.now();
- var duration = (end - start)/1000;
+ .then((output) => {
+ const logger = output.getLogger();
+ const end = Date.now();
+ const duration = (end - start) / 1000;
logger.info.ok('generation finished with success in ' + duration.toFixed(1) + 's !');
diff --git a/packages/gitbook/src/output/generatePage.js b/packages/gitbook/src/output/generatePage.js
new file mode 100644
index 0000000..7375f1d
--- /dev/null
+++ b/packages/gitbook/src/output/generatePage.js
@@ -0,0 +1,68 @@
+const path = require('path');
+
+const Promise = require('../utils/promise');
+const error = require('../utils/error');
+const timing = require('../utils/timing');
+
+const Templating = require('../templating');
+const JSONUtils = require('../json');
+const createTemplateEngine = require('./createTemplateEngine');
+const callPageHook = require('./callPageHook');
+
+/**
+ * Prepare and generate HTML for a page
+ *
+ * @param {Output} output
+ * @param {Page} page
+ * @return {Promise<Page>}
+ */
+function generatePage(output, page) {
+ const book = output.getBook();
+ const engine = createTemplateEngine(output);
+
+ return timing.measure(
+ 'page.generate',
+ Promise(page)
+ .then(function(resultPage) {
+ const file = resultPage.getFile();
+ const filePath = file.getPath();
+ const parser = file.getParser();
+ const context = JSONUtils.encodeState(output, resultPage);
+
+ if (!parser) {
+ return Promise.reject(error.FileNotParsableError({
+ filename: filePath
+ }));
+ }
+
+ // Call hook "page:before"
+ return callPageHook('page:before', output, resultPage)
+
+ // Escape code blocks with raw tags
+ .then((currentPage) => {
+ return parser.preparePage(currentPage.getContent());
+ })
+
+ // Render templating syntax
+ .then((content) => {
+ const absoluteFilePath = path.join(book.getContentRoot(), filePath);
+ return Templating.render(engine, absoluteFilePath, content, context);
+ })
+
+ // Parse with markdown/asciidoc parser
+ .then((content) => parser.parsePage(content))
+
+ // Return new page
+ .then(({content}) => {
+ return resultPage.set('content', content);
+ })
+
+ // Call final hook
+ .then((currentPage) => {
+ return callPageHook('page', output, currentPage);
+ });
+ })
+ );
+}
+
+module.exports = generatePage;
diff --git a/lib/output/generatePages.js b/packages/gitbook/src/output/generatePages.js
index 73c5c09..21b6610 100644
--- a/lib/output/generatePages.js
+++ b/packages/gitbook/src/output/generatePages.js
@@ -1,5 +1,5 @@
-var Promise = require('../utils/promise');
-var generatePage = require('./generatePage');
+const Promise = require('../utils/promise');
+const generatePage = require('./generatePage');
/**
Output all pages using a generator
@@ -9,8 +9,8 @@ var generatePage = require('./generatePage');
@return {Promise<Output>}
*/
function generatePages(generator, output) {
- var pages = output.getPages();
- var logger = output.getLogger();
+ const pages = output.getPages();
+ const logger = output.getLogger();
// Is generator ignoring assets?
if (!generator.onPage) {
@@ -18,7 +18,7 @@ function generatePages(generator, output) {
}
return Promise.reduce(pages, function(out, page) {
- var file = page.getFile();
+ const file = page.getFile();
logger.debug.ln('generate page "' + file.getPath() + '"');
diff --git a/packages/gitbook/src/output/getModifiers.js b/packages/gitbook/src/output/getModifiers.js
new file mode 100644
index 0000000..3007b02
--- /dev/null
+++ b/packages/gitbook/src/output/getModifiers.js
@@ -0,0 +1,42 @@
+const Modifiers = require('./modifiers');
+
+/**
+ * Return default modifier to prepare a page for
+ * rendering.
+ *
+ * @return {Array<Modifier>}
+ */
+function getModifiers(output, page) {
+ const book = output.getBook();
+ const glossary = book.getGlossary();
+ const file = page.getFile();
+
+ // Map of urls
+ const urls = output.getURLIndex();
+
+ // Glossary entries
+ const entries = glossary.getEntries();
+ const glossaryFile = glossary.getFile();
+ const glossaryFilename = urls.resolveToURL(glossaryFile.getPath());
+
+ // Current file path
+ const currentFilePath = file.getPath();
+
+ return [
+ // Normalize IDs on headings
+ Modifiers.addHeadingId,
+
+ // Annotate text with glossary entries
+ Modifiers.annotateText.bind(null, entries, glossaryFilename),
+
+ // Resolve images
+ Modifiers.resolveImages.bind(null, currentFilePath),
+
+ // Resolve links (.md -> .html)
+ Modifiers.resolveLinks.bind(null,
+ (filePath => urls.resolveToURLFrom(currentFilePath, filePath))
+ )
+ ];
+}
+
+module.exports = getModifiers;
diff --git a/lib/output/helper/index.js b/packages/gitbook/src/output/helper/index.js
index f8bc109..f8bc109 100644
--- a/lib/output/helper/index.js
+++ b/packages/gitbook/src/output/helper/index.js
diff --git a/lib/output/helper/writeFile.js b/packages/gitbook/src/output/helper/writeFile.js
index a6d4645..01a8e68 100644
--- a/lib/output/helper/writeFile.js
+++ b/packages/gitbook/src/output/helper/writeFile.js
@@ -1,5 +1,5 @@
-var path = require('path');
-var fs = require('../../utils/fs');
+const path = require('path');
+const fs = require('../../utils/fs');
/**
Write a file to the output folder
@@ -10,7 +10,7 @@ var fs = require('../../utils/fs');
@return {Promise}
*/
function writeFile(output, filePath, content) {
- var rootFolder = output.getRoot();
+ const rootFolder = output.getRoot();
filePath = path.join(rootFolder, filePath);
return fs.ensureFile(filePath)
diff --git a/lib/output/index.js b/packages/gitbook/src/output/index.js
index 9b8ec17..574b3df 100644
--- a/lib/output/index.js
+++ b/packages/gitbook/src/output/index.js
@@ -1,6 +1,6 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var generators = Immutable.List([
+const generators = Immutable.List([
require('./json'),
require('./website'),
require('./ebook')
@@ -20,5 +20,5 @@ function getGenerator(name) {
module.exports = {
generate: require('./generateBook'),
- getGenerator: getGenerator
+ getGenerator
};
diff --git a/lib/output/json/index.js b/packages/gitbook/src/output/json/index.js
index 361da06..361da06 100644
--- a/lib/output/json/index.js
+++ b/packages/gitbook/src/output/json/index.js
diff --git a/lib/output/json/onFinish.js b/packages/gitbook/src/output/json/onFinish.js
index d41d778..24f5159 100644
--- a/lib/output/json/onFinish.js
+++ b/packages/gitbook/src/output/json/onFinish.js
@@ -1,35 +1,36 @@
-var path = require('path');
+const path = require('path');
-var Promise = require('../../utils/promise');
-var fs = require('../../utils/fs');
-var JSONUtils = require('../../json');
+const Promise = require('../../utils/promise');
+const fs = require('../../utils/fs');
+const JSONUtils = require('../../json');
/**
- Finish the generation
-
- @param {Output}
- @return {Output}
-*/
+ * Finish the generation
+ *
+ * @param {Output}
+ * @return {Output}
+ */
function onFinish(output) {
- var book = output.getBook();
- var outputRoot = output.getRoot();
+ const book = output.getBook();
+ const outputRoot = output.getRoot();
+ const urls = output.getURLIndex();
if (!book.isMultilingual()) {
return Promise(output);
}
// Get main language
- var languages = book.getLanguages();
- var mainLanguage = languages.getDefaultLanguage();
+ const languages = book.getLanguages();
+ const mainLanguage = languages.getDefaultLanguage();
// Read the main JSON
return fs.readFile(path.resolve(outputRoot, mainLanguage.getID(), 'README.json'), 'utf8')
// Extend the JSON
.then(function(content) {
- var json = JSON.parse(content);
+ const json = JSON.parse(content);
- json.languages = JSONUtils.encodeLanguages(languages);
+ json.languages = JSONUtils.encodeLanguages(languages, urls);
return json;
})
diff --git a/lib/output/json/onPage.js b/packages/gitbook/src/output/json/onPage.js
index 2315ba0..f31fadc 100644
--- a/lib/output/json/onPage.js
+++ b/packages/gitbook/src/output/json/onPage.js
@@ -1,10 +1,10 @@
-var JSONUtils = require('../../json');
-var PathUtils = require('../../utils/path');
-var Modifiers = require('../modifiers');
-var writeFile = require('../helper/writeFile');
-var getModifiers = require('../getModifiers');
+const JSONUtils = require('../../json');
+const PathUtils = require('../../utils/path');
+const Modifiers = require('../modifiers');
+const writeFile = require('../helper/writeFile');
+const getModifiers = require('../getModifiers');
-var JSON_VERSION = '3';
+const JSON_VERSION = '3';
/**
* Write a page as a json file
@@ -13,13 +13,13 @@ var JSON_VERSION = '3';
* @param {Page} page
*/
function onPage(output, page) {
- var file = page.getFile();
- var readme = output.getBook().getReadme().getFile();
+ const file = page.getFile();
+ const readme = output.getBook().getReadme().getFile();
return Modifiers.modifyHTML(page, getModifiers(output, page))
.then(function(resultPage) {
// Generate the JSON
- var json = JSONUtils.encodeBookWithPage(output.getBook(), resultPage);
+ const json = JSONUtils.encodeState(output, resultPage);
// Delete some private properties
delete json.config;
@@ -28,7 +28,7 @@ function onPage(output, page) {
json.version = JSON_VERSION;
// File path in the output folder
- var filePath = file.getPath() == readme.getPath()? 'README.json' : file.getPath();
+ let filePath = file.getPath() == readme.getPath() ? 'README.json' : file.getPath();
filePath = PathUtils.setExtension(filePath, '.json');
// Write it to the disk
diff --git a/lib/output/json/options.js b/packages/gitbook/src/output/json/options.js
index 79167b1..2a9de0e 100644
--- a/lib/output/json/options.js
+++ b/packages/gitbook/src/output/json/options.js
@@ -1,6 +1,6 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var Options = Immutable.Record({
+const Options = Immutable.Record({
// Root folder for the output
root: String()
});
diff --git a/lib/output/modifiers/__tests__/addHeadingId.js b/packages/gitbook/src/output/modifiers/__tests__/addHeadingId.js
index a3b1d81..4d77e75 100644
--- a/lib/output/modifiers/__tests__/addHeadingId.js
+++ b/packages/gitbook/src/output/modifiers/__tests__/addHeadingId.js
@@ -1,26 +1,25 @@
-var cheerio = require('cheerio');
-var addHeadingId = require('../addHeadingId');
+const cheerio = require('cheerio');
+const addHeadingId = require('../addHeadingId');
describe('addHeadingId', function() {
it('should add an ID if none', function() {
- var $ = cheerio.load('<h1>Hello World</h1><h2>Cool !!</h2>');
+ const $ = cheerio.load('<h1>Hello World</h1><h2>Cool !!</h2>');
return addHeadingId($)
.then(function() {
- var html = $.html();
+ const html = $.html();
expect(html).toBe('<h1 id="hello-world">Hello World</h1><h2 id="cool-">Cool !!</h2>');
});
});
it('should not change existing IDs', function() {
- var $ = cheerio.load('<h1 id="awesome">Hello World</h1>');
+ const $ = cheerio.load('<h1 id="awesome">Hello World</h1>');
return addHeadingId($)
.then(function() {
- var html = $.html();
+ const html = $.html();
expect(html).toBe('<h1 id="awesome">Hello World</h1>');
});
});
});
-
diff --git a/lib/output/modifiers/__tests__/annotateText.js b/packages/gitbook/src/output/modifiers/__tests__/annotateText.js
index 67e7a10..28a5cc5 100644
--- a/lib/output/modifiers/__tests__/annotateText.js
+++ b/packages/gitbook/src/output/modifiers/__tests__/annotateText.js
@@ -1,46 +1,45 @@
-var Immutable = require('immutable');
-var cheerio = require('cheerio');
-var GlossaryEntry = require('../../../models/glossaryEntry');
-var annotateText = require('../annotateText');
+const Immutable = require('immutable');
+const cheerio = require('cheerio');
+const GlossaryEntry = require('../../../models/glossaryEntry');
+const annotateText = require('../annotateText');
describe('annotateText', function() {
- var entries = Immutable.List([
+ const entries = Immutable.List([
GlossaryEntry({ name: 'Word' }),
GlossaryEntry({ name: 'Multiple Words' })
]);
it('should annotate text', function() {
- var $ = cheerio.load('<p>This is a word, and multiple words</p>');
+ const $ = cheerio.load('<p>This is a word, and multiple words</p>');
annotateText(entries, 'GLOSSARY.md', $);
- var links = $('a');
+ const links = $('a');
expect(links.length).toBe(2);
- var word = $(links.get(0));
+ const word = $(links.get(0));
expect(word.attr('href')).toBe('/GLOSSARY.md#word');
expect(word.text()).toBe('word');
expect(word.hasClass('glossary-term')).toBeTruthy();
- var words = $(links.get(1));
+ const words = $(links.get(1));
expect(words.attr('href')).toBe('/GLOSSARY.md#multiple-words');
expect(words.text()).toBe('multiple words');
expect(words.hasClass('glossary-term')).toBeTruthy();
});
it('should not annotate scripts', function() {
- var $ = cheerio.load('<script>This is a word, and multiple words</script>');
+ const $ = cheerio.load('<script>This is a word, and multiple words</script>');
annotateText(entries, 'GLOSSARY.md', $);
expect($('a').length).toBe(0);
});
it('should not annotate when has class "no-glossary"', function() {
- var $ = cheerio.load('<p class="no-glossary">This is a word, and multiple words</p>');
+ const $ = cheerio.load('<p class="no-glossary">This is a word, and multiple words</p>');
annotateText(entries, 'GLOSSARY.md', $);
expect($('a').length).toBe(0);
});
});
-
diff --git a/packages/gitbook/src/output/modifiers/__tests__/fetchRemoteImages.js b/packages/gitbook/src/output/modifiers/__tests__/fetchRemoteImages.js
new file mode 100644
index 0000000..9145cae
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/__tests__/fetchRemoteImages.js
@@ -0,0 +1,39 @@
+const cheerio = require('cheerio');
+const tmp = require('tmp');
+const path = require('path');
+
+const URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png';
+
+describe('fetchRemoteImages', function() {
+ let dir;
+ const fetchRemoteImages = require('../fetchRemoteImages');
+
+ beforeEach(function() {
+ dir = tmp.dirSync();
+ });
+
+ it('should download image file', function() {
+ const $ = cheerio.load('<img src="' + URL + '" />');
+
+ return fetchRemoteImages(dir.name, 'index.html', $)
+ .then(function() {
+ const $img = $('img');
+ const src = $img.attr('src');
+
+ expect(dir.name).toHaveFile(src);
+ });
+ });
+
+ it('should download image file and replace with relative path', function() {
+ const $ = cheerio.load('<img src="' + URL + '" />');
+
+ return fetchRemoteImages(dir.name, 'test/index.html', $)
+ .then(function() {
+ const $img = $('img');
+ const src = $img.attr('src');
+
+ expect(dir.name).toHaveFile(path.join('test', src));
+ });
+ });
+});
+
diff --git a/packages/gitbook/src/output/modifiers/__tests__/inlinePng.js b/packages/gitbook/src/output/modifiers/__tests__/inlinePng.js
new file mode 100644
index 0000000..fd031b0
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/__tests__/inlinePng.js
@@ -0,0 +1,24 @@
+const cheerio = require('cheerio');
+const tmp = require('tmp');
+const inlinePng = require('../inlinePng');
+
+describe('inlinePng', function() {
+ let dir;
+
+ beforeEach(function() {
+ dir = tmp.dirSync();
+ });
+
+ it('should write an inline PNG using data URI as a file', function() {
+ const $ = cheerio.load('<img alt="GitBook Logo 20x20" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUEAYAAADdGcFOAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAF+klEQVRIDY3Wf5CVVR3H8c9z791fyI9dQwdQ4TTI7wEWnQZZAa/mJE4Z0OaKUuN1KoaykZxUGGHay+iIVFMoEYrUPhDCKEKW2ChT8dA0RCSxWi6EW3sYYpcfxq5C+4O9957O+7m7O/qHQ9/XzH1+nHuec57z8wkWTsKw0y6N/LxXN6KzTnEUHi8eP/l3YStSU/MdsYvBbGh8six2YXcbcgc++QkfTQkWz/81KtqDA0hlUoWnsX+5uxe5X365BB9my2bjrHNHccLk16BpS9CExjcmXMDbD6wehdyEjxbjz1uK1zn9qga6dcfnMLXeXY/qjuQqTF4W1MKke8ZgeNhjMCxMPIWSd4OF78C55CFI/1kF6WwXpMqjkAZ/CKniNDrCsmU4lE1YbPlgR2x7R39FF23D4mq3A1+Z35PGTNs1E1XhxcGQOh6HNPwXkK56BVJhOaRg/pvoHXNxHFw410B25EYE2RMvI0i/twFJvXcrFObykEa+DmnQGLwYqR0l2a6JqItaj8C/4E2QxtZCofkC8tF1t8HZc/fAZaLnIF2xEsoEtW1w7vBSSFtfhDTnCki9cSi81Ain1uko2Ld+Dmf2rkUq0/5t+PYbFtPQdkjzNiAXTWtDEF49FgkzJInAVPwNyhzcDOmrdZCm/Rn+ebWtcPs+/U24hmg2XL0rRkPPELh9R8fDtXR2oC/VuZbGaci79Ajkb6lZgfyYtyzy/X9s6T/pO/ZfN/RdNxxIwTWM2wbX8KVmuIaEqmKm6zEondwGpd0SyOy5DrJ//TFkX9kMhd3XQHbEVCSsm4OECV5HIv2p15CwfWPSntoHRbv2Q1HzSvSlSqZwATIuBxk/zZBOBbdB+u9hSKU3Q7pwAjInZkFm6U8hu7MSMqe/Dqn8fUj5GVCmpxK+4N/F1LMa0p5eSOPqIPP7NGSunAI/+R6GnzQzIBt8A1LC/QZ+6HwLst1rITv0n5CtXgSZ78yFTNkR+FdeDZneJkip3fAtsQ5Scilkek7CH9dAmjIWvkK7IXXOh6/IzZDNPQdZXR1TQmdjKv0ZfEu0YKDpNflpyG5aDtnRv8VAuu3dBV+huyBbvgdS97tQNLQc0mfugKy5Cb4BipPIXvsUpK5N8Mvao/Bd3QDZRH9Rrtj3Cl6FHwPFMLmNkKrj8BnHoT+XX6f2wl+XxFS4Ab7C72Dgf7bi+5DpTkNm8kQMpCs/BzIlz8LfPxnzLdh3EjwMX4GX4Ju4GNb9A1L7k/D3J8b6kv2LFCtmCmcgUzoJsr2z4MfwFsh87xikZefg188fYaAhpPUxm3ge/vFnYkoED0HqeQiyJYcwkNGWnoNv6s9C1p1Bf/389VYoCjohW7UfMms3wXdpBv7+FEiPLIHs4DIMNERUNhbSpY3wk6QOsqlCDVx2xCrInMpBmfNPQOnzKxBkkrugdOl9GKigSZZCUWIm/GqwDtLUI5D+WAOlb9wKP0YvQLbjZSjsaYaL/n0/FA3fDtnCGihK5UYjCK+ZDr+TDIKLdm2Fs1UOzo76F5wO74XSZj0S6d7RCMLkCshcXALZxaWQRjXDZQ62oRAdCeG/Ju5HELX2QFH3C0hkRy6GovyfwF58AoVbguOxyB2H7/I34Gf11yANnQSp7Vr4MbQH0vg7kbNNp5AM3UrIVDchnz56B1Jm573wW9gZSFVPwO/hefg5FsIvN09CchtQCIOFw/F5U8ii3CZn4cqo7C8YlXEPYkx9cacZl00+iwnprrtwVdj1Q/gXmAs/pu6LZc9XQOGgSvh19n2cDZN341g2EcfxTEGwH/RewqlMsUfbbWIGLjUG+j/j9nokD1beiOvLS5dhjr30Gu6ZnivgdtM/6VJvY1+6pBHbH+h9CX84vfMxNJtisYVFlys+WNCIZJNmIsjohlhNSQC3f8R55H+y/hjkN8GPR9ndCLJxT4/3n0Px51ay8XQnNrYfDJHf//Fc0oMrEZSeeQGJ7+Z+gKCgLbHNWgXnB9FlYt5JaN38JIINC95EakjtAqQeuUx21c5B6tEFf0fSfbEFQf28Z6D6y+X/H0jf40QQJhYwAAAAAElFTkSuQmCC"/>');
+
+ return inlinePng(dir.name, 'index.html', $)
+ .then(function() {
+ const $img = $('img');
+ const src = $img.attr('src');
+
+ expect(dir.name).toHaveFile(src);
+ });
+ });
+});
+
diff --git a/packages/gitbook/src/output/modifiers/__tests__/resolveLinks.js b/packages/gitbook/src/output/modifiers/__tests__/resolveLinks.js
new file mode 100644
index 0000000..d11a31f
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/__tests__/resolveLinks.js
@@ -0,0 +1,34 @@
+const cheerio = require('cheerio');
+const resolveLinks = require('../resolveLinks');
+
+describe('resolveLinks', () => {
+ function resolveFileBasic(href) {
+ return 'fakeDir/' + href;
+ }
+
+ it('should resolve path using resolver', () => {
+ const TEST = '<p>This is a <a href="test/cool.md"></a></p>';
+ const $ = cheerio.load(TEST);
+
+ return resolveLinks(resolveFileBasic, $)
+ .then(function() {
+ const link = $('a');
+ expect(link.attr('href')).toBe('fakeDir/test/cool.md');
+ });
+ });
+
+ describe('External link', () => {
+ const TEST = '<p>This is a <a href="http://www.github.com">external link</a></p>';
+
+ it('should have target="_blank" attribute', () => {
+ const $ = cheerio.load(TEST);
+
+ return resolveLinks(resolveFileBasic, $)
+ .then(function() {
+ const link = $('a');
+ expect(link.attr('target')).toBe('_blank');
+ });
+ });
+ });
+
+});
diff --git a/packages/gitbook/src/output/modifiers/__tests__/svgToImg.js b/packages/gitbook/src/output/modifiers/__tests__/svgToImg.js
new file mode 100644
index 0000000..4bdab59
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/__tests__/svgToImg.js
@@ -0,0 +1,24 @@
+const cheerio = require('cheerio');
+const tmp = require('tmp');
+
+describe('svgToImg', function() {
+ let dir;
+ const svgToImg = require('../svgToImg');
+
+ beforeEach(function() {
+ dir = tmp.dirSync();
+ });
+
+ it('should write svg as a file', function() {
+ const $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>');
+
+ return svgToImg(dir.name, 'index.html', $)
+ .then(function() {
+ const $img = $('img');
+ const src = $img.attr('src');
+
+ expect(dir.name).toHaveFile(src);
+ });
+ });
+});
+
diff --git a/packages/gitbook/src/output/modifiers/__tests__/svgToPng.js b/packages/gitbook/src/output/modifiers/__tests__/svgToPng.js
new file mode 100644
index 0000000..0a12938
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/__tests__/svgToPng.js
@@ -0,0 +1,32 @@
+const cheerio = require('cheerio');
+const tmp = require('tmp');
+const path = require('path');
+
+const svgToImg = require('../svgToImg');
+const svgToPng = require('../svgToPng');
+
+describe('svgToPng', function() {
+ let dir;
+
+ beforeEach(function() {
+ dir = tmp.dirSync();
+ });
+
+ it('should write svg as png file', function() {
+ const $ = cheerio.load('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" version="1.1"><rect width="200" height="100" stroke="black" stroke-width="6" fill="green"/></svg>');
+ const fileName = 'index.html';
+
+ return svgToImg(dir.name, fileName, $)
+ .then(function() {
+ return svgToPng(dir.name, fileName, $);
+ })
+ .then(function() {
+ const $img = $('img');
+ const src = $img.attr('src');
+
+ expect(dir.name).toHaveFile(src);
+ expect(path.extname(src)).toBe('.png');
+ });
+ });
+});
+
diff --git a/lib/output/modifiers/addHeadingId.js b/packages/gitbook/src/output/modifiers/addHeadingId.js
index e2e2720..e528b9d 100644
--- a/lib/output/modifiers/addHeadingId.js
+++ b/packages/gitbook/src/output/modifiers/addHeadingId.js
@@ -1,21 +1,19 @@
-var slug = require('github-slugid');
-var editHTMLElement = require('./editHTMLElement');
+const slug = require('github-slugid');
+const editHTMLElement = require('./editHTMLElement');
/**
- Add ID to an heading
-
- @param {HTMLElement} heading
-*/
+ * Add ID to an heading.
+ * @param {HTMLElement} heading
+ */
function addId(heading) {
if (heading.attr('id')) return;
heading.attr('id', slug(heading.text()));
}
/**
- Add ID to all headings
-
- @param {HTMLDom} $
-*/
+ * Add ID to all headings.
+ * @param {HTMLDom} $
+ */
function addHeadingId($) {
return editHTMLElement($, 'h1,h2,h3,h4,h5,h6', addId);
}
diff --git a/lib/output/modifiers/annotateText.js b/packages/gitbook/src/output/modifiers/annotateText.js
index 490c228..36ee4e9 100644
--- a/lib/output/modifiers/annotateText.js
+++ b/packages/gitbook/src/output/modifiers/annotateText.js
@@ -1,46 +1,43 @@
-var escape = require('escape-html');
+const escape = require('escape-html');
// Selector to ignore
-var ANNOTATION_IGNORE = '.no-glossary,code,pre,a,script,h1,h2,h3,h4,h5,h6';
+const ANNOTATION_IGNORE = '.no-glossary,code,pre,a,script,h1,h2,h3,h4,h5,h6';
-function pregQuote( str ) {
- return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1');
+function pregQuote(str) {
+ return (str + '').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1');
}
-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 = [];
+function replaceText($, el, search, replace, text_only) {
+ return $(el).each(function() {
+ let node = this.firstChild, val, new_val;
+ // Elements to be removed at the end.
+ const remove = [];
// Only continue if firstChild exists.
- if ( node ) {
+ if (node) {
// Loop over all childNodes.
while (node) {
// Only process text nodes.
- if ( node.nodeType === 3 ) {
+ if (node.nodeType === 3) {
// The original node value.
val = node.nodeValue;
// The new value.
- new_val = val.replace( search, replace );
+ new_val = val.replace(search, replace);
// Only replace text if the new value is actually different!
- if ( new_val !== val ) {
+ if (new_val !== val) {
- if ( !text_only && /</.test( new_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 );
+ $(node).before(new_val);
// Don't remove the node yet, or the loop will lose its place.
- remove.push( node );
+ remove.push(node);
} else {
// The new value contains no HTML, so it can be set in this
// very fast, simple way.
@@ -67,13 +64,13 @@ function replaceText($, el, search, replace, text_only ) {
*/
function annotateText(entries, glossaryFilePath, $) {
entries.forEach(function(entry) {
- var entryId = entry.getID();
- var name = entry.getName();
- var description = entry.getDescription();
- var searchRegex = new RegExp( '\\b(' + pregQuote(name.toLowerCase()) + ')\\b' , 'gi' );
+ const entryId = entry.getID();
+ const name = entry.getName();
+ const description = entry.getDescription();
+ const searchRegex = new RegExp('\\b(' + pregQuote(name.toLowerCase()) + ')\\b' , 'gi');
$('*').each(function() {
- var $this = $(this);
+ const $this = $(this);
if (
$this.is(ANNOTATION_IGNORE) ||
diff --git a/lib/output/modifiers/editHTMLElement.js b/packages/gitbook/src/output/modifiers/editHTMLElement.js
index 755598e..d0d2b19 100644
--- a/lib/output/modifiers/editHTMLElement.js
+++ b/packages/gitbook/src/output/modifiers/editHTMLElement.js
@@ -1,13 +1,13 @@
-var Promise = require('../../utils/promise');
+const Promise = require('../../utils/promise');
/**
Edit all elements matching a selector
*/
function editHTMLElement($, selector, fn) {
- var $elements = $(selector);
+ const $elements = $(selector);
return Promise.forEach($elements, function(el) {
- var $el = $(el);
+ const $el = $(el);
return fn($el);
});
}
diff --git a/packages/gitbook/src/output/modifiers/fetchRemoteImages.js b/packages/gitbook/src/output/modifiers/fetchRemoteImages.js
new file mode 100644
index 0000000..f022093
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/fetchRemoteImages.js
@@ -0,0 +1,44 @@
+const path = require('path');
+const crc = require('crc');
+
+const editHTMLElement = require('./editHTMLElement');
+const fs = require('../../utils/fs');
+const LocationUtils = require('../../utils/location');
+
+/**
+ * Fetch all remote images
+ *
+ * @param {String} rootFolder
+ * @param {String} currentFile
+ * @param {HTMLDom} $
+ * @return {Promise}
+ */
+function fetchRemoteImages(rootFolder, currentFile, $) {
+ const currentDirectory = path.dirname(currentFile);
+
+ return editHTMLElement($, 'img', function($img) {
+ let src = $img.attr('src');
+ const extension = path.extname(src);
+
+ if (!LocationUtils.isExternal(src)) {
+ return;
+ }
+
+ // We avoid generating twice the same PNG
+ const hash = crc.crc32(src).toString(16);
+ const fileName = hash + extension;
+ const filePath = path.join(rootFolder, fileName);
+
+ return fs.assertFile(filePath, function() {
+ return fs.download(src, filePath);
+ })
+ .then(function() {
+ // Convert to relative
+ src = LocationUtils.relative(currentDirectory, fileName);
+
+ $img.replaceWith('<img src="' + src + '" />');
+ });
+ });
+}
+
+module.exports = fetchRemoteImages;
diff --git a/lib/output/modifiers/index.js b/packages/gitbook/src/output/modifiers/index.js
index f1daa2b..5f290f6 100644
--- a/lib/output/modifiers/index.js
+++ b/packages/gitbook/src/output/modifiers/index.js
@@ -10,6 +10,5 @@ module.exports = {
svgToPng: require('./svgToPng'),
resolveLinks: require('./resolveLinks'),
resolveImages: require('./resolveImages'),
- annotateText: require('./annotateText'),
- highlightCode: require('./highlightCode')
+ annotateText: require('./annotateText')
};
diff --git a/lib/output/modifiers/inlineAssets.js b/packages/gitbook/src/output/modifiers/inlineAssets.js
index 7cd874b..4541fcc 100644
--- a/lib/output/modifiers/inlineAssets.js
+++ b/packages/gitbook/src/output/modifiers/inlineAssets.js
@@ -1,16 +1,16 @@
-var svgToImg = require('./svgToImg');
-var svgToPng = require('./svgToPng');
-var inlinePng = require('./inlinePng');
-var resolveImages = require('./resolveImages');
-var fetchRemoteImages = require('./fetchRemoteImages');
+const svgToImg = require('./svgToImg');
+const svgToPng = require('./svgToPng');
+const inlinePng = require('./inlinePng');
+const resolveImages = require('./resolveImages');
+const fetchRemoteImages = require('./fetchRemoteImages');
-var Promise = require('../../utils/promise');
+const Promise = require('../../utils/promise');
/**
- Inline all assets in a page
-
- @param {String} rootFolder
-*/
+ * Inline all assets in a page
+ *
+ * @param {String} rootFolder
+ */
function inlineAssets(rootFolder, currentFile) {
return function($) {
return Promise()
diff --git a/lib/output/modifiers/inlinePng.js b/packages/gitbook/src/output/modifiers/inlinePng.js
index 161f164..bf14e4f 100644
--- a/lib/output/modifiers/inlinePng.js
+++ b/packages/gitbook/src/output/modifiers/inlinePng.js
@@ -1,34 +1,34 @@
-var crc = require('crc');
-var path = require('path');
+const crc = require('crc');
+const path = require('path');
-var imagesUtil = require('../../utils/images');
-var fs = require('../../utils/fs');
-var LocationUtils = require('../../utils/location');
+const imagesUtil = require('../../utils/images');
+const fs = require('../../utils/fs');
+const LocationUtils = require('../../utils/location');
-var editHTMLElement = require('./editHTMLElement');
+const editHTMLElement = require('./editHTMLElement');
/**
- Convert all inline PNG images to PNG file
-
- @param {String} rootFolder
- @param {HTMLDom} $
- @return {Promise}
-*/
+ * Convert all inline PNG images to PNG file
+ *
+ * @param {String} rootFolder
+ * @param {HTMLDom} $
+ * @return {Promise}
+ */
function inlinePng(rootFolder, currentFile, $) {
- var currentDirectory = path.dirname(currentFile);
+ const currentDirectory = path.dirname(currentFile);
return editHTMLElement($, 'img', function($img) {
- var src = $img.attr('src');
+ const src = $img.attr('src');
if (!LocationUtils.isDataURI(src)) {
return;
}
// We avoid generating twice the same PNG
- var hash = crc.crc32(src).toString(16);
- var fileName = hash + '.png';
+ const hash = crc.crc32(src).toString(16);
+ let fileName = hash + '.png';
// Result file path
- var filePath = path.join(rootFolder, fileName);
+ const filePath = path.join(rootFolder, fileName);
return fs.assertFile(filePath, function() {
return imagesUtil.convertInlinePNG(src, filePath);
@@ -43,5 +43,4 @@ function inlinePng(rootFolder, currentFile, $) {
});
}
-
module.exports = inlinePng;
diff --git a/packages/gitbook/src/output/modifiers/modifyHTML.js b/packages/gitbook/src/output/modifiers/modifyHTML.js
new file mode 100644
index 0000000..64abd07
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/modifyHTML.js
@@ -0,0 +1,25 @@
+const cheerio = require('cheerio');
+const Promise = require('../../utils/promise');
+
+/**
+ * Apply a list of operations to a page and
+ * output the new page.
+ *
+ * @param {Page} page
+ * @param {List|Array<Transformation>} operations
+ * @return {Promise<Page>} page
+ */
+function modifyHTML(page, operations) {
+ const html = page.getContent();
+ const $ = cheerio.load(html);
+
+ return Promise.forEach(operations, function(op) {
+ return op($);
+ })
+ .then(function() {
+ const resultHTML = $.html();
+ return page.set('content', resultHTML);
+ });
+}
+
+module.exports = modifyHTML;
diff --git a/lib/output/modifiers/resolveImages.js b/packages/gitbook/src/output/modifiers/resolveImages.js
index cc25cfa..c647fde 100644
--- a/lib/output/modifiers/resolveImages.js
+++ b/packages/gitbook/src/output/modifiers/resolveImages.js
@@ -1,20 +1,20 @@
-var path = require('path');
+const path = require('path');
-var LocationUtils = require('../../utils/location');
-var editHTMLElement = require('./editHTMLElement');
+const LocationUtils = require('../../utils/location');
+const editHTMLElement = require('./editHTMLElement');
/**
- Resolve all HTML images:
- - /test.png in hello -> ../test.html
-
- @param {String} currentFile
- @param {HTMLDom} $
-*/
+ * Resolve all HTML images:
+ * - /test.png in hello -> ../test.html
+ *
+ * @param {String} currentFile
+ * @param {HTMLDom} $
+ */
function resolveImages(currentFile, $) {
- var currentDirectory = path.dirname(currentFile);
+ const currentDirectory = path.dirname(currentFile);
return editHTMLElement($, 'img', function($img) {
- var src = $img.attr('src');
+ let src = $img.attr('src');
if (LocationUtils.isExternal(src) || LocationUtils.isDataURI(src)) {
return;
diff --git a/packages/gitbook/src/output/modifiers/resolveLinks.js b/packages/gitbook/src/output/modifiers/resolveLinks.js
new file mode 100644
index 0000000..ca81ccb
--- /dev/null
+++ b/packages/gitbook/src/output/modifiers/resolveLinks.js
@@ -0,0 +1,30 @@
+const LocationUtils = require('../../utils/location');
+const editHTMLElement = require('./editHTMLElement');
+
+/**
+ * Resolve all HTML links:
+ * - /test.md in hello -> ../test.html
+ *
+ * @param {Function(String) -> String} resolveURL
+ * @param {HTMLDom} $
+ */
+function resolveLinks(resolveURL, $) {
+ return editHTMLElement($, 'a', function($a) {
+ let href = $a.attr('href');
+
+ // Don't change a tag without href
+ if (!href) {
+ return;
+ }
+
+ if (LocationUtils.isExternal(href)) {
+ $a.attr('target', '_blank');
+ return;
+ }
+
+ href = resolveURL(href);
+ $a.attr('href', href);
+ });
+}
+
+module.exports = resolveLinks;
diff --git a/lib/output/modifiers/svgToImg.js b/packages/gitbook/src/output/modifiers/svgToImg.js
index f31b06d..ac37d07 100644
--- a/lib/output/modifiers/svgToImg.js
+++ b/packages/gitbook/src/output/modifiers/svgToImg.js
@@ -1,10 +1,10 @@
-var path = require('path');
-var crc = require('crc');
-var domSerializer = require('dom-serializer');
+const path = require('path');
+const crc = require('crc');
+const domSerializer = require('dom-serializer');
-var editHTMLElement = require('./editHTMLElement');
-var fs = require('../../utils/fs');
-var LocationUtils = require('../../utils/location');
+const editHTMLElement = require('./editHTMLElement');
+const fs = require('../../utils/fs');
+const LocationUtils = require('../../utils/location');
/**
Render a cheerio DOM as html
@@ -18,7 +18,7 @@ function renderDOM($, dom, options) {
if (!dom && $._root && $._root.children) {
dom = $._root.children;
}
- options = options|| dom.options || $._options;
+ options = options || dom.options || $._options;
return domSerializer(dom, options);
}
@@ -29,16 +29,16 @@ function renderDOM($, dom, options) {
@param {HTMLDom} $
*/
function svgToImg(baseFolder, currentFile, $) {
- var currentDirectory = path.dirname(currentFile);
+ const currentDirectory = path.dirname(currentFile);
return editHTMLElement($, 'svg', function($svg) {
- var content = '<?xml version="1.0" encoding="UTF-8"?>' +
+ const content = '<?xml version="1.0" encoding="UTF-8"?>' +
renderDOM($, $svg);
// We avoid generating twice the same PNG
- var hash = crc.crc32(content).toString(16);
- var fileName = hash + '.svg';
- var filePath = path.join(baseFolder, fileName);
+ const hash = crc.crc32(content).toString(16);
+ const fileName = hash + '.svg';
+ const filePath = path.join(baseFolder, fileName);
// Write the svg to the file
return fs.assertFile(filePath, function() {
@@ -47,7 +47,7 @@ function svgToImg(baseFolder, currentFile, $) {
// Return as image
.then(function() {
- var src = LocationUtils.relative(currentDirectory, fileName);
+ const src = LocationUtils.relative(currentDirectory, fileName);
$svg.replaceWith('<img src="' + src + '" />');
});
});
diff --git a/lib/output/modifiers/svgToPng.js b/packages/gitbook/src/output/modifiers/svgToPng.js
index 1093106..ad3f31f 100644
--- a/lib/output/modifiers/svgToPng.js
+++ b/packages/gitbook/src/output/modifiers/svgToPng.js
@@ -1,11 +1,11 @@
-var crc = require('crc');
-var path = require('path');
+const crc = require('crc');
+const path = require('path');
-var imagesUtil = require('../../utils/images');
-var fs = require('../../utils/fs');
-var LocationUtils = require('../../utils/location');
+const imagesUtil = require('../../utils/images');
+const fs = require('../../utils/fs');
+const LocationUtils = require('../../utils/location');
-var editHTMLElement = require('./editHTMLElement');
+const editHTMLElement = require('./editHTMLElement');
/**
Convert all SVG images to PNG
@@ -15,10 +15,10 @@ var editHTMLElement = require('./editHTMLElement');
@return {Promise}
*/
function svgToPng(rootFolder, currentFile, $) {
- var currentDirectory = path.dirname(currentFile);
+ const currentDirectory = path.dirname(currentFile);
return editHTMLElement($, 'img', function($img) {
- var src = $img.attr('src');
+ let src = $img.attr('src');
if (path.extname(src) !== '.svg') {
return;
}
@@ -27,14 +27,14 @@ function svgToPng(rootFolder, currentFile, $) {
src = LocationUtils.toAbsolute(src, currentDirectory, '.');
// We avoid generating twice the same PNG
- var hash = crc.crc32(src).toString(16);
- var fileName = hash + '.png';
+ const hash = crc.crc32(src).toString(16);
+ let fileName = hash + '.png';
// Input file path
- var inputPath = path.join(rootFolder, src);
+ const inputPath = path.join(rootFolder, src);
// Result file path
- var filePath = path.join(rootFolder, fileName);
+ const filePath = path.join(rootFolder, fileName);
return fs.assertFile(filePath, function() {
return imagesUtil.convertSVGToPNG(inputPath, filePath);
diff --git a/lib/output/prepareAssets.js b/packages/gitbook/src/output/prepareAssets.js
index ae9b55a..2851b01 100644
--- a/lib/output/prepareAssets.js
+++ b/packages/gitbook/src/output/prepareAssets.js
@@ -1,15 +1,15 @@
-var Parse = require('../parse');
+const Parse = require('../parse');
/**
- List all assets in the book
-
- @param {Output}
- @return {Promise<Output>}
-*/
+ * List all assets in the book.
+ *
+ * @param {Output} output
+ * @return {Promise<Output>} output
+ */
function prepareAssets(output) {
- var book = output.getBook();
- var pages = output.getPages();
- var logger = output.getLogger();
+ const book = output.getBook();
+ const pages = output.getPages();
+ const logger = output.getLogger();
return Parse.listAssets(book, pages)
.then(function(assets) {
diff --git a/packages/gitbook/src/output/preparePages.js b/packages/gitbook/src/output/preparePages.js
new file mode 100644
index 0000000..0cf1412
--- /dev/null
+++ b/packages/gitbook/src/output/preparePages.js
@@ -0,0 +1,35 @@
+const Parse = require('../parse');
+const Promise = require('../utils/promise');
+const parseURIIndexFromPages = require('../parse/parseURIIndexFromPages');
+
+/**
+ * List and parse all pages, then create the urls mapping.
+ *
+ * @param {Output}
+ * @return {Promise<Output>}
+ */
+function preparePages(output) {
+ const book = output.getBook();
+ const logger = book.getLogger();
+ const readme = book.getReadme();
+
+ if (book.isMultilingual()) {
+ return Promise(output);
+ }
+
+ return Parse.parsePagesList(book)
+ .then((pages) => {
+ logger.info.ln('found', pages.size, 'pages');
+ let urls = parseURIIndexFromPages(pages);
+
+ // Readme should always generate an index.html
+ urls = urls.append(readme.getFile().getPath(), 'index.html');
+
+ return output.merge({
+ pages,
+ urls
+ });
+ });
+}
+
+module.exports = preparePages;
diff --git a/lib/output/preparePlugins.js b/packages/gitbook/src/output/preparePlugins.js
index 5c4be93..c84bade 100644
--- a/lib/output/preparePlugins.js
+++ b/packages/gitbook/src/output/preparePlugins.js
@@ -1,5 +1,5 @@
-var Plugins = require('../plugins');
-var Promise = require('../utils/promise');
+const Plugins = require('../plugins');
+const Promise = require('../utils/promise');
/**
* Load and setup plugins
@@ -8,7 +8,7 @@ var Promise = require('../utils/promise');
* @return {Promise<Output>}
*/
function preparePlugins(output) {
- var book = output.getBook();
+ const book = output.getBook();
return Promise()
@@ -27,7 +27,7 @@ function preparePlugins(output) {
.then(function(newBook) {
return output.merge({
book: newBook,
- plugins: plugins
+ plugins
});
});
});
diff --git a/packages/gitbook/src/output/website/copyPluginAssets.js b/packages/gitbook/src/output/website/copyPluginAssets.js
new file mode 100644
index 0000000..fe75377
--- /dev/null
+++ b/packages/gitbook/src/output/website/copyPluginAssets.js
@@ -0,0 +1,111 @@
+const path = require('path');
+
+const ASSET_FOLDER = require('../../constants/pluginAssetsFolder');
+const Promise = require('../../utils/promise');
+const fs = require('../../utils/fs');
+
+/**
+ * Copy all assets from plugins.
+ * Assets are files stored in a "_assets" of the plugin.
+ *
+ * @param {Output}
+ * @return {Promise}
+ */
+function copyPluginAssets(output) {
+ const book = output.getBook();
+
+ // Don't copy plugins assets for language book
+ // It'll be resolved to the parent folder
+ if (book.isLanguageBook()) {
+ return Promise(output);
+ }
+
+ const plugins = output.getPlugins();
+
+ return Promise.forEach(plugins, (plugin) => {
+ return copyAssets(output, plugin)
+ .then(() => copyBrowserJS(output, plugin));
+ })
+ .then(() => copyCoreJS(output))
+ .thenResolve(output);
+}
+
+/**
+ * Copy assets from a plugin
+ *
+ * @param {Plugin}
+ * @return {Promise}
+ */
+function copyAssets(output, plugin) {
+ const logger = output.getLogger();
+ const pluginRoot = plugin.getPath();
+ const options = output.getOptions();
+
+ const outputRoot = options.get('root');
+ const prefix = options.get('prefix');
+
+ const assetFolder = path.join(pluginRoot, ASSET_FOLDER, prefix);
+ const assetOutputFolder = path.join(outputRoot, 'gitbook', plugin.getName());
+
+ if (!fs.existsSync(assetFolder)) {
+ return Promise();
+ }
+
+ logger.debug.ln('copy assets from plugin', assetFolder);
+ return fs.copyDir(
+ assetFolder,
+ assetOutputFolder,
+ {
+ deleteFirst: false,
+ overwrite: true,
+ confirm: true
+ }
+ );
+}
+
+/**
+ * Copy JS file for the plugin
+ *
+ * @param {Plugin}
+ * @return {Promise}
+ */
+function copyBrowserJS(output, plugin) {
+ const logger = output.getLogger();
+ const pluginRoot = plugin.getPath();
+ const options = output.getOptions();
+ const outputRoot = options.get('root');
+
+ let browserFile = plugin.getPackage().get('browser');
+
+ if (!browserFile) {
+ return Promise();
+ }
+
+ browserFile = path.join(pluginRoot, browserFile);
+ const outputFile = path.join(outputRoot, 'gitbook/plugins', plugin.getName() + '.js');
+
+ logger.debug.ln('copy browser JS file from plugin', browserFile);
+ return fs.ensureFile(outputFile)
+ .then(() => fs.copy(browserFile, outputFile));
+}
+
+/**
+ * Copy JS file for gitbook-core
+ *
+ * @param {Plugin}
+ * @return {Promise}
+ */
+function copyCoreJS(output) {
+ const logger = output.getLogger();
+ const options = output.getOptions();
+ const outputRoot = options.get('root');
+
+ const inputFile = require.resolve('gitbook-core/dist/gitbook.core.min.js');
+ const outputFile = path.join(outputRoot, 'gitbook/core.js');
+
+ logger.debug.ln('copy JS for gitbook-core');
+ return fs.ensureFile(outputFile)
+ .then(() => fs.copy(inputFile, outputFile));
+}
+
+module.exports = copyPluginAssets;
diff --git a/packages/gitbook/src/output/website/index.js b/packages/gitbook/src/output/website/index.js
new file mode 100644
index 0000000..c6031e1
--- /dev/null
+++ b/packages/gitbook/src/output/website/index.js
@@ -0,0 +1,10 @@
+
+module.exports = {
+ name: 'website',
+ State: require('./state'),
+ Options: require('./options'),
+ onInit: require('./onInit'),
+ onFinish: require('./onFinish'),
+ onPage: require('./onPage'),
+ onAsset: require('./onAsset')
+};
diff --git a/packages/gitbook/src/output/website/onAsset.js b/packages/gitbook/src/output/website/onAsset.js
new file mode 100644
index 0000000..b72c47d
--- /dev/null
+++ b/packages/gitbook/src/output/website/onAsset.js
@@ -0,0 +1,29 @@
+const path = require('path');
+const fs = require('../../utils/fs');
+
+/**
+ * Copy an asset from the book to the output folder.
+ *
+ * @param {Output} output
+ * @param {Page} page
+ * @return {Output} output
+ */
+function onAsset(output, asset) {
+ const book = output.getBook();
+ const options = output.getOptions();
+ const bookFS = book.getContentFS();
+
+ const outputFolder = options.get('root');
+ const outputPath = path.resolve(outputFolder, asset);
+
+ return fs.ensureFile(outputPath)
+ .then(function() {
+ return bookFS.readAsStream(asset)
+ .then(function(stream) {
+ return fs.writeStream(outputPath, stream);
+ });
+ })
+ .thenResolve(output);
+}
+
+module.exports = onAsset;
diff --git a/packages/gitbook/src/output/website/onFinish.js b/packages/gitbook/src/output/website/onFinish.js
new file mode 100644
index 0000000..6efeed8
--- /dev/null
+++ b/packages/gitbook/src/output/website/onFinish.js
@@ -0,0 +1,30 @@
+const JSONUtils = require('../../json');
+const Promise = require('../../utils/promise');
+const writeFile = require('../helper/writeFile');
+const render = require('../../browser/render');
+
+/**
+ * Finish the generation, write the languages index.
+ *
+ * @param {Output}
+ * @return {Output}
+ */
+function onFinish(output) {
+ const book = output.getBook();
+
+ if (!book.isMultilingual()) {
+ return Promise(output);
+ }
+
+ const plugins = output.getPlugins();
+
+ // Generate initial state
+ const initialState = JSONUtils.encodeState(output);
+
+ // Render using React
+ const html = render(plugins, initialState, 'browser', 'website:languages');
+
+ return writeFile(output, 'index.html', html);
+}
+
+module.exports = onFinish;
diff --git a/packages/gitbook/src/output/website/onInit.js b/packages/gitbook/src/output/website/onInit.js
new file mode 100644
index 0000000..b13c719
--- /dev/null
+++ b/packages/gitbook/src/output/website/onInit.js
@@ -0,0 +1,15 @@
+const Promise = require('../../utils/promise');
+const copyPluginAssets = require('./copyPluginAssets');
+
+/**
+ * Initialize the generator
+ *
+ * @param {Output}
+ * @return {Output}
+ */
+function onInit(output) {
+ return Promise(output)
+ .then(copyPluginAssets);
+}
+
+module.exports = onInit;
diff --git a/packages/gitbook/src/output/website/onPage.js b/packages/gitbook/src/output/website/onPage.js
new file mode 100644
index 0000000..90eec63
--- /dev/null
+++ b/packages/gitbook/src/output/website/onPage.js
@@ -0,0 +1,34 @@
+const JSONUtils = require('../../json');
+const Modifiers = require('../modifiers');
+const writeFile = require('../helper/writeFile');
+const getModifiers = require('../getModifiers');
+const render = require('../../browser/render');
+
+/**
+ * Generate a page using react and the plugins.
+ *
+ * @param {Output} output
+ * @param {Page} page
+ */
+function onPage(output, page) {
+ const file = page.getFile();
+ const plugins = output.getPlugins();
+ const urls = output.getURLIndex();
+
+ // Output file path
+ const filePath = urls.resolve(file.getPath());
+
+ return Modifiers.modifyHTML(page, getModifiers(output, page))
+ .then(function(resultPage) {
+ // Generate the context
+ const initialState = JSONUtils.encodeState(output, resultPage);
+
+ // Render the theme
+ const html = render(plugins, initialState, 'browser', 'website:body');
+
+ // Write it to the disk
+ return writeFile(output, filePath, html);
+ });
+}
+
+module.exports = onPage;
diff --git a/packages/gitbook/src/output/website/options.js b/packages/gitbook/src/output/website/options.js
new file mode 100644
index 0000000..3bcbd9a
--- /dev/null
+++ b/packages/gitbook/src/output/website/options.js
@@ -0,0 +1,10 @@
+const Immutable = require('immutable');
+
+const Options = Immutable.Record({
+ // Root folder for the output
+ root: String(),
+ // Prefix for generation
+ prefix: String('website')
+});
+
+module.exports = Options;
diff --git a/lib/output/website/state.js b/packages/gitbook/src/output/website/state.js
index cb8f750..2adb9ed 100644
--- a/lib/output/website/state.js
+++ b/packages/gitbook/src/output/website/state.js
@@ -1,11 +1,10 @@
-var I18n = require('i18n-t');
-var Immutable = require('immutable');
-
-var GeneratorState = Immutable.Record({
- i18n: I18n(),
+const I18n = require('i18n-t');
+const Immutable = require('immutable');
+const GeneratorState = Immutable.Record({
+ i18n: I18n(),
// List of plugins' resources
- resources: Immutable.Map()
+ resources: Immutable.Map()
});
GeneratorState.prototype.getI18n = function() {
diff --git a/lib/parse/__tests__/listAssets.js b/packages/gitbook/src/parse/__tests__/listAssets.js
index 4c5b0a0..102aed9 100644
--- a/lib/parse/__tests__/listAssets.js
+++ b/packages/gitbook/src/parse/__tests__/listAssets.js
@@ -1,20 +1,20 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var Book = require('../../models/book');
-var createMockFS = require('../../fs/mock');
-var listAssets = require('../listAssets');
-var parseGlossary = require('../parseGlossary');
+const Book = require('../../models/book');
+const createMockFS = require('../../fs/mock');
+const listAssets = require('../listAssets');
+const parseGlossary = require('../parseGlossary');
describe('listAssets', function() {
it('should not list glossary as asset', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'GLOSSARY.md': '# Glossary\n\n## Hello\nDescription for hello',
'assetFile.js': '',
'assets': {
'file.js': ''
}
});
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseGlossary(book)
.then(function(resultBook) {
diff --git a/lib/parse/__tests__/parseBook.js b/packages/gitbook/src/parse/__tests__/parseBook.js
index b1236c9..d5de25c 100644
--- a/lib/parse/__tests__/parseBook.js
+++ b/packages/gitbook/src/parse/__tests__/parseBook.js
@@ -1,11 +1,11 @@
-var Book = require('../../models/book');
-var createMockFS = require('../../fs/mock');
+const Book = require('../../models/book');
+const createMockFS = require('../../fs/mock');
describe('parseBook', function() {
- var parseBook = require('../parseBook');
+ const parseBook = require('../parseBook');
it('should parse multilingual book', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
'en': {
'README.md': 'Hello'
@@ -14,12 +14,12 @@ describe('parseBook', function() {
'README.md': 'Bonjour'
}
});
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseBook(book)
.then(function(resultBook) {
- var languages = resultBook.getLanguages();
- var books = resultBook.getBooks();
+ const languages = resultBook.getLanguages();
+ const books = resultBook.getBooks();
expect(resultBook.isMultilingual()).toBe(true);
expect(languages.getList().size).toBe(2);
@@ -28,7 +28,7 @@ describe('parseBook', function() {
});
it('should extend configuration for multilingual book', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'LANGS.md': '# Languages\n\n* [en](en)\n* [fr](fr)',
'book.json': '{ "title": "Test", "author": "GitBook" }',
'en': {
@@ -39,20 +39,20 @@ describe('parseBook', function() {
'README.md': 'Bonjour'
}
});
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseBook(book)
.then(function(resultBook) {
- var books = resultBook.getBooks();
+ const books = resultBook.getBooks();
expect(resultBook.isMultilingual()).toBe(true);
expect(books.size).toBe(2);
- var en = books.get('en');
- var fr = books.get('fr');
+ const en = books.get('en');
+ const fr = books.get('fr');
- var enConfig = en.getConfig();
- var frConfig = fr.getConfig();
+ const enConfig = en.getConfig();
+ const frConfig = fr.getConfig();
expect(enConfig.getValue('title')).toBe('Test EN');
expect(enConfig.getValue('author')).toBe('GitBook');
@@ -63,7 +63,7 @@ describe('parseBook', function() {
});
it('should parse book in a directory', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'book.json': JSON.stringify({
root: './test'
}),
@@ -73,13 +73,13 @@ describe('parseBook', function() {
'page.md': 'Page'
}
});
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseBook(book)
.then(function(resultBook) {
- var readme = resultBook.getReadme();
- var summary = resultBook.getSummary();
- var articles = summary.getArticlesAsList();
+ const readme = resultBook.getReadme();
+ const summary = resultBook.getSummary();
+ const articles = summary.getArticlesAsList();
expect(summary.getFile().exists()).toBe(true);
expect(readme.getFile().exists()).toBe(true);
diff --git a/lib/parse/__tests__/parseGlossary.js b/packages/gitbook/src/parse/__tests__/parseGlossary.js
index 9069af6..ba2e407 100644
--- a/lib/parse/__tests__/parseGlossary.js
+++ b/packages/gitbook/src/parse/__tests__/parseGlossary.js
@@ -1,20 +1,20 @@
-var Book = require('../../models/book');
-var createMockFS = require('../../fs/mock');
+const Book = require('../../models/book');
+const createMockFS = require('../../fs/mock');
describe('parseGlossary', function() {
- var parseGlossary = require('../parseGlossary');
+ const parseGlossary = require('../parseGlossary');
it('should parse glossary if exists', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'GLOSSARY.md': '# Glossary\n\n## Hello\nDescription for hello'
});
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseGlossary(book)
.then(function(resultBook) {
- var glossary = resultBook.getGlossary();
- var file = glossary.getFile();
- var entries = glossary.getEntries();
+ const glossary = resultBook.getGlossary();
+ const file = glossary.getFile();
+ const entries = glossary.getEntries();
expect(file.exists()).toBeTruthy();
expect(entries.size).toBe(1);
@@ -22,13 +22,13 @@ describe('parseGlossary', function() {
});
it('should not fail if doesn\'t exist', function() {
- var fs = createMockFS({});
- var book = Book.createForFS(fs);
+ const fs = createMockFS({});
+ const book = Book.createForFS(fs);
return parseGlossary(book)
.then(function(resultBook) {
- var glossary = resultBook.getGlossary();
- var file = glossary.getFile();
+ const glossary = resultBook.getGlossary();
+ const file = glossary.getFile();
expect(file.exists()).toBeFalsy();
});
diff --git a/lib/parse/__tests__/parseIgnore.js b/packages/gitbook/src/parse/__tests__/parseIgnore.js
index 54e7dae..b1bd43c 100644
--- a/lib/parse/__tests__/parseIgnore.js
+++ b/packages/gitbook/src/parse/__tests__/parseIgnore.js
@@ -1,9 +1,9 @@
-var Book = require('../../models/book');
-var createMockFS = require('../../fs/mock');
+const Book = require('../../models/book');
+const createMockFS = require('../../fs/mock');
describe('parseIgnore', function() {
- var parseIgnore = require('../parseIgnore');
- var fs = createMockFS({
+ const parseIgnore = require('../parseIgnore');
+ const fs = createMockFS({
'.ignore': 'test-1.js',
'.gitignore': 'test-2.js\ntest-3.js',
'.bookignore': '!test-3.js',
@@ -13,7 +13,7 @@ describe('parseIgnore', function() {
});
function getBook() {
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseIgnore(book);
}
diff --git a/lib/parse/__tests__/parsePageFromString.js b/packages/gitbook/src/parse/__tests__/parsePageFromString.js
index 2911fa3..13bc544 100644
--- a/lib/parse/__tests__/parsePageFromString.js
+++ b/packages/gitbook/src/parse/__tests__/parsePageFromString.js
@@ -1,25 +1,25 @@
-var parsePageFromString = require('../parsePageFromString');
-var Page = require('../../models/page');
+const parsePageFromString = require('../parsePageFromString');
+const Page = require('../../models/page');
describe('parsePageFromString', function() {
- var page = new Page();
+ const page = new Page();
it('should parse YAML frontmatter', function() {
- var CONTENT = '---\nhello: true\nworld: "cool"\n---\n# Hello World\n';
- var newPage = parsePageFromString(page, CONTENT);
+ const CONTENT = '---\nhello: true\nworld: "cool"\n---\n# Hello World\n';
+ const newPage = parsePageFromString(page, CONTENT);
expect(newPage.getDir()).toBe('ltr');
expect(newPage.getContent()).toBe('# Hello World\n');
- var attrs = newPage.getAttributes();
+ const attrs = newPage.getAttributes();
expect(attrs.size).toBe(2);
expect(attrs.get('hello')).toBe(true);
expect(attrs.get('world')).toBe('cool');
});
it('should parse text direction (english)', function() {
- var CONTENT = 'Hello World';
- var newPage = parsePageFromString(page, CONTENT);
+ const CONTENT = 'Hello World';
+ const newPage = parsePageFromString(page, CONTENT);
expect(newPage.getDir()).toBe('ltr');
expect(newPage.getContent()).toBe('Hello World');
@@ -27,8 +27,8 @@ describe('parsePageFromString', function() {
});
it('should parse text direction (arab)', function() {
- var CONTENT = 'مرحبا بالعالم';
- var newPage = parsePageFromString(page, CONTENT);
+ const CONTENT = 'مرحبا بالعالم';
+ const newPage = parsePageFromString(page, CONTENT);
expect(newPage.getDir()).toBe('rtl');
expect(newPage.getContent()).toBe('مرحبا بالعالم');
diff --git a/lib/parse/__tests__/parseReadme.js b/packages/gitbook/src/parse/__tests__/parseReadme.js
index 4270ea3..45ecfa3 100644
--- a/lib/parse/__tests__/parseReadme.js
+++ b/packages/gitbook/src/parse/__tests__/parseReadme.js
@@ -1,20 +1,20 @@
-var Promise = require('../../utils/promise');
-var Book = require('../../models/book');
-var createMockFS = require('../../fs/mock');
+const Promise = require('../../utils/promise');
+const Book = require('../../models/book');
+const createMockFS = require('../../fs/mock');
describe('parseReadme', function() {
- var parseReadme = require('../parseReadme');
+ const parseReadme = require('../parseReadme');
it('should parse summary if exists', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'README.md': '# Hello\n\nAnd here is the description.'
});
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseReadme(book)
.then(function(resultBook) {
- var readme = resultBook.getReadme();
- var file = readme.getFile();
+ const readme = resultBook.getReadme();
+ const file = readme.getFile();
expect(file.exists()).toBeTruthy();
expect(readme.getTitle()).toBe('Hello');
@@ -23,8 +23,8 @@ describe('parseReadme', function() {
});
it('should fail if doesn\'t exist', function() {
- var fs = createMockFS({});
- var book = Book.createForFS(fs);
+ const fs = createMockFS({});
+ const book = Book.createForFS(fs);
return parseReadme(book)
.then(function(resultBook) {
diff --git a/lib/parse/__tests__/parseSummary.js b/packages/gitbook/src/parse/__tests__/parseSummary.js
index 55a445e..8b86c45 100644
--- a/lib/parse/__tests__/parseSummary.js
+++ b/packages/gitbook/src/parse/__tests__/parseSummary.js
@@ -1,32 +1,32 @@
-var Book = require('../../models/book');
-var createMockFS = require('../../fs/mock');
+const Book = require('../../models/book');
+const createMockFS = require('../../fs/mock');
describe('parseSummary', function() {
- var parseSummary = require('../parseSummary');
+ const parseSummary = require('../parseSummary');
it('should parse summary if exists', function() {
- var fs = createMockFS({
+ const fs = createMockFS({
'SUMMARY.md': '# Summary\n\n* [Hello](hello.md)'
});
- var book = Book.createForFS(fs);
+ const book = Book.createForFS(fs);
return parseSummary(book)
.then(function(resultBook) {
- var summary = resultBook.getSummary();
- var file = summary.getFile();
+ const summary = resultBook.getSummary();
+ const file = summary.getFile();
expect(file.exists()).toBeTruthy();
});
});
it('should not fail if doesn\'t exist', function() {
- var fs = createMockFS({});
- var book = Book.createForFS(fs);
+ const fs = createMockFS({});
+ const book = Book.createForFS(fs);
return parseSummary(book)
.then(function(resultBook) {
- var summary = resultBook.getSummary();
- var file = summary.getFile();
+ const summary = resultBook.getSummary();
+ const file = summary.getFile();
expect(file.exists()).toBeFalsy();
});
diff --git a/packages/gitbook/src/parse/__tests__/parseURIIndexFromPages.js b/packages/gitbook/src/parse/__tests__/parseURIIndexFromPages.js
new file mode 100644
index 0000000..755b225
--- /dev/null
+++ b/packages/gitbook/src/parse/__tests__/parseURIIndexFromPages.js
@@ -0,0 +1,26 @@
+const { OrderedMap } = require('immutable');
+
+const parseURIIndexFromPages = require('../parseURIIndexFromPages');
+const Page = require('../../models/page');
+
+describe('parseURIIndexFromPages', () => {
+
+ it('should map file to html', () => {
+ const pages = OrderedMap({
+ 'page.md': new Page()
+ });
+ const urls = parseURIIndexFromPages(pages);
+
+ expect(urls.resolve('page.md')).toBe('page.html');
+ });
+
+ it('should map README to folder', () => {
+ const pages = OrderedMap({
+ 'hello/README.md': new Page()
+ });
+ const urls = parseURIIndexFromPages(pages);
+
+ expect(urls.resolveToURL('hello/README.md')).toBe('hello/');
+ });
+
+});
diff --git a/lib/parse/findParsableFile.js b/packages/gitbook/src/parse/findParsableFile.js
index 51e2dd0..c30dbbd 100644
--- a/lib/parse/findParsableFile.js
+++ b/packages/gitbook/src/parse/findParsableFile.js
@@ -1,7 +1,7 @@
-var path = require('path');
+const path = require('path');
-var Promise = require('../utils/promise');
-var parsers = require('../parsers');
+const Promise = require('../utils/promise');
+const parsers = require('../parsers');
/**
Find a file parsable (Markdown or AsciiDoc) in a book
@@ -11,16 +11,16 @@ var parsers = require('../parsers');
@return {Promise<File | Undefined>}
*/
function findParsableFile(book, filename) {
- var fs = book.getContentFS();
- var ext = path.extname(filename);
- var basename = path.basename(filename, ext);
- var basedir = path.dirname(filename);
+ const fs = book.getContentFS();
+ const ext = path.extname(filename);
+ const basename = path.basename(filename, ext);
+ const basedir = path.dirname(filename);
// Ordered list of extensions to test
- var exts = parsers.extensions;
+ const exts = parsers.extensions;
return Promise.some(exts, function(ext) {
- var filepath = basename + ext;
+ const filepath = basename + ext;
return fs.findFile(basedir, filepath)
.then(function(found) {
diff --git a/lib/parse/index.js b/packages/gitbook/src/parse/index.js
index 1f73946..1f73946 100644
--- a/lib/parse/index.js
+++ b/packages/gitbook/src/parse/index.js
diff --git a/packages/gitbook/src/parse/listAssets.js b/packages/gitbook/src/parse/listAssets.js
new file mode 100644
index 0000000..91699df
--- /dev/null
+++ b/packages/gitbook/src/parse/listAssets.js
@@ -0,0 +1,43 @@
+const timing = require('../utils/timing');
+
+/**
+ * List all assets in a book
+ * Assets are file not ignored and not a page
+ *
+ * @param {Book} book
+ * @param {List<String>} pages
+ * @return {Promise<List<String>>} assets
+ */
+function listAssets(book, pages) {
+ const fs = book.getContentFS();
+
+ const summary = book.getSummary();
+ const summaryFile = summary.getFile().getPath();
+
+ const glossary = book.getGlossary();
+ const glossaryFile = glossary.getFile().getPath();
+
+ const langs = book.getLanguages();
+ const langsFile = langs.getFile().getPath();
+
+ const config = book.getConfig();
+ const configFile = config.getFile().getPath();
+
+ function filterFile(file) {
+ return !(
+ file === summaryFile ||
+ file === glossaryFile ||
+ file === langsFile ||
+ file === configFile ||
+ book.isContentFileIgnored(file) ||
+ pages.has(file)
+ );
+ }
+
+ return timing.measure(
+ 'parse.listAssets',
+ fs.listAllFiles('.', filterFile)
+ );
+}
+
+module.exports = listAssets;
diff --git a/lib/parse/lookupStructureFile.js b/packages/gitbook/src/parse/lookupStructureFile.js
index 36b37f8..e54a769 100644
--- a/lib/parse/lookupStructureFile.js
+++ b/packages/gitbook/src/parse/lookupStructureFile.js
@@ -1,4 +1,4 @@
-var findParsableFile = require('./findParsableFile');
+const findParsableFile = require('./findParsableFile');
/**
Lookup a structure file (ex: SUMMARY.md, GLOSSARY.md) in a book. Uses
@@ -10,9 +10,9 @@ var findParsableFile = require('./findParsableFile');
to the book content root.
*/
function lookupStructureFile(book, type) {
- var config = book.getConfig();
+ const config = book.getConfig();
- var fileToSearch = config.getValue(['structure', type]);
+ const fileToSearch = config.getValue(['structure', type]);
return findParsableFile(book, fileToSearch);
}
diff --git a/lib/parse/parseBook.js b/packages/gitbook/src/parse/parseBook.js
index a92f39e..e5c1784 100644
--- a/lib/parse/parseBook.js
+++ b/packages/gitbook/src/parse/parseBook.js
@@ -1,20 +1,20 @@
-var Promise = require('../utils/promise');
-var timing = require('../utils/timing');
-var Book = require('../models/book');
+const Promise = require('../utils/promise');
+const timing = require('../utils/timing');
+const Book = require('../models/book');
-var parseIgnore = require('./parseIgnore');
-var parseConfig = require('./parseConfig');
-var parseGlossary = require('./parseGlossary');
-var parseSummary = require('./parseSummary');
-var parseReadme = require('./parseReadme');
-var parseLanguages = require('./parseLanguages');
+const parseIgnore = require('./parseIgnore');
+const parseConfig = require('./parseConfig');
+const parseGlossary = require('./parseGlossary');
+const parseSummary = require('./parseSummary');
+const parseReadme = require('./parseReadme');
+const parseLanguages = require('./parseLanguages');
/**
- Parse content of a book
-
- @param {Book} book
- @return {Promise<Book>}
-*/
+ * Parse content of a book
+ *
+ * @param {Book} book
+ * @return {Promise<Book>}
+ */
function parseBookContent(book) {
return Promise(book)
.then(parseReadme)
@@ -23,19 +23,19 @@ function parseBookContent(book) {
}
/**
- Parse a multilingual book
-
- @param {Book} book
- @return {Promise<Book>}
-*/
+ * Parse a multilingual book
+ *
+ * @param {Book} book
+ * @return {Promise<Book>}
+ */
function parseMultilingualBook(book) {
- var languages = book.getLanguages();
- var langList = languages.getList();
+ const languages = book.getLanguages();
+ const langList = languages.getList();
return Promise.reduce(langList, function(currentBook, lang) {
- var langID = lang.getID();
- var child = Book.createFromParent(currentBook, langID);
- var ignore = currentBook.getIgnore();
+ const langID = lang.getID();
+ const child = Book.createFromParent(currentBook, langID);
+ let ignore = currentBook.getIgnore();
return Promise(child)
.then(parseConfig)
@@ -52,11 +52,11 @@ function parseMultilingualBook(book) {
/**
- Parse a whole book from a filesystem
-
- @param {Book} book
- @return {Promise<Book>}
-*/
+ * Parse a whole book from a filesystem
+ *
+ * @param {Book} book
+ * @return {Promise<Book>}
+ */
function parseBook(book) {
return timing.measure(
'parse.book',
diff --git a/lib/parse/parseConfig.js b/packages/gitbook/src/parse/parseConfig.js
index a411af8..cd27426 100644
--- a/lib/parse/parseConfig.js
+++ b/packages/gitbook/src/parse/parseConfig.js
@@ -1,7 +1,7 @@
-var Promise = require('../utils/promise');
+const Promise = require('../utils/promise');
-var validateConfig = require('./validateConfig');
-var CONFIG_FILES = require('../constants/configFiles');
+const validateConfig = require('./validateConfig');
+const CONFIG_FILES = require('../constants/configFiles');
/**
Parse configuration from "book.json" or "book.js"
@@ -10,8 +10,8 @@ var CONFIG_FILES = require('../constants/configFiles');
@return {Promise<Book>}
*/
function parseConfig(book) {
- var fs = book.getFS();
- var config = book.getConfig();
+ const fs = book.getFS();
+ let config = book.getConfig();
return Promise.some(CONFIG_FILES, function(filename) {
// Is this file ignored?
@@ -25,19 +25,19 @@ function parseConfig(book) {
return fs.statFile(filename)
.then(function(file) {
return {
- file: file,
+ file,
values: cfg
};
});
})
.fail(function(err) {
- if (err.code != 'MODULE_NOT_FOUND') throw(err);
+ if (err.code != 'MODULE_NOT_FOUND') throw (err);
else return Promise(false);
});
})
.then(function(result) {
- var values = result? result.values : {};
+ let values = result ? result.values : {};
values = validateConfig(values);
// Set the file
diff --git a/lib/parse/parseGlossary.js b/packages/gitbook/src/parse/parseGlossary.js
index a96e5fc..052985b 100644
--- a/lib/parse/parseGlossary.js
+++ b/packages/gitbook/src/parse/parseGlossary.js
@@ -1,5 +1,5 @@
-var parseStructureFile = require('./parseStructureFile');
-var Glossary = require('../models/glossary');
+const parseStructureFile = require('./parseStructureFile');
+const Glossary = require('../models/glossary');
/**
Parse glossary
@@ -8,7 +8,7 @@ var Glossary = require('../models/glossary');
@return {Promise<Book>}
*/
function parseGlossary(book) {
- var logger = book.getLogger();
+ const logger = book.getLogger();
return parseStructureFile(book, 'glossary')
.spread(function(file, entries) {
@@ -18,7 +18,7 @@ function parseGlossary(book) {
logger.debug.ln('glossary index file found at', file.getPath());
- var glossary = Glossary.createFromEntries(file, entries);
+ const glossary = Glossary.createFromEntries(file, entries);
return book.set('glossary', glossary);
});
}
diff --git a/lib/parse/parseIgnore.js b/packages/gitbook/src/parse/parseIgnore.js
index 84d8c33..a42805b 100644
--- a/lib/parse/parseIgnore.js
+++ b/packages/gitbook/src/parse/parseIgnore.js
@@ -1,7 +1,7 @@
-var Promise = require('../utils/promise');
-var IGNORE_FILES = require('../constants/ignoreFiles');
+const Promise = require('../utils/promise');
+const IGNORE_FILES = require('../constants/ignoreFiles');
-var DEFAULT_IGNORES = [
+const DEFAULT_IGNORES = [
// Skip Git stuff
'.git/',
@@ -19,31 +19,34 @@ var DEFAULT_IGNORES = [
];
/**
- Parse ignore files
-
- @param {Book}
- @return {Book}
-*/
+ * Parse ignore files
+ *
+ * @param {Book} book
+ * @return {Book} book
+ */
function parseIgnore(book) {
if (book.isLanguageBook()) {
return Promise.reject(new Error('Ignore files could be parsed for language books'));
}
- var fs = book.getFS();
- var ignore = book.getIgnore();
+ const fs = book.getFS();
+ let ignore = book.getIgnore();
ignore = ignore.add(DEFAULT_IGNORES);
- return Promise.serie(IGNORE_FILES, function(filename) {
+ return Promise.serie(IGNORE_FILES, (filename) => {
return fs.readAsString(filename)
- .then(function(content) {
- ignore = ignore.add(content.toString().split(/\r?\n/));
- }, function(err) {
- return Promise();
- });
+ .then(
+ (content) => {
+ ignore = ignore.add(content.toString().split(/\r?\n/));
+ },
+ (err) => {
+ return Promise();
+ }
+ );
})
- .then(function() {
+ .then(() => {
return book.setIgnore(ignore);
});
}
diff --git a/lib/parse/parseLanguages.js b/packages/gitbook/src/parse/parseLanguages.js
index 346f3a3..1b28930 100644
--- a/lib/parse/parseLanguages.js
+++ b/packages/gitbook/src/parse/parseLanguages.js
@@ -1,5 +1,5 @@
-var parseStructureFile = require('./parseStructureFile');
-var Languages = require('../models/languages');
+const parseStructureFile = require('./parseStructureFile');
+const Languages = require('../models/languages');
/**
Parse languages list from book
@@ -8,7 +8,7 @@ var Languages = require('../models/languages');
@return {Promise<Book>}
*/
function parseLanguages(book) {
- var logger = book.getLogger();
+ const logger = book.getLogger();
return parseStructureFile(book, 'langs')
.spread(function(file, result) {
@@ -16,7 +16,7 @@ function parseLanguages(book) {
return book;
}
- var languages = Languages.createFromList(file, result);
+ const languages = Languages.createFromList(file, result);
logger.debug.ln('languages index file found at', file.getPath());
logger.info.ln('parsing multilingual book, with', languages.getList().size, 'languages');
diff --git a/lib/parse/parsePage.js b/packages/gitbook/src/parse/parsePage.js
index fdc56a3..72f9ddf 100644
--- a/lib/parse/parsePage.js
+++ b/packages/gitbook/src/parse/parsePage.js
@@ -1,4 +1,4 @@
-var parsePageFromString = require('./parsePageFromString');
+const parsePageFromString = require('./parsePageFromString');
/**
* Parse a page, read its content and parse the YAMl header
@@ -8,8 +8,8 @@ var parsePageFromString = require('./parsePageFromString');
* @return {Promise<Page>}
*/
function parsePage(book, page) {
- var fs = book.getContentFS();
- var file = page.getFile();
+ const fs = book.getContentFS();
+ const file = page.getFile();
return fs.readAsString(file.getPath())
.then(function(content) {
diff --git a/lib/parse/parsePageFromString.js b/packages/gitbook/src/parse/parsePageFromString.js
index e64664b..2e4a598 100644
--- a/lib/parse/parsePageFromString.js
+++ b/packages/gitbook/src/parse/parsePageFromString.js
@@ -1,6 +1,6 @@
-var Immutable = require('immutable');
-var fm = require('front-matter');
-var direction = require('direction');
+const Immutable = require('immutable');
+const fm = require('front-matter');
+const direction = require('direction');
/**
* Parse a page, its content and the YAMl header
@@ -9,8 +9,7 @@ var direction = require('direction');
* @return {Page}
*/
function parsePageFromString(page, content) {
- // Parse page YAML
- var parsed = fm(content);
+ const parsed = fm(content);
return page.merge({
content: parsed.body,
diff --git a/lib/parse/parsePagesList.js b/packages/gitbook/src/parse/parsePagesList.js
index fa15a9d..89a1a4f 100644
--- a/lib/parse/parsePagesList.js
+++ b/packages/gitbook/src/parse/parsePagesList.js
@@ -1,25 +1,25 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var timing = require('../utils/timing');
-var Page = require('../models/page');
-var walkSummary = require('./walkSummary');
-var parsePage = require('./parsePage');
+const timing = require('../utils/timing');
+const Page = require('../models/page');
+const walkSummary = require('./walkSummary');
+const parsePage = require('./parsePage');
/**
- Parse a page from a path
-
- @param {Book} book
- @param {String} filePath
- @return {Page?}
-*/
+ * Parse a page from a path
+ *
+ * @param {Book} book
+ * @param {String} filePath
+ * @return {Page?}
+ */
function parseFilePage(book, filePath) {
- var fs = book.getContentFS();
+ const fs = book.getContentFS();
return fs.statFile(filePath)
.then(
function(file) {
- var page = Page.createForFile(file);
+ const page = Page.createForFile(file);
return parsePage(book, page);
},
function(err) {
@@ -28,7 +28,7 @@ function parseFilePage(book, filePath) {
}
)
.fail(function(err) {
- var logger = book.getLogger();
+ const logger = book.getLogger();
logger.error.ln('error while parsing page "' + filePath + '":');
throw err;
});
@@ -36,15 +36,15 @@ function parseFilePage(book, filePath) {
/**
- Parse all pages from a book as an OrderedMap
-
- @param {Book} book
- @return {Promise<OrderedMap<Page>>}
-*/
+ * Parse all pages from a book as an OrderedMap
+ *
+ * @param {Book} book
+ * @return {Promise<OrderedMap<Page>>}
+ */
function parsePagesList(book) {
- var summary = book.getSummary();
- var glossary = book.getGlossary();
- var map = Immutable.OrderedMap();
+ const summary = book.getSummary();
+ const glossary = book.getGlossary();
+ let map = Immutable.OrderedMap();
// Parse pages from summary
return timing.measure(
@@ -52,7 +52,7 @@ function parsePagesList(book) {
walkSummary(summary, function(article) {
if (!article.isPage()) return;
- var filepath = article.getPath();
+ const filepath = article.getPath();
// Is the page ignored?
if (book.isContentFileIgnored(filepath)) return;
@@ -71,7 +71,7 @@ function parsePagesList(book) {
// Parse glossary
.then(function() {
- var file = glossary.getFile();
+ const file = glossary.getFile();
if (!file.exists()) {
return;
diff --git a/lib/parse/parseReadme.js b/packages/gitbook/src/parse/parseReadme.js
index a2ede77..82f8f19 100644
--- a/lib/parse/parseReadme.js
+++ b/packages/gitbook/src/parse/parseReadme.js
@@ -1,7 +1,7 @@
-var parseStructureFile = require('./parseStructureFile');
-var Readme = require('../models/readme');
+const parseStructureFile = require('./parseStructureFile');
+const Readme = require('../models/readme');
-var error = require('../utils/error');
+const error = require('../utils/error');
/**
Parse readme from book
@@ -10,7 +10,7 @@ var error = require('../utils/error');
@return {Promise<Book>}
*/
function parseReadme(book) {
- var logger = book.getLogger();
+ const logger = book.getLogger();
return parseStructureFile(book, 'readme')
.spread(function(file, result) {
@@ -20,7 +20,7 @@ function parseReadme(book) {
logger.debug.ln('readme found at', file.getPath());
- var readme = Readme.create(file, result);
+ const readme = Readme.create(file, result);
return book.set('readme', readme);
});
}
diff --git a/lib/parse/parseStructureFile.js b/packages/gitbook/src/parse/parseStructureFile.js
index 718f731..951da96 100644
--- a/lib/parse/parseStructureFile.js
+++ b/packages/gitbook/src/parse/parseStructureFile.js
@@ -1,6 +1,6 @@
-var Promise = require('../utils/promise');
-var error = require('../utils/error');
-var lookupStructureFile = require('./lookupStructureFile');
+const Promise = require('../utils/promise');
+const error = require('../utils/error');
+const lookupStructureFile = require('./lookupStructureFile');
/**
Parse a ParsableFile using a specific method
@@ -11,8 +11,8 @@ var lookupStructureFile = require('./lookupStructureFile');
@return {Promise<Array<String, List|Map>>}
*/
function parseFile(fs, file, type) {
- var filepath = file.getPath();
- var parser = file.getParser();
+ const filepath = file.getPath();
+ const parser = file.getParser();
if (!parser) {
return Promise.reject(
@@ -54,7 +54,7 @@ function parseFile(fs, file, type) {
@return {Promise<List|Map>}
*/
function parseStructureFile(book, type) {
- var fs = book.getContentFS();
+ const fs = book.getContentFS();
return lookupStructureFile(book, type)
.then(function(file) {
diff --git a/lib/parse/parseSummary.js b/packages/gitbook/src/parse/parseSummary.js
index 2c1e3b3..9488341 100644
--- a/lib/parse/parseSummary.js
+++ b/packages/gitbook/src/parse/parseSummary.js
@@ -1,6 +1,6 @@
-var parseStructureFile = require('./parseStructureFile');
-var Summary = require('../models/summary');
-var SummaryModifier = require('../modifiers').Summary;
+const parseStructureFile = require('./parseStructureFile');
+const Summary = require('../models/summary');
+const SummaryModifier = require('../modifiers').Summary;
/**
Parse summary in a book, the summary can only be parsed
@@ -10,13 +10,13 @@ var SummaryModifier = require('../modifiers').Summary;
@return {Promise<Book>}
*/
function parseSummary(book) {
- var readme = book.getReadme();
- var logger = book.getLogger();
- var readmeFile = readme.getFile();
+ const readme = book.getReadme();
+ const logger = book.getLogger();
+ const readmeFile = readme.getFile();
return parseStructureFile(book, 'summary')
.spread(function(file, result) {
- var summary;
+ let summary;
if (!file) {
logger.warn.ln('no summary file in this book');
@@ -27,7 +27,7 @@ function parseSummary(book) {
}
// Insert readme as first entry if not in SUMMARY.md
- var readmeArticle = summary.getByPath(readmeFile.getPath());
+ const readmeArticle = summary.getByPath(readmeFile.getPath());
if (readmeFile.exists() && !readmeArticle) {
summary = SummaryModifier.unshiftArticle(summary, {
diff --git a/packages/gitbook/src/parse/parseURIIndexFromPages.js b/packages/gitbook/src/parse/parseURIIndexFromPages.js
new file mode 100644
index 0000000..645d083
--- /dev/null
+++ b/packages/gitbook/src/parse/parseURIIndexFromPages.js
@@ -0,0 +1,44 @@
+const path = require('path');
+const PathUtils = require('../utils/path');
+const LocationUtils = require('../utils/location');
+const URIIndex = require('../models/uriIndex');
+
+const OUTPUT_EXTENSION = '.html';
+
+/**
+ * Convert a filePath (absolute) to an url (without hostname).
+ * It returns an absolute path.
+ *
+ * "README.md" -> "/index.html"
+ * "test/hello.md" -> "test/hello.html"
+ * "test/README.md" -> "test/index.html"
+ *
+ * @param {Output} output
+ * @param {String} filePath
+ * @return {String}
+ */
+function fileToURL(filePath) {
+ if (
+ path.basename(filePath, path.extname(filePath)) == 'README'
+ ) {
+ filePath = path.join(path.dirname(filePath), 'index' + OUTPUT_EXTENSION);
+ } else {
+ filePath = PathUtils.setExtension(filePath, OUTPUT_EXTENSION);
+ }
+
+ return LocationUtils.normalize(filePath);
+}
+
+/**
+ * Parse a set of pages into an URIIndex.
+ * Each pages is added as an entry in the index.
+ *
+ * @param {OrderedMap<Page>} pages
+ * @return {URIIndex} index
+ */
+function parseURIIndexFromPages(pages) {
+ const urls = pages.map((page, filePath) => fileToURL(filePath));
+ return new URIIndex(urls);
+}
+
+module.exports = parseURIIndexFromPages;
diff --git a/lib/parse/validateConfig.js b/packages/gitbook/src/parse/validateConfig.js
index 21294ac..e766fae 100644
--- a/lib/parse/validateConfig.js
+++ b/packages/gitbook/src/parse/validateConfig.js
@@ -1,9 +1,9 @@
-var jsonschema = require('jsonschema');
-var jsonSchemaDefaults = require('json-schema-defaults');
+const jsonschema = require('jsonschema');
+const jsonSchemaDefaults = require('json-schema-defaults');
-var schema = require('../constants/configSchema');
-var error = require('../utils/error');
-var mergeDefaults = require('../utils/mergeDefaults');
+const schema = require('../constants/configSchema');
+const error = require('../utils/error');
+const mergeDefaults = require('../utils/mergeDefaults');
/**
Validate a book.json content
@@ -13,8 +13,8 @@ var mergeDefaults = require('../utils/mergeDefaults');
@return {Object}
*/
function validateConfig(bookJson) {
- var v = new jsonschema.Validator();
- var result = v.validate(bookJson, schema, {
+ const v = new jsonschema.Validator();
+ const result = v.validate(bookJson, schema, {
propertyName: 'config'
});
@@ -24,7 +24,7 @@ function validateConfig(bookJson) {
}
// Insert default values
- var defaults = jsonSchemaDefaults(schema);
+ const defaults = jsonSchemaDefaults(schema);
return mergeDefaults(bookJson, defaults);
}
diff --git a/lib/parse/walkSummary.js b/packages/gitbook/src/parse/walkSummary.js
index 0117752..47feb1f 100644
--- a/lib/parse/walkSummary.js
+++ b/packages/gitbook/src/parse/walkSummary.js
@@ -1,4 +1,4 @@
-var Promise = require('../utils/promise');
+const Promise = require('../utils/promise');
/**
Walk over a list of articles
@@ -24,7 +24,7 @@ function walkArticles(articles, fn) {
@return {Promise}
*/
function walkSummary(summary, fn) {
- var parts = summary.getParts();
+ const parts = summary.getParts();
return Promise.forEach(parts, function(part) {
return walkArticles(part.getArticles(), fn);
diff --git a/lib/parsers.js b/packages/gitbook/src/parsers.js
index 70e44f4..62c3776 100644
--- a/lib/parsers.js
+++ b/packages/gitbook/src/parsers.js
@@ -1,15 +1,15 @@
-var path = require('path');
-var Immutable = require('immutable');
+const path = require('path');
+const Immutable = require('immutable');
-var markdownParser = require('gitbook-markdown');
-var asciidocParser = require('gitbook-asciidoc');
+const markdownParser = require('gitbook-markdown');
+const asciidocParser = require('gitbook-asciidoc');
-var EXTENSIONS_MARKDOWN = require('./constants/extsMarkdown');
-var EXTENSIONS_ASCIIDOC = require('./constants/extsAsciidoc');
-var Parser = require('./models/parser');
+const EXTENSIONS_MARKDOWN = require('./constants/extsMarkdown');
+const EXTENSIONS_ASCIIDOC = require('./constants/extsAsciidoc');
+const Parser = require('./models/parser');
// This list is ordered by priority of parsers to use
-var parsers = Immutable.List([
+const parsers = Immutable.List([
Parser.create('markdown', EXTENSIONS_MARKDOWN, markdownParser),
Parser.create('asciidoc', EXTENSIONS_ASCIIDOC, asciidocParser)
]);
@@ -49,14 +49,14 @@ function getParserForFile(filename) {
}
// List all parsable extensions
-var extensions = parsers
+const extensions = parsers
.map(function(parser) {
return parser.getExtensions();
})
.flatten();
module.exports = {
- extensions: extensions,
+ extensions,
get: getParser,
getByExt: getParserByExt,
getForFile: getParserForFile
diff --git a/packages/gitbook/src/plugins/__tests__/findForBook.js b/packages/gitbook/src/plugins/__tests__/findForBook.js
new file mode 100644
index 0000000..0d12aa1
--- /dev/null
+++ b/packages/gitbook/src/plugins/__tests__/findForBook.js
@@ -0,0 +1,19 @@
+const path = require('path');
+
+const Book = require('../../models/book');
+const createNodeFS = require('../../fs/node');
+const findForBook = require('../findForBook');
+
+describe('findForBook', () => {
+ const fs = createNodeFS(
+ path.resolve(__dirname, '../../..')
+ );
+ const book = Book.createForFS(fs);
+
+ it('should list default plugins', () => {
+ return findForBook(book)
+ .then((plugins) => {
+ expect(plugins.has('theme-default')).toBeTruthy();
+ });
+ });
+});
diff --git a/lib/plugins/__tests__/findInstalled.js b/packages/gitbook/src/plugins/__tests__/findInstalled.js
index 9377190..e787761 100644
--- a/lib/plugins/__tests__/findInstalled.js
+++ b/packages/gitbook/src/plugins/__tests__/findInstalled.js
@@ -1,13 +1,13 @@
-var path = require('path');
-var Immutable = require('immutable');
+const path = require('path');
+const Immutable = require('immutable');
describe('findInstalled', function() {
- var findInstalled = require('../findInstalled');
+ const findInstalled = require('../findInstalled');
it('must list default plugins for gitbook directory', function() {
// Read gitbook-plugins from package.json
- var pkg = require(path.resolve(__dirname, '../../../package.json'));
- var gitbookPlugins = Immutable.Seq(pkg.dependencies)
+ const pkg = require(path.resolve(__dirname, '../../../package.json'));
+ const gitbookPlugins = Immutable.Seq(pkg.dependencies)
.filter(function(v, k) {
return k.indexOf('gitbook-plugin') === 0;
})
@@ -17,7 +17,7 @@ describe('findInstalled', function() {
.then(function(plugins) {
expect(plugins.size >= gitbookPlugins.size).toBeTruthy();
- expect(plugins.has('fontsettings')).toBe(true);
+ expect(plugins.has('highlight')).toBe(true);
expect(plugins.has('search')).toBe(true);
});
});
diff --git a/packages/gitbook/src/plugins/__tests__/installPlugin.js b/packages/gitbook/src/plugins/__tests__/installPlugin.js
new file mode 100644
index 0000000..97f1475
--- /dev/null
+++ b/packages/gitbook/src/plugins/__tests__/installPlugin.js
@@ -0,0 +1,42 @@
+const tmp = require('tmp');
+
+const PluginDependency = require('../../models/pluginDependency');
+const Book = require('../../models/book');
+const NodeFS = require('../../fs/node');
+const installPlugin = require('../installPlugin');
+
+const Parse = require('../../parse');
+
+describe('installPlugin', () => {
+ let book, dir;
+
+ before(() => {
+ dir = tmp.dirSync({ unsafeCleanup: true });
+ const fs = NodeFS(dir.name);
+ const baseBook = Book.createForFS(fs)
+ .setLogLevel('disabled');
+
+ return Parse.parseConfig(baseBook)
+ .then((_book) => {
+ book = _book;
+ });
+ });
+
+ after(() => {
+ dir.removeCallback();
+ });
+
+ it('must install a plugin from NPM', () => {
+ const dep = PluginDependency.createFromString('ga');
+ return installPlugin(book, dep)
+ .then(() => {
+ expect(dir.name).toHaveFile('node_modules/gitbook-plugin-ga/package.json');
+ expect(dir.name).toNotHaveFile('package.json');
+ });
+ });
+
+ it('must install a specific version of a plugin', () => {
+ const dep = PluginDependency.createFromString('ga@0.2.1');
+ return installPlugin(book, dep);
+ });
+});
diff --git a/packages/gitbook/src/plugins/__tests__/installPlugins.js b/packages/gitbook/src/plugins/__tests__/installPlugins.js
new file mode 100644
index 0000000..26f135d
--- /dev/null
+++ b/packages/gitbook/src/plugins/__tests__/installPlugins.js
@@ -0,0 +1,37 @@
+const tmp = require('tmp');
+
+const Book = require('../../models/book');
+const MockFS = require('../../fs/mock');
+const installPlugins = require('../installPlugins');
+
+const Parse = require('../../parse');
+
+describe('installPlugins', () => {
+ let book, dir;
+
+ before(() => {
+ dir = tmp.dirSync({ unsafeCleanup: true });
+
+ const fs = MockFS({
+ 'book.json': JSON.stringify({ plugins: ['ga', 'sitemap' ]})
+ }, dir.name);
+ const baseBook = Book.createForFS(fs)
+ .setLogLevel('disabled');
+
+ return Parse.parseConfig(baseBook)
+ .then((_book) => {
+ book = _book;
+ });
+ });
+
+ after(() => {
+ dir.removeCallback();
+ });
+
+ it('must install all plugins from NPM', () => {
+ return installPlugins(book)
+ .then(function(n) {
+ expect(n).toBe(2);
+ });
+ });
+});
diff --git a/packages/gitbook/src/plugins/__tests__/listDependencies.js b/packages/gitbook/src/plugins/__tests__/listDependencies.js
new file mode 100644
index 0000000..002f0e9
--- /dev/null
+++ b/packages/gitbook/src/plugins/__tests__/listDependencies.js
@@ -0,0 +1,39 @@
+const PluginDependency = require('../../models/pluginDependency');
+const listDependencies = require('../listDependencies');
+const toNames = require('../toNames');
+
+describe('listDependencies', () => {
+ it('must list default', () => {
+ const deps = PluginDependency.listFromString('ga,great');
+ const plugins = listDependencies(deps);
+ const names = toNames(plugins);
+
+ expect(names).toEqual([
+ 'ga', 'great', 'highlight', 'search', 'lunr',
+ 'sharing', 'hints', 'headings', 'copy-code', 'theme-default'
+ ]);
+ });
+
+ it('must list from array with -', () => {
+ const deps = PluginDependency.listFromString('ga,-great');
+ const plugins = listDependencies(deps);
+ const names = toNames(plugins);
+
+ expect(names).toEqual([
+ 'ga', 'highlight', 'search', 'lunr',
+ 'sharing', 'hints', 'headings',
+ 'copy-code', 'theme-default'
+ ]);
+ });
+
+ it('must remove default plugins using -', () => {
+ const deps = PluginDependency.listFromString('ga,-search');
+ const plugins = listDependencies(deps);
+ const names = toNames(plugins);
+
+ expect(names).toEqual([
+ 'ga', 'highlight', 'lunr', 'sharing',
+ 'hints', 'headings', 'copy-code', 'theme-default'
+ ]);
+ });
+});
diff --git a/lib/plugins/__tests__/locateRootFolder.js b/packages/gitbook/src/plugins/__tests__/locateRootFolder.js
index bb414a3..54e095b 100644
--- a/lib/plugins/__tests__/locateRootFolder.js
+++ b/packages/gitbook/src/plugins/__tests__/locateRootFolder.js
@@ -1,5 +1,5 @@
-var path = require('path');
-var locateRootFolder = require('../locateRootFolder');
+const path = require('path');
+const locateRootFolder = require('../locateRootFolder');
describe('locateRootFolder', function() {
it('should correctly resolve the node_modules for gitbook', function() {
diff --git a/lib/plugins/__tests__/resolveVersion.js b/packages/gitbook/src/plugins/__tests__/resolveVersion.js
index 1877c9e..949d078 100644
--- a/lib/plugins/__tests__/resolveVersion.js
+++ b/packages/gitbook/src/plugins/__tests__/resolveVersion.js
@@ -1,9 +1,9 @@
-var PluginDependency = require('../../models/pluginDependency');
-var resolveVersion = require('../resolveVersion');
+const PluginDependency = require('../../models/pluginDependency');
+const resolveVersion = require('../resolveVersion');
describe('resolveVersion', function() {
it('must skip resolving and return non-semver versions', function() {
- var plugin = PluginDependency.createFromString('ga@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
+ const plugin = PluginDependency.createFromString('ga@git+ssh://samy@github.com/GitbookIO/plugin-ga.git');
return resolveVersion(plugin)
.then(function(version) {
@@ -12,7 +12,7 @@ describe('resolveVersion', function() {
});
it('must resolve a normal plugin dependency', function() {
- var plugin = PluginDependency.createFromString('ga@>0.9.0 < 1.0.1');
+ const plugin = PluginDependency.createFromString('ga@>0.9.0 < 1.0.1');
return resolveVersion(plugin)
.then(function(version) {
diff --git a/lib/plugins/__tests__/sortDependencies.js b/packages/gitbook/src/plugins/__tests__/sortDependencies.js
index 87df477..a08d59d 100644
--- a/lib/plugins/__tests__/sortDependencies.js
+++ b/packages/gitbook/src/plugins/__tests__/sortDependencies.js
@@ -1,17 +1,17 @@
-var PluginDependency = require('../../models/pluginDependency');
-var sortDependencies = require('../sortDependencies');
-var toNames = require('../toNames');
+const PluginDependency = require('../../models/pluginDependency');
+const sortDependencies = require('../sortDependencies');
+const toNames = require('../toNames');
describe('sortDependencies', function() {
it('must load themes after plugins', function() {
- var allPlugins = PluginDependency.listFromArray([
+ const allPlugins = PluginDependency.listFromArray([
'hello',
'theme-test',
'world'
]);
- var sorted = sortDependencies(allPlugins);
- var names = toNames(sorted);
+ const sorted = sortDependencies(allPlugins);
+ const names = toNames(sorted);
expect(names).toEqual([
'hello',
@@ -21,15 +21,15 @@ describe('sortDependencies', function() {
});
it('must keep order of themes', function() {
- var allPlugins = PluginDependency.listFromArray([
+ const allPlugins = PluginDependency.listFromArray([
'theme-test',
'theme-test1',
'hello',
'theme-test2',
'world'
]);
- var sorted = sortDependencies(allPlugins);
- var names = toNames(sorted);
+ const sorted = sortDependencies(allPlugins);
+ const names = toNames(sorted);
expect(names).toEqual([
'hello',
@@ -39,4 +39,4 @@ describe('sortDependencies', function() {
'theme-test2'
]);
});
-}); \ No newline at end of file
+});
diff --git a/lib/plugins/__tests__/validatePlugin.js b/packages/gitbook/src/plugins/__tests__/validatePlugin.js
index 635423c..a2bd23b 100644
--- a/lib/plugins/__tests__/validatePlugin.js
+++ b/packages/gitbook/src/plugins/__tests__/validatePlugin.js
@@ -1,10 +1,10 @@
-var Promise = require('../../utils/promise');
-var Plugin = require('../../models/plugin');
-var validatePlugin = require('../validatePlugin');
+const Promise = require('../../utils/promise');
+const Plugin = require('../../models/plugin');
+const validatePlugin = require('../validatePlugin');
describe('validatePlugin', function() {
it('must not validate a not loaded plugin', function() {
- var plugin = Plugin.createFromString('test');
+ const plugin = Plugin.createFromString('test');
return validatePlugin(plugin)
.then(function() {
diff --git a/lib/plugins/findForBook.js b/packages/gitbook/src/plugins/findForBook.js
index be2ad9f..8668d1d 100644
--- a/lib/plugins/findForBook.js
+++ b/packages/gitbook/src/plugins/findForBook.js
@@ -1,9 +1,9 @@
-var Immutable = require('immutable');
+const { List, OrderedMap } = require('immutable');
-var Promise = require('../utils/promise');
-var timing = require('../utils/timing');
-var findInstalled = require('./findInstalled');
-var locateRootFolder = require('./locateRootFolder');
+const Promise = require('../utils/promise');
+const timing = require('../utils/timing');
+const findInstalled = require('./findInstalled');
+const locateRootFolder = require('./locateRootFolder');
/**
* List all plugins installed in a book
@@ -22,13 +22,12 @@ function findForBook(book) {
// Merge all plugins
.then(function(results) {
- return Immutable.List(results)
+ return List(results)
.reduce(function(out, result) {
return out.merge(result);
- }, Immutable.OrderedMap());
+ }, OrderedMap());
})
);
}
-
module.exports = findForBook;
diff --git a/packages/gitbook/src/plugins/findInstalled.js b/packages/gitbook/src/plugins/findInstalled.js
new file mode 100644
index 0000000..fb690c2
--- /dev/null
+++ b/packages/gitbook/src/plugins/findInstalled.js
@@ -0,0 +1,81 @@
+const { OrderedMap } = require('immutable');
+const path = require('path');
+
+const Promise = require('../utils/promise');
+const fs = require('../utils/fs');
+const Plugin = require('../models/plugin');
+const PREFIX = require('../constants/pluginPrefix');
+
+/**
+ * Validate if a package name is a GitBook plugin
+ *
+ * @return {Boolean}
+ */
+function validateId(name) {
+ return name && name.indexOf(PREFIX) === 0;
+}
+
+/**
+ * Read details about a node module.
+ * @param {String} modulePath
+ * @param {Number} depth
+ * @param {String} parent
+ * @return {Plugin} plugin
+ */
+function readModule(modulePath, depth, parent) {
+ const pkg = require(path.join(modulePath, 'package.json'));
+ const pluginName = pkg.name.slice(PREFIX.length);
+
+ return new Plugin({
+ name: pluginName,
+ version: pkg.version,
+ path: modulePath,
+ depth,
+ parent
+ });
+}
+
+/**
+ * List all packages installed inside a folder
+ *
+ * @param {String} folder
+ * @param {Number} depth
+ * @param {String} parent
+ * @return {Promise<OrderedMap<String:Plugin>>} plugins
+ */
+function findInstalled(folder, depth = 0, parent = null) {
+ // When tetsing with mock-fs
+ if (!folder) {
+ return Promise(OrderedMap());
+ }
+
+ // Search for gitbook-plugins in node_modules folder
+ const node_modules = path.join(folder, 'node_modules');
+
+ // List all folders in node_modules
+ return fs.readdir(node_modules)
+ .fail(() => {
+ return Promise([]);
+ })
+ .then((modules) => {
+ return Promise.reduce(modules, (results, moduleName) => {
+ // Not a gitbook-plugin
+ if (!validateId(moduleName)) {
+ return results;
+ }
+
+ // Read gitbook-plugin package details
+ const moduleFolder = path.join(node_modules, moduleName);
+ const plugin = readModule(moduleFolder, depth, parent);
+
+ results = results.set(plugin.getName(), plugin);
+
+ return findInstalled(moduleFolder, depth + 1, plugin.getName())
+ .then((innerModules) => {
+ return results.merge(innerModules);
+ });
+ }, OrderedMap());
+ });
+}
+
+module.exports = findInstalled;
diff --git a/packages/gitbook/src/plugins/index.js b/packages/gitbook/src/plugins/index.js
new file mode 100644
index 0000000..bdc3b05
--- /dev/null
+++ b/packages/gitbook/src/plugins/index.js
@@ -0,0 +1,8 @@
+
+module.exports = {
+ loadForBook: require('./loadForBook'),
+ validateConfig: require('./validateConfig'),
+ installPlugins: require('./installPlugins'),
+ listBlocks: require('./listBlocks'),
+ listFilters: require('./listFilters')
+};
diff --git a/packages/gitbook/src/plugins/installPlugin.js b/packages/gitbook/src/plugins/installPlugin.js
new file mode 100644
index 0000000..9834d05
--- /dev/null
+++ b/packages/gitbook/src/plugins/installPlugin.js
@@ -0,0 +1,44 @@
+const resolve = require('resolve');
+
+const { exec } = require('../utils/command');
+const resolveVersion = require('./resolveVersion');
+
+/**
+ * Install a plugin for a book
+ *
+ * @param {Book} book
+ * @param {PluginDependency} plugin
+ * @return {Promise}
+ */
+function installPlugin(book, plugin) {
+ const logger = book.getLogger();
+
+ const installFolder = book.getRoot();
+ const name = plugin.getName();
+ const requirement = plugin.getVersion();
+
+ logger.info.ln('');
+ logger.info.ln('installing plugin "' + name + '"');
+
+ const installerBin = resolve.sync('ied/lib/cmd.js');
+
+ // Find a version to install
+ return resolveVersion(plugin)
+ .then(function(version) {
+ if (!version) {
+ throw new Error('Found no satisfactory version for plugin "' + name + '" with requirement "' + requirement + '"');
+ }
+
+ logger.info.ln('install plugin "' + name + '" (' + requirement + ') with version', version);
+
+ const npmID = plugin.getNpmID();
+ const command = `${installerBin} install ${npmID}@${version}`;
+
+ return exec(command, { cwd: installFolder });
+ })
+ .then(function() {
+ logger.info.ok('plugin "' + name + '" installed with success');
+ });
+}
+
+module.exports = installPlugin;
diff --git a/lib/plugins/installPlugins.js b/packages/gitbook/src/plugins/installPlugins.js
index 307c41e..9d2520f 100644
--- a/lib/plugins/installPlugins.js
+++ b/packages/gitbook/src/plugins/installPlugins.js
@@ -1,24 +1,22 @@
-var npmi = require('npmi');
-
-var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
-var Promise = require('../utils/promise');
-var installPlugin = require('./installPlugin');
+const DEFAULT_PLUGINS = require('../constants/defaultPlugins');
+const Promise = require('../utils/promise');
+const installPlugin = require('./installPlugin');
/**
- Install plugin requirements for a book
-
- @param {Book}
- @return {Promise<Number>}
-*/
+ * Install plugin requirements for a book
+ *
+ * @param {Book} book
+ * @return {Promise<Number>} count
+ */
function installPlugins(book) {
- var logger = book.getLogger();
- var config = book.getConfig();
- var plugins = config.getPluginDependencies();
+ const logger = book.getLogger();
+ const config = book.getConfig();
+ let plugins = config.getPluginDependencies();
// Remove default plugins
// (only if version is same as installed)
plugins = plugins.filterNot(function(plugin) {
- var dependency = DEFAULT_PLUGINS.find(function(dep) {
+ const dependency = DEFAULT_PLUGINS.find(function(dep) {
return dep.getName() === plugin.getName();
});
@@ -34,10 +32,10 @@ function installPlugins(book) {
if (plugins.size == 0) {
logger.info.ln('nothing to install!');
- return Promise();
+ return Promise(0);
}
- logger.info.ln('installing', plugins.size, 'plugins using npm@' + npmi.NPM_VERSION);
+ logger.info.ln('installing', plugins.size, 'plugins from registry');
return Promise.forEach(plugins, function(plugin) {
return installPlugin(book, plugin);
diff --git a/packages/gitbook/src/plugins/listBlocks.js b/packages/gitbook/src/plugins/listBlocks.js
new file mode 100644
index 0000000..a2b04f5
--- /dev/null
+++ b/packages/gitbook/src/plugins/listBlocks.js
@@ -0,0 +1,21 @@
+const { Map } = require('immutable');
+
+/**
+ * List blocks from a list of plugins
+ *
+ * @param {OrderedMap<String:Plugin>}
+ * @return {Map<String:TemplateBlock>}
+ */
+function listBlocks(plugins) {
+ return plugins
+ .reverse()
+ .reduce(
+ (result, plugin) => {
+ const blocks = plugin.getBlocks();
+ return result.merge(blocks);
+ },
+ Map()
+ );
+}
+
+module.exports = listBlocks;
diff --git a/lib/plugins/listDependencies.js b/packages/gitbook/src/plugins/listDependencies.js
index d52eaa9..3930ae7 100644
--- a/lib/plugins/listDependencies.js
+++ b/packages/gitbook/src/plugins/listDependencies.js
@@ -1,5 +1,5 @@
-var DEFAULT_PLUGINS = require('../constants/defaultPlugins');
-var sortDependencies = require('./sortDependencies');
+const DEFAULT_PLUGINS = require('../constants/defaultPlugins');
+const sortDependencies = require('./sortDependencies');
/**
* List all dependencies for a book, including default plugins.
@@ -10,7 +10,7 @@ var sortDependencies = require('./sortDependencies');
*/
function listDependencies(deps) {
// Extract list of plugins to disable (starting with -)
- var toRemove = deps
+ const toRemove = deps
.filter(function(plugin) {
return !plugin.isEnabled();
})
diff --git a/lib/plugins/listDepsForBook.js b/packages/gitbook/src/plugins/listDepsForBook.js
index 196e3aa..81f619d 100644
--- a/lib/plugins/listDepsForBook.js
+++ b/packages/gitbook/src/plugins/listDepsForBook.js
@@ -1,16 +1,16 @@
-var listDependencies = require('./listDependencies');
+const listDependencies = require('./listDependencies');
/**
* List all plugin requirements for a book.
* It can be different from the final list of plugins,
* since plugins can have their own dependencies
*
- * @param {Book}
- * @return {List<PluginDependency>}
+ * @param {Book} book
+ * @return {List<PluginDependency>} dependencies
*/
function listDepsForBook(book) {
- var config = book.getConfig();
- var plugins = config.getPluginDependencies();
+ const config = book.getConfig();
+ const plugins = config.getPluginDependencies();
return listDependencies(plugins);
}
diff --git a/packages/gitbook/src/plugins/listFilters.js b/packages/gitbook/src/plugins/listFilters.js
new file mode 100644
index 0000000..57d5c29
--- /dev/null
+++ b/packages/gitbook/src/plugins/listFilters.js
@@ -0,0 +1,20 @@
+const { Map } = require('immutable');
+
+/**
+ * List filters from a list of plugins
+ *
+ * @param {OrderedMap<String:Plugin>} plugins
+ * @return {Map<String:Function>} filters
+ */
+function listFilters(plugins) {
+ return plugins
+ .reverse()
+ .reduce(
+ (result, plugin) => {
+ return result.merge(plugin.getFilters());
+ },
+ Map()
+ );
+}
+
+module.exports = listFilters;
diff --git a/lib/plugins/loadForBook.js b/packages/gitbook/src/plugins/loadForBook.js
index 757677e..0baa78e 100644
--- a/lib/plugins/loadForBook.js
+++ b/packages/gitbook/src/plugins/loadForBook.js
@@ -1,9 +1,9 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var Promise = require('../utils/promise');
-var listDepsForBook = require('./listDepsForBook');
-var findForBook = require('./findForBook');
-var loadPlugin = require('./loadPlugin');
+const Promise = require('../utils/promise');
+const listDepsForBook = require('./listDepsForBook');
+const findForBook = require('./findForBook');
+const loadPlugin = require('./loadPlugin');
/**
@@ -13,21 +13,21 @@ var loadPlugin = require('./loadPlugin');
* @return {Promise<Map<String:Plugin>}
*/
function loadForBook(book) {
- var logger = book.getLogger();
+ const logger = book.getLogger();
// List the dependencies
- var requirements = listDepsForBook(book);
+ const requirements = listDepsForBook(book);
// List all plugins installed in the book
return findForBook(book)
.then(function(installedMap) {
- var missing = [];
- var plugins = requirements.reduce(function(result, dep) {
- var name = dep.getName();
- var installed = installedMap.get(name);
+ const missing = [];
+ let plugins = requirements.reduce(function(result, dep) {
+ const name = dep.getName();
+ const installed = installedMap.get(name);
if (installed) {
- var deps = installedMap
+ const deps = installedMap
.filter(function(plugin) {
return plugin.getParent() === name;
})
diff --git a/lib/plugins/loadPlugin.js b/packages/gitbook/src/plugins/loadPlugin.js
index 9ed83a1..167587a 100644
--- a/lib/plugins/loadPlugin.js
+++ b/packages/gitbook/src/plugins/loadPlugin.js
@@ -1,12 +1,12 @@
-var path = require('path');
-var resolve = require('resolve');
-var Immutable = require('immutable');
+const path = require('path');
+const resolve = require('resolve');
+const Immutable = require('immutable');
-var Promise = require('../utils/promise');
-var error = require('../utils/error');
-var timing = require('../utils/timing');
+const Promise = require('../utils/promise');
+const error = require('../utils/error');
+const timing = require('../utils/timing');
-var validatePlugin = require('./validatePlugin');
+const validatePlugin = require('./validatePlugin');
// Return true if an error is a "module not found"
// Wait on https://github.com/substack/node-resolve/pull/81 to be merged
@@ -15,29 +15,29 @@ function isModuleNotFound(err) {
}
/**
- Load a plugin in a book
-
- @param {Book} book
- @param {Plugin} plugin
- @param {String} pkgPath (optional)
- @return {Promise<Plugin>}
-*/
+ * Load a plugin in a book
+ *
+ * @param {Book} book
+ * @param {Plugin} plugin
+ * @param {String} pkgPath (optional)
+ * @return {Promise<Plugin>}
+ */
function loadPlugin(book, plugin) {
- var logger = book.getLogger();
+ const logger = book.getLogger();
- var name = plugin.getName();
- var pkgPath = plugin.getPath();
+ const name = plugin.getName();
+ let pkgPath = plugin.getPath();
// Try loading plugins from different location
- var p = Promise()
+ let p = Promise()
.then(function() {
- var packageContent;
- var packageMain;
- var content;
+ let packageContent;
+ let packageMain;
+ let content;
// Locate plugin and load package.json
try {
- var res = resolve.sync('./package.json', { basedir: pkgPath });
+ const res = resolve.sync('./package.json', { basedir: pkgPath });
pkgPath = path.dirname(res);
packageContent = require(res);
@@ -52,7 +52,7 @@ function loadPlugin(book, plugin) {
// Locate the main package
try {
- var indexJs = path.normalize(packageContent.main || 'index.js');
+ const indexJs = path.normalize(packageContent.main || 'index.js');
packageMain = resolve.sync('./' + indexJs, { basedir: pkgPath });
} catch (err) {
if (!isModuleNotFound(err)) throw err;
@@ -63,7 +63,7 @@ function loadPlugin(book, plugin) {
if (packageMain) {
try {
content = require(packageMain);
- } catch(err) {
+ } catch (err) {
throw new error.PluginError(err, {
plugin: name
});
diff --git a/packages/gitbook/src/plugins/locateRootFolder.js b/packages/gitbook/src/plugins/locateRootFolder.js
new file mode 100644
index 0000000..64e06a8
--- /dev/null
+++ b/packages/gitbook/src/plugins/locateRootFolder.js
@@ -0,0 +1,22 @@
+const path = require('path');
+const resolve = require('resolve');
+
+const DEFAULT_PLUGINS = require('../constants/defaultPlugins');
+
+/**
+ * Resolve the root folder containing for node_modules
+ * since gitbook can be used as a library and dependency can be flattened.
+ *
+ * @return {String} folderPath
+ */
+function locateRootFolder() {
+ const firstDefaultPlugin = DEFAULT_PLUGINS.first();
+ const pluginPath = resolve.sync(firstDefaultPlugin.getNpmID() + '/package.json', {
+ basedir: __dirname
+ });
+ const nodeModules = path.resolve(pluginPath, '../../..');
+
+ return nodeModules;
+}
+
+module.exports = locateRootFolder;
diff --git a/lib/plugins/resolveVersion.js b/packages/gitbook/src/plugins/resolveVersion.js
index 61aef8d..a241c23 100644
--- a/lib/plugins/resolveVersion.js
+++ b/packages/gitbook/src/plugins/resolveVersion.js
@@ -1,18 +1,17 @@
-var npm = require('npm');
-var semver = require('semver');
-var Immutable = require('immutable');
+const npm = require('npm');
+const semver = require('semver');
+const { Map } = require('immutable');
-var Promise = require('../utils/promise');
-var Plugin = require('../models/plugin');
-var gitbook = require('../gitbook');
+const Promise = require('../utils/promise');
+const Plugin = require('../models/plugin');
+const gitbook = require('../gitbook');
-var npmIsReady;
+let npmIsReady;
/**
- Initialize and prepare NPM
-
- @return {Promise}
-*/
+ * Initialize and prepare NPM
+ * @return {Promise}
+ */
function initNPM() {
if (npmIsReady) return npmIsReady;
@@ -25,14 +24,14 @@ function initNPM() {
}
/**
- Resolve a plugin dependency to a version
-
- @param {PluginDependency} plugin
- @return {Promise<String>}
-*/
+ * Resolve a plugin dependency to a version
+ *
+ * @param {PluginDependency} plugin
+ * @return {Promise<String>}
+ */
function resolveVersion(plugin) {
- var npmId = Plugin.nameToNpmID(plugin.getName());
- var requiredVersion = plugin.getVersion();
+ const npmId = Plugin.nameToNpmID(plugin.getName());
+ const requiredVersion = plugin.getVersion();
if (plugin.isGitDependency()) {
return Promise.resolve(requiredVersion);
@@ -43,9 +42,9 @@ function resolveVersion(plugin) {
return Promise.nfcall(npm.commands.view, [npmId + '@' + requiredVersion, 'engines'], true);
})
.then(function(versions) {
- versions = Immutable.Map(versions).entrySeq();
+ versions = Map(versions).entrySeq();
- var result = versions
+ const result = versions
.map(function(entry) {
return {
version: entry[0],
@@ -56,7 +55,7 @@ function resolveVersion(plugin) {
return v.gitbook && gitbook.satisfies(v.gitbook);
})
.sort(function(v1, v2) {
- return semver.lt(v1.version, v2.version)? 1 : -1;
+ return semver.lt(v1.version, v2.version) ? 1 : -1;
})
.get(0);
diff --git a/lib/plugins/sortDependencies.js b/packages/gitbook/src/plugins/sortDependencies.js
index 7f10095..2adfa20 100644
--- a/lib/plugins/sortDependencies.js
+++ b/packages/gitbook/src/plugins/sortDependencies.js
@@ -1,9 +1,9 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
-var THEME_PREFIX = require('../constants/themePrefix');
+const THEME_PREFIX = require('../constants/themePrefix');
-var TYPE_PLUGIN = 'plugin';
-var TYPE_THEME = 'theme';
+const TYPE_PLUGIN = 'plugin';
+const TYPE_THEME = 'theme';
/**
@@ -12,7 +12,7 @@ var TYPE_THEME = 'theme';
* @return {String}
*/
function pluginType(plugin) {
- var name = plugin.getName();
+ const name = plugin.getName();
return (name && name.indexOf(THEME_PREFIX) === 0) ? TYPE_THEME : TYPE_PLUGIN;
}
@@ -25,10 +25,10 @@ function pluginType(plugin) {
* @return {List<PluginDependency>}
*/
function sortDependencies(plugins) {
- var byTypes = plugins.groupBy(pluginType);
+ const byTypes = plugins.groupBy(pluginType);
return byTypes.get(TYPE_PLUGIN, Immutable.List())
.concat(byTypes.get(TYPE_THEME, Immutable.List()));
}
-module.exports = sortDependencies; \ No newline at end of file
+module.exports = sortDependencies;
diff --git a/lib/plugins/toNames.js b/packages/gitbook/src/plugins/toNames.js
index ad0dd8f..422a24d 100644
--- a/lib/plugins/toNames.js
+++ b/packages/gitbook/src/plugins/toNames.js
@@ -1,6 +1,6 @@
/**
- * Return list of plugin names. This method is nly used in unit tests.
+ * Return list of plugin names. This method is only used in unit tests.
*
* @param {OrderedMap<String:Plugin} plugins
* @return {Array<String>}
diff --git a/packages/gitbook/src/plugins/validateConfig.js b/packages/gitbook/src/plugins/validateConfig.js
new file mode 100644
index 0000000..82a2507
--- /dev/null
+++ b/packages/gitbook/src/plugins/validateConfig.js
@@ -0,0 +1,71 @@
+const Immutable = require('immutable');
+const jsonschema = require('jsonschema');
+const jsonSchemaDefaults = require('json-schema-defaults');
+
+const Promise = require('../utils/promise');
+const error = require('../utils/error');
+const mergeDefaults = require('../utils/mergeDefaults');
+
+/**
+ * Validate one plugin for a book and update book's confiration
+ *
+ * @param {Book}
+ * @param {Plugin}
+ * @return {Book}
+ */
+function validatePluginConfig(book, plugin) {
+ let config = book.getConfig();
+ const packageInfos = plugin.getPackage();
+
+ const configKey = [
+ 'pluginsConfig',
+ plugin.getName()
+ ].join('.');
+
+ let pluginConfig = config.getValue(configKey, {}).toJS();
+
+ const schema = (packageInfos.get('gitbook') || Immutable.Map()).toJS();
+ if (!schema) return book;
+
+ // Normalize schema
+ schema.id = '/' + configKey;
+ schema.type = 'object';
+
+ // Validate and throw if invalid
+ const v = new jsonschema.Validator();
+ const result = v.validate(pluginConfig, schema, {
+ propertyName: configKey
+ });
+
+ // Throw error
+ if (result.errors.length > 0) {
+ throw new error.ConfigurationError(new Error(result.errors[0].stack));
+ }
+
+ // Insert default values
+ const defaults = jsonSchemaDefaults(schema);
+ pluginConfig = mergeDefaults(pluginConfig, defaults);
+
+
+ // Update configuration
+ config = config.setValue(configKey, pluginConfig);
+
+ // Return new book
+ return book.set('config', config);
+}
+
+/**
+ * Validate a book configuration for plugins and
+ * returns an update configuration with default values.
+ *
+ * @param {Book}
+ * @param {OrderedMap<String:Plugin>}
+ * @return {Promise<Book>}
+ */
+function validateConfig(book, plugins) {
+ return Promise.reduce(plugins, function(newBook, plugin) {
+ return validatePluginConfig(newBook, plugin);
+ }, book);
+}
+
+module.exports = validateConfig;
diff --git a/lib/plugins/validatePlugin.js b/packages/gitbook/src/plugins/validatePlugin.js
index 4baa911..cc9ac7b 100644
--- a/lib/plugins/validatePlugin.js
+++ b/packages/gitbook/src/plugins/validatePlugin.js
@@ -1,17 +1,17 @@
-var gitbook = require('../gitbook');
+const gitbook = require('../gitbook');
-var Promise = require('../utils/promise');
+const Promise = require('../utils/promise');
/**
- Validate a plugin
-
- @param {Plugin}
- @return {Promise<Plugin>}
-*/
+ * Validate a plugin
+ *
+ * @param {Plugin}
+ * @return {Promise<Plugin>}
+ */
function validatePlugin(plugin) {
- var packageInfos = plugin.getPackage();
+ const packageInfos = plugin.getPackage();
- var isValid = (
+ const isValid = (
plugin.isLoaded() &&
packageInfos &&
packageInfos.get('name') &&
@@ -23,7 +23,7 @@ function validatePlugin(plugin) {
return Promise.reject(new Error('Error loading plugin "' + plugin.getName() + '" at "' + plugin.getPath() + '"'));
}
- var engine = packageInfos.get('engines').get('gitbook');
+ const engine = packageInfos.get('engines').get('gitbook');
if (!gitbook.satisfies(engine)) {
return Promise.reject(new Error('GitBook doesn\'t satisfy the requirements of this plugin: ' + engine));
}
diff --git a/packages/gitbook/src/templating/__tests__/conrefsLoader.js b/packages/gitbook/src/templating/__tests__/conrefsLoader.js
new file mode 100644
index 0000000..1b8e92f
--- /dev/null
+++ b/packages/gitbook/src/templating/__tests__/conrefsLoader.js
@@ -0,0 +1,111 @@
+const path = require('path');
+
+const TemplateEngine = require('../../models/templateEngine');
+const renderTemplate = require('../render');
+const ConrefsLoader = require('../conrefsLoader');
+
+describe('ConrefsLoader', () => {
+ const dirName = __dirname + '/';
+ const fileName = path.join(dirName, 'test.md');
+
+ describe('Git', () => {
+ let engine;
+
+ before(() => {
+ engine = new TemplateEngine({
+ loader: new ConrefsLoader(dirName)
+ });
+ });
+
+ it('should include content from git', () => {
+ return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md" %}')
+ .then((out) => {
+ expect(out).toBe('Hello from git');
+ });
+ });
+
+ it('should handle deep inclusion (1)', () => {
+ return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test2.md" %}')
+ .then((out) => {
+ expect(out).toBe('First Hello. Hello from git');
+ });
+ });
+
+ it('should handle deep inclusion (2)', () => {
+ return renderTemplate(engine, fileName, '{% include "git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test3.md" %}')
+ .then((out) => {
+ expect(out).toBe('First Hello. Hello from git');
+ });
+ });
+ });
+
+ describe('Local', () => {
+ let engine;
+
+ before(() => {
+ engine = new TemplateEngine({
+ loader: new ConrefsLoader(dirName)
+ });
+ });
+
+ describe('Relative', () => {
+ it('should resolve basic relative filepath', () => {
+ return renderTemplate(engine, fileName, '{% include "include.md" %}')
+ .then((out) => {
+ expect(out).toBe('Hello World');
+ });
+ });
+
+ it('should resolve basic parent filepath', () => {
+ return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "../include.md" %}')
+ .then((out) => {
+ expect(out).toBe('Hello World');
+ });
+ });
+ });
+
+ describe('Absolute', function() {
+ it('should resolve absolute filepath', () => {
+ return renderTemplate(engine, fileName, '{% include "/include.md" %}')
+ .then((out) => {
+ expect(out).toBe('Hello World');
+ });
+ });
+
+ it('should resolve absolute filepath when in a directory', () => {
+ return renderTemplate(engine, path.join(dirName, 'hello/test.md'), '{% include "/include.md" %}')
+ .then((out) => {
+ expect(out).toBe('Hello World');
+ });
+ });
+ });
+
+ });
+
+ describe('transform', () => {
+ function transform(filePath, source) {
+ expect(filePath).toBeA('string');
+ expect(source).toBeA('string');
+
+ expect(filePath).toBe(path.resolve(__dirname, 'include.md'));
+ expect(source).toBe('Hello World');
+
+ return 'test-' + source + '-endtest';
+ }
+
+ let engine;
+
+ before(() => {
+ engine = new TemplateEngine({
+ loader: new ConrefsLoader(dirName, transform)
+ });
+ });
+
+ it('should transform included content', () => {
+ return renderTemplate(engine, fileName, '{% include "include.md" %}')
+ .then((out) => {
+ expect(out).toBe('test-Hello World-endtest');
+ });
+ });
+ });
+});
diff --git a/lib/templating/__tests__/include.md b/packages/gitbook/src/templating/__tests__/include.md
index 5e1c309..5e1c309 100644
--- a/lib/templating/__tests__/include.md
+++ b/packages/gitbook/src/templating/__tests__/include.md
diff --git a/packages/gitbook/src/templating/__tests__/replaceShortcuts.js b/packages/gitbook/src/templating/__tests__/replaceShortcuts.js
new file mode 100644
index 0000000..1126f91
--- /dev/null
+++ b/packages/gitbook/src/templating/__tests__/replaceShortcuts.js
@@ -0,0 +1,31 @@
+const Immutable = require('immutable');
+
+const TemplateBlock = require('../../models/templateBlock');
+const replaceShortcuts = require('../replaceShortcuts');
+
+describe('replaceShortcuts', function() {
+ const blocks = Immutable.List([
+ TemplateBlock.create('math', {
+ shortcuts: {
+ start: '$$',
+ end: '$$',
+ parsers: ['markdown']
+ }
+ })
+ ]);
+
+ it('should correctly replace inline matches by block', function() {
+ const content = replaceShortcuts(blocks, 'test.md', 'Hello $$a = b$$');
+ expect(content).toBe('Hello {% math %}a = b{% endmath %}');
+ });
+
+ it('should correctly replace multiple inline matches by block', function() {
+ const content = replaceShortcuts(blocks, 'test.md', 'Hello $$a = b$$ and $$c = d$$');
+ expect(content).toBe('Hello {% math %}a = b{% endmath %} and {% math %}c = d{% endmath %}');
+ });
+
+ it('should correctly replace block matches', function() {
+ const content = replaceShortcuts(blocks, 'test.md', 'Hello\n$$\na = b\n$$\n');
+ expect(content).toBe('Hello\n{% math %}\na = b\n{% endmath %}\n');
+ });
+});
diff --git a/lib/templating/conrefsLoader.js b/packages/gitbook/src/templating/conrefsLoader.js
index b3cdb3f..3660d17 100644
--- a/lib/templating/conrefsLoader.js
+++ b/packages/gitbook/src/templating/conrefsLoader.js
@@ -1,10 +1,10 @@
-var path = require('path');
-var nunjucks = require('nunjucks');
+const path = require('path');
+const nunjucks = require('nunjucks');
-var fs = require('../utils/fs');
-var Git = require('../utils/git');
-var LocationUtils = require('../utils/location');
-var PathUtils = require('../utils/path');
+const fs = require('../utils/fs');
+const LocationUtils = require('../utils/location');
+const PathUtils = require('../utils/path');
+const Git = require('../utils/git');
/**
@@ -17,18 +17,18 @@ var PathUtils = require('../utils/path');
* @param {Function(filePath, source)} transformFn (optional)
* @param {Logger} logger (optional)
*/
-var ConrefsLoader = nunjucks.Loader.extend({
+const ConrefsLoader = nunjucks.Loader.extend({
async: true,
- init: function(rootFolder, transformFn, logger) {
+ init(rootFolder, transformFn, logger, git = new Git()) {
this.rootFolder = rootFolder;
this.transformFn = transformFn;
this.logger = logger;
- this.git = new Git();
+ this.git = git;
},
- getSource: function(sourceURL, callback) {
- var that = this;
+ getSource(sourceURL, callback) {
+ const that = this;
this.git.resolve(sourceURL)
.then(function(filepath) {
@@ -60,22 +60,22 @@ var ConrefsLoader = nunjucks.Loader.extend({
.nodeify(callback);
},
- resolve: function(from, to) {
+ resolve(from, to) {
// If origin is in the book, we enforce result file to be in the book
if (PathUtils.isInRoot(this.rootFolder, from)) {
// Path of current template in the rootFolder (not absolute to fs)
- var fromRelative = path.relative(this.rootFolder, from);
+ const fromRelative = path.relative(this.rootFolder, from);
// Resolve "to" to a filepath relative to rootFolder
- var href = LocationUtils.toAbsolute(to, path.dirname(fromRelative), '');
+ const href = LocationUtils.toAbsolute(to, path.dirname(fromRelative), '');
// Return absolute path
return PathUtils.resolveInRoot(this.rootFolder, href);
}
// If origin is in a git repository, we resolve file in the git repository
- var gitRoot = this.git.resolveRoot(from);
+ const gitRoot = this.git.resolveRoot(from);
if (gitRoot) {
return PathUtils.resolveInRoot(gitRoot, to);
}
@@ -85,7 +85,7 @@ var ConrefsLoader = nunjucks.Loader.extend({
},
// Handle all files as relative, so that nunjucks pass responsability to 'resolve'
- isRelative: function(filename) {
+ isRelative(filename) {
return LocationUtils.isRelative(filename);
}
});
diff --git a/packages/gitbook/src/templating/index.js b/packages/gitbook/src/templating/index.js
new file mode 100644
index 0000000..5189eac
--- /dev/null
+++ b/packages/gitbook/src/templating/index.js
@@ -0,0 +1,7 @@
+
+module.exports = {
+ render: require('./render'),
+ renderFile: require('./renderFile'),
+ replaceShortcuts: require('./replaceShortcuts'),
+ ConrefsLoader: require('./conrefsLoader')
+};
diff --git a/lib/templating/listShortcuts.js b/packages/gitbook/src/templating/listShortcuts.js
index 8d0a64a..099b709 100644
--- a/lib/templating/listShortcuts.js
+++ b/packages/gitbook/src/templating/listShortcuts.js
@@ -1,5 +1,5 @@
-var Immutable = require('immutable');
-var parsers = require('../parsers');
+const { List } = require('immutable');
+const parsers = require('../parsers');
/**
* Return a list of all shortcuts that can apply
@@ -7,13 +7,13 @@ var parsers = require('../parsers');
*
* @param {List<TemplateBlock>} engine
* @param {String} filePath
- * @return {List<TemplateShortcut>}
+ * @return {List<TemplateShortcut>} shortcuts
*/
function listShortcuts(blocks, filePath) {
- var parser = parsers.getForFile(filePath);
+ const parser = parsers.getForFile(filePath);
if (!parser) {
- return Immutable.List();
+ return List();
}
return blocks
diff --git a/lib/templating/render.js b/packages/gitbook/src/templating/render.js
index 1a8b0cd..945d6dc 100644
--- a/lib/templating/render.js
+++ b/packages/gitbook/src/templating/render.js
@@ -1,7 +1,6 @@
-var Promise = require('../utils/promise');
-var timing = require('../utils/timing');
-var TemplateOutput = require('../models/templateOutput');
-var replaceShortcuts = require('./replaceShortcuts');
+const Promise = require('../utils/promise');
+const timing = require('../utils/timing');
+const replaceShortcuts = require('./replaceShortcuts');
/**
* Render a template
@@ -10,16 +9,16 @@ var replaceShortcuts = require('./replaceShortcuts');
* @param {String} filePath: absolute path for the loader
* @param {String} content
* @param {Object} context (optional)
- * @return {Promise<TemplateOutput>}
+ * @return {Promise<String>}
*/
function renderTemplate(engine, filePath, content, context) {
context = context || {};
// Mutable objects to contains all blocks requiring post-processing
- var blocks = {};
+ const blocks = {};
// Create nunjucks environment
- var env = engine.toNunjucks(blocks);
+ const env = engine.toNunjucks(blocks);
// Replace shortcuts from plugin's blocks
content = replaceShortcuts(engine.getBlocks(), filePath, content);
@@ -35,9 +34,6 @@ function renderTemplate(engine, filePath, content, context) {
path: filePath
}
)
- .then(function(content) {
- return TemplateOutput.create(content, blocks);
- })
);
}
diff --git a/lib/templating/renderFile.js b/packages/gitbook/src/templating/renderFile.js
index 8672e8b..a2463f8 100644
--- a/lib/templating/renderFile.js
+++ b/packages/gitbook/src/templating/renderFile.js
@@ -1,6 +1,6 @@
-var Promise = require('../utils/promise');
-var error = require('../utils/error');
-var render = require('./render');
+const Promise = require('../utils/promise');
+const error = require('../utils/error');
+const render = require('./render');
/**
* Render a template
@@ -11,10 +11,10 @@ var render = require('./render');
* @return {Promise<TemplateOutput>}
*/
function renderTemplateFile(engine, filePath, context) {
- var loader = engine.getLoader();
+ const loader = engine.getLoader();
// Resolve the filePath
- var resolvedFilePath = loader.resolve(null, filePath);
+ const resolvedFilePath = loader.resolve(null, filePath);
return Promise()
.then(function() {
@@ -22,7 +22,7 @@ function renderTemplateFile(engine, filePath, context) {
return loader.getSource(resolvedFilePath);
}
- var deferred = Promise.defer();
+ const deferred = Promise.defer();
loader.getSource(resolvedFilePath, deferred.makeNodeResolver());
return deferred.promise;
})
diff --git a/lib/templating/replaceShortcuts.js b/packages/gitbook/src/templating/replaceShortcuts.js
index 1cfdbf0..25f598f 100644
--- a/lib/templating/replaceShortcuts.js
+++ b/packages/gitbook/src/templating/replaceShortcuts.js
@@ -1,5 +1,5 @@
-var escapeStringRegexp = require('escape-string-regexp');
-var listShortcuts = require('./listShortcuts');
+const escapeStringRegexp = require('escape-string-regexp');
+const listShortcuts = require('./listShortcuts');
/**
* Apply a shortcut of block to a template
@@ -8,13 +8,13 @@ var listShortcuts = require('./listShortcuts');
* @return {String}
*/
function applyShortcut(content, shortcut) {
- var start = shortcut.getStart();
- var end = shortcut.getEnd();
+ const start = shortcut.getStart();
+ const end = shortcut.getEnd();
- var tagStart = shortcut.getStartTag();
- var tagEnd = shortcut.getEndTag();
+ const tagStart = shortcut.getStartTag();
+ const tagEnd = shortcut.getEndTag();
- var regex = new RegExp(
+ const regex = new RegExp(
escapeStringRegexp(start) + '([\\s\\S]*?[^\\$])' + escapeStringRegexp(end),
'g'
);
@@ -32,7 +32,7 @@ function applyShortcut(content, shortcut) {
* @return {String}
*/
function replaceShortcuts(blocks, filePath, content) {
- var shortcuts = listShortcuts(blocks, filePath);
+ const shortcuts = listShortcuts(blocks, filePath);
return shortcuts.reduce(applyShortcut, content);
}
diff --git a/lib/utils/__tests__/git.js b/packages/gitbook/src/utils/__tests__/git.js
index abc1ea1..29be4a1 100644
--- a/lib/utils/__tests__/git.js
+++ b/packages/gitbook/src/utils/__tests__/git.js
@@ -1,13 +1,11 @@
-var path = require('path');
-var os = require('os');
+const path = require('path');
+const Git = require('../git');
-var Git = require('../git');
+describe('Git', () => {
-describe('Git', function() {
+ describe('URL parsing', () => {
- describe('URL parsing', function() {
-
- it('should correctly validate git urls', function() {
+ it('should correctly validate git urls', () => {
// HTTPS
expect(Git.isUrl('git+https://github.com/Hello/world.git')).toBeTruthy();
@@ -19,24 +17,24 @@ describe('Git', function() {
expect(Git.isUrl('README.md')).toBeFalsy();
});
- it('should parse HTTPS urls', function() {
- var parts = Git.parseUrl('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md');
+ it('should parse HTTPS urls', () => {
+ const parts = Git.parseUrl('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md');
expect(parts.host).toBe('https://gist.github.com/69ea4542e4c8967d2fa7.git');
expect(parts.ref).toBe(null);
expect(parts.filepath).toBe('test.md');
});
- it('should parse HTTPS urls with a reference', function() {
- var parts = Git.parseUrl('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md#1.0.0');
+ it('should parse HTTPS urls with a reference', () => {
+ const parts = Git.parseUrl('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md#1.0.0');
expect(parts.host).toBe('https://gist.github.com/69ea4542e4c8967d2fa7.git');
expect(parts.ref).toBe('1.0.0');
expect(parts.filepath).toBe('test.md');
});
- it('should parse SSH urls', function() {
- var parts = Git.parseUrl('git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1');
+ it('should parse SSH urls', () => {
+ const parts = Git.parseUrl('git+git@github.com:GitbookIO/gitbook.git/directory/README.md#e1594cde2c32e4ff48f6c4eff3d3d461743d74e1');
expect(parts.host).toBe('git@github.com:GitbookIO/gitbook.git');
expect(parts.ref).toBe('e1594cde2c32e4ff48f6c4eff3d3d461743d74e1');
@@ -44,9 +42,9 @@ describe('Git', function() {
});
});
- describe('Cloning and resolving', function() {
- it('should clone an HTTPS url', function() {
- var git = new Git(path.join(os.tmpdir(), 'test-git-'+Date.now()));
+ describe('Cloning and resolving', () => {
+ it('should clone an HTTPS url', () => {
+ const git = new Git();
return git.resolve('git+https://gist.github.com/69ea4542e4c8967d2fa7.git/test.md')
.then(function(filename) {
expect(path.extname(filename)).toBe('.md');
diff --git a/lib/utils/__tests__/location.js b/packages/gitbook/src/utils/__tests__/location.js
index 822338e..a565adb 100644
--- a/lib/utils/__tests__/location.js
+++ b/packages/gitbook/src/utils/__tests__/location.js
@@ -1,4 +1,4 @@
-var LocationUtils = require('../location');
+const LocationUtils = require('../location');
describe('LocationUtils', function() {
it('should correctly test external location', function() {
@@ -97,4 +97,3 @@ describe('LocationUtils', function() {
});
-
diff --git a/lib/utils/__tests__/path.js b/packages/gitbook/src/utils/__tests__/path.js
index 22bb016..1f8a1d3 100644
--- a/lib/utils/__tests__/path.js
+++ b/packages/gitbook/src/utils/__tests__/path.js
@@ -1,7 +1,7 @@
-var path = require('path');
+const path = require('path');
describe('Paths', function() {
- var PathUtils = require('..//path');
+ const PathUtils = require('..//path');
describe('setExtension', function() {
it('should correctly change extension of filename', function() {
diff --git a/lib/utils/command.js b/packages/gitbook/src/utils/command.js
index 90a556e..5533ca8 100644
--- a/lib/utils/command.js
+++ b/packages/gitbook/src/utils/command.js
@@ -1,19 +1,19 @@
-var is = require('is');
-var childProcess = require('child_process');
-var spawn = require('spawn-cmd').spawn;
-var Promise = require('./promise');
+const is = require('is');
+const childProcess = require('child_process');
+const spawn = require('spawn-cmd').spawn;
+const Promise = require('./promise');
/**
- Execute a command
-
- @param {String} command
- @param {Object} options
- @return {Promise}
-*/
+ * Execute a command
+ *
+ * @param {String} command
+ * @param {Object} options
+ * @return {Promise}
+ */
function exec(command, options) {
- var d = Promise.defer();
+ const d = Promise.defer();
- var child = childProcess.exec(command, options, function(err, stdout, stderr) {
+ const child = childProcess.exec(command, options, function(err, stdout, stderr) {
if (!err) {
return d.resolve();
}
@@ -22,11 +22,11 @@ function exec(command, options) {
d.reject(err);
});
- child.stdout.on('data', function (data) {
+ child.stdout.on('data', function(data) {
d.notify(data);
});
- child.stderr.on('data', function (data) {
+ child.stderr.on('data', function(data) {
d.notify(data);
});
@@ -34,26 +34,26 @@ function exec(command, options) {
}
/**
- Spawn an executable
-
- @param {String} command
- @param {Array} args
- @param {Object} options
- @return {Promise}
-*/
+ * Spawn an executable
+ *
+ * @param {String} command
+ * @param {Array} args
+ * @param {Object} options
+ * @return {Promise}
+ */
function spawnCmd(command, args, options) {
- var d = Promise.defer();
- var child = spawn(command, args, options);
+ const d = Promise.defer();
+ const child = spawn(command, args, options);
child.on('error', function(error) {
return d.reject(error);
});
- child.stdout.on('data', function (data) {
+ child.stdout.on('data', function(data) {
d.notify(data);
});
- child.stderr.on('data', function (data) {
+ child.stderr.on('data', function(data) {
d.notify(data);
});
@@ -61,7 +61,7 @@ function spawnCmd(command, args, options) {
if (code === 0) {
d.resolve();
} else {
- d.reject(new Error('Error with command "'+command+'"'));
+ d.reject(new Error('Error with command "' + command + '"'));
}
});
@@ -69,11 +69,11 @@ function spawnCmd(command, args, options) {
}
/**
- Transform an option object to a command line string
-
- @param {String|number} value
- @param {String}
-*/
+ * Transform an option object to a command line string
+ *
+ * @param {String|number} value
+ * @param {String}
+ */
function escapeShellArg(value) {
if (is.number(value)) {
return value;
@@ -86,16 +86,16 @@ function escapeShellArg(value) {
}
/**
- Transform a map of options into a command line arguments string
-
- @param {Object} options
- @return {String}
-*/
+ * Transform a map of options into a command line arguments string
+ *
+ * @param {Object} options
+ * @return {String}
+ */
function optionsToShellArgs(options) {
- var result = [];
+ const result = [];
- for (var key in options) {
- var value = options[key];
+ for (const key in options) {
+ const value = options[key];
if (value === null || value === undefined || value === false) {
continue;
@@ -112,7 +112,7 @@ function optionsToShellArgs(options) {
}
module.exports = {
- exec: exec,
+ exec,
spawn: spawnCmd,
- optionsToShellArgs: optionsToShellArgs
+ optionsToShellArgs
};
diff --git a/lib/utils/error.js b/packages/gitbook/src/utils/error.js
index 7686779..925b5ff 100644
--- a/lib/utils/error.js
+++ b/packages/gitbook/src/utils/error.js
@@ -1,7 +1,7 @@
-var is = require('is');
+const is = require('is');
-var TypedError = require('error/typed');
-var WrappedError = require('error/wrapped');
+const TypedError = require('error/typed');
+const WrappedError = require('error/wrapped');
// Enforce as an Error object, and cleanup message
@@ -13,31 +13,31 @@ function enforce(err) {
}
// Random error wrappers during parsing/generation
-var ParsingError = WrappedError({
+const ParsingError = WrappedError({
message: 'Parsing Error: {origMessage}',
type: 'parse'
});
-var OutputError = WrappedError({
+const OutputError = WrappedError({
message: 'Output Error: {origMessage}',
type: 'generate'
});
// A file does not exists
-var FileNotFoundError = TypedError({
+const FileNotFoundError = TypedError({
type: 'file.not-found',
message: 'No "{filename}" file (or is ignored)',
filename: null
});
// A file cannot be parsed
-var FileNotParsableError = TypedError({
+const FileNotParsableError = TypedError({
type: 'file.not-parsable',
message: '"{filename}" file cannot be parsed',
filename: null
});
// A file is outside the scope
-var FileOutOfScopeError = TypedError({
+const FileOutOfScopeError = TypedError({
type: 'file.out-of-scope',
message: '"{filename}" not in "{root}"',
filename: null,
@@ -46,7 +46,7 @@ var FileOutOfScopeError = TypedError({
});
// A file is outside the scope
-var RequireInstallError = TypedError({
+const RequireInstallError = TypedError({
type: 'install.required',
message: '"{cmd}" is not installed.\n{install}',
cmd: null,
@@ -55,45 +55,45 @@ var RequireInstallError = TypedError({
});
// Error for nunjucks templates
-var TemplateError = WrappedError({
+const TemplateError = WrappedError({
message: 'Error compiling template "{filename}": {origMessage}',
type: 'template',
filename: null
});
// Error for nunjucks templates
-var PluginError = WrappedError({
+const PluginError = WrappedError({
message: 'Error with plugin "{plugin}": {origMessage}',
type: 'plugin',
plugin: null
});
// Error with the book's configuration
-var ConfigurationError = WrappedError({
+const ConfigurationError = WrappedError({
message: 'Error with book\'s configuration: {origMessage}',
type: 'configuration'
});
// Error during ebook generation
-var EbookError = WrappedError({
+const EbookError = WrappedError({
message: 'Error during ebook generation: {origMessage}\n{stdout}',
type: 'ebook',
stdout: ''
});
module.exports = {
- enforce: enforce,
+ enforce,
- ParsingError: ParsingError,
- OutputError: OutputError,
- RequireInstallError: RequireInstallError,
+ ParsingError,
+ OutputError,
+ RequireInstallError,
- FileNotParsableError: FileNotParsableError,
- FileNotFoundError: FileNotFoundError,
- FileOutOfScopeError: FileOutOfScopeError,
+ FileNotParsableError,
+ FileNotFoundError,
+ FileOutOfScopeError,
- TemplateError: TemplateError,
- PluginError: PluginError,
- ConfigurationError: ConfigurationError,
- EbookError: EbookError
+ TemplateError,
+ PluginError,
+ ConfigurationError,
+ EbookError
};
diff --git a/lib/utils/fs.js b/packages/gitbook/src/utils/fs.js
index 35839a3..17b2ebb 100644
--- a/lib/utils/fs.js
+++ b/packages/gitbook/src/utils/fs.js
@@ -1,30 +1,30 @@
-var fs = require('graceful-fs');
-var mkdirp = require('mkdirp');
-var destroy = require('destroy');
-var rmdir = require('rmdir');
-var tmp = require('tmp');
-var request = require('request');
-var path = require('path');
-var cp = require('cp');
-var cpr = require('cpr');
-
-var Promise = require('./promise');
+const fs = require('graceful-fs');
+const mkdirp = require('mkdirp');
+const destroy = require('destroy');
+const rmdir = require('rmdir');
+const tmp = require('tmp');
+const request = require('request');
+const path = require('path');
+const cp = require('cp');
+const cpr = require('cpr');
+
+const Promise = require('./promise');
// Write a stream to a file
function writeStream(filename, st) {
- var d = Promise.defer();
+ const d = Promise.defer();
- var wstream = fs.createWriteStream(filename);
- var cleanup = function() {
+ const wstream = fs.createWriteStream(filename);
+ const cleanup = function() {
destroy(wstream);
wstream.removeAllListeners();
};
- wstream.on('finish', function () {
+ wstream.on('finish', function() {
cleanup();
d.resolve();
});
- wstream.on('error', function (err) {
+ wstream.on('error', function(err) {
cleanup();
d.reject(err);
});
@@ -41,7 +41,7 @@ function writeStream(filename, st) {
// Return a promise resolved with a boolean
function fileExists(filename) {
- var d = Promise.defer();
+ const d = Promise.defer();
fs.exists(filename, function(exists) {
d.resolve(exists);
@@ -69,13 +69,13 @@ function download(uri, dest) {
// Find a filename available in a folder
function uniqueFilename(base, filename) {
- var ext = path.extname(filename);
+ const ext = path.extname(filename);
filename = path.resolve(base, filename);
filename = path.join(path.dirname(filename), path.basename(filename, ext));
- var _filename = filename+ext;
+ let _filename = filename + ext;
- var i = 0;
+ let i = 0;
while (fs.existsSync(filename)) {
_filename = filename + '_' + i + ext;
i = i + 1;
@@ -86,14 +86,14 @@ function uniqueFilename(base, filename) {
// Create all required folder to create a file
function ensureFile(filename) {
- var base = path.dirname(filename);
+ const base = path.dirname(filename);
return Promise.nfcall(mkdirp, base);
}
// Remove a folder
function rmDir(base) {
return Promise.nfcall(rmdir, base, {
- fs: fs
+ fs
});
}
@@ -121,7 +121,7 @@ function assertFile(filePath, generator) {
@return {String}
*/
function pickFile(rootFolder, fileName) {
- var result = path.join(rootFolder, fileName);
+ const result = path.join(rootFolder, fileName);
if (fs.existsSync(result)) {
return result;
}
@@ -151,20 +151,20 @@ module.exports = {
mkdirp: Promise.nfbind(mkdirp),
readFile: Promise.nfbind(fs.readFile),
writeFile: Promise.nfbind(fs.writeFile),
- assertFile: assertFile,
- pickFile: pickFile,
+ assertFile,
+ pickFile,
stat: Promise.nfbind(fs.stat),
statSync: fs.statSync,
readdir: Promise.nfbind(fs.readdir),
- writeStream: writeStream,
+ writeStream,
readStream: fs.createReadStream,
copy: Promise.nfbind(cp),
copyDir: Promise.nfbind(cpr),
tmpFile: genTmpFile,
tmpDir: genTmpDir,
- download: download,
- uniqueFilename: uniqueFilename,
- ensureFile: ensureFile,
- ensureFolder: ensureFolder,
- rmDir: rmDir
+ download,
+ uniqueFilename,
+ ensureFile,
+ ensureFolder,
+ rmDir
};
diff --git a/lib/utils/genKey.js b/packages/gitbook/src/utils/genKey.js
index 0650011..e4982f4 100644
--- a/lib/utils/genKey.js
+++ b/packages/gitbook/src/utils/genKey.js
@@ -1,4 +1,4 @@
-var lastKey = 0;
+let lastKey = 0;
/*
Generate a random key
@@ -6,7 +6,7 @@ var lastKey = 0;
*/
function generateKey() {
lastKey += 1;
- var str = lastKey.toString(16);
+ const str = lastKey.toString(16);
return '00000'.slice(str.length) + str;
}
diff --git a/packages/gitbook/src/utils/git.js b/packages/gitbook/src/utils/git.js
new file mode 100644
index 0000000..2b2a3e3
--- /dev/null
+++ b/packages/gitbook/src/utils/git.js
@@ -0,0 +1,158 @@
+const is = require('is');
+const path = require('path');
+const crc = require('crc');
+const URI = require('urijs');
+
+const pathUtil = require('./path');
+const Promise = require('./promise');
+const command = require('./command');
+const fs = require('./fs');
+
+const GIT_PREFIX = 'git+';
+
+class Git {
+ constructor() {
+ this.tmpDir = null;
+ this.cloned = {};
+ }
+
+ // Return an unique ID for a combinaison host/ref
+ repoID(host, ref) {
+ return crc.crc32(host + '#' + (ref || '')).toString(16);
+ }
+
+ // Allocate a temporary folder for cloning repos in it
+ allocateDir() {
+ const that = this;
+
+ if (this.tmpDir) {
+ return Promise();
+ }
+
+ return fs.tmpDir()
+ .then(function(dir) {
+ that.tmpDir = dir;
+ });
+ }
+
+ /**
+ * Clone a git repository if non existant
+ * @param {String} host: url of the git repository
+ * @param {String} ref: branch/commit/tag to checkout
+ * @return {Promise<String>} repoPath
+ */
+ clone(host, ref) {
+ const that = this;
+
+ return this.allocateDir()
+
+ // Return or clone the git repo
+ .then(function() {
+ // Unique ID for repo/ref combinaison
+ const repoId = that.repoID(host, ref);
+
+ // Absolute path to the folder
+ const repoPath = path.join(that.tmpDir, repoId);
+
+ if (that.cloned[repoId]) return repoPath;
+
+ // Clone repo
+ return command.exec('git clone ' + host + ' ' + repoPath)
+
+ // Checkout reference if specified
+ .then(function() {
+ that.cloned[repoId] = true;
+
+ if (!ref) return;
+ return command.exec('git checkout ' + ref, { cwd: repoPath });
+ })
+ .thenResolve(repoPath);
+ });
+ }
+
+ /**
+ * Resole a git url, clone the repo and return the path to the right file.
+ * @param {String} giturl
+ * @return {Promise<String>} filePath
+ */
+ resolve(giturl) {
+ // Path to a file in a git repo?
+ if (!Git.isUrl(giturl)) {
+ if (this.resolveRoot(giturl)) return Promise(giturl);
+ return Promise(null);
+ }
+ if (is.string(giturl)) giturl = Git.parseUrl(giturl);
+ if (!giturl) return Promise(null);
+
+ // Clone or get from cache
+ return this.clone(giturl.host, giturl.ref)
+ .then(function(repo) {
+ return path.resolve(repo, giturl.filepath);
+ });
+ }
+
+ /**
+ * Return root of git repo from a filepath
+ * @param {String} filePath
+ * @return {String} repoPath
+ */
+ resolveRoot(filepath) {
+ // No git repo cloned, or file is not in a git repository
+ if (!this.tmpDir || !pathUtil.isInRoot(this.tmpDir, filepath)) return null;
+
+ // Extract first directory (is the repo id)
+ const relativeToGit = path.relative(this.tmpDir, filepath);
+ const repoId = relativeToGit.split(path.sep)[0];
+
+ if (!repoId) {
+ return;
+ }
+
+ // Return an absolute file
+ return path.resolve(this.tmpDir, repoId);
+ }
+
+ /**
+ * Check if an url is a git dependency url
+ * @param {String} giturl
+ * @return {Boolean} isUrl
+ */
+ static isUrl(giturl) {
+ return (giturl.indexOf(GIT_PREFIX) === 0);
+ }
+
+ /**
+ * Parse and extract infos
+ * @param {String} giturl
+ * @return {Object} { host, ref, filepath }
+ */
+ static parseUrl(giturl) {
+ if (!Git.isUrl(giturl)) {
+ return null;
+ }
+ giturl = giturl.slice(GIT_PREFIX.length);
+
+ const uri = new URI(giturl);
+ const ref = uri.fragment() || null;
+ uri.fragment(null);
+
+ // Extract file inside the repo (after the .git)
+ const fileParts = uri.path().split('.git');
+ let filepath = fileParts.length > 1 ? fileParts.slice(1).join('.git') : '';
+ if (filepath[0] == '/') {
+ filepath = filepath.slice(1);
+ }
+
+ // Recreate pathname without the real filename
+ uri.path(fileParts[0] + '.git');
+
+ return {
+ host: uri.toString(),
+ ref,
+ filepath
+ };
+ }
+
+}
+
+module.exports = Git;
diff --git a/lib/utils/images.js b/packages/gitbook/src/utils/images.js
index 6d4b927..808be63 100644
--- a/lib/utils/images.js
+++ b/packages/gitbook/src/utils/images.js
@@ -1,7 +1,7 @@
-var Promise = require('./promise');
-var command = require('./command');
-var fs = require('./fs');
-var error = require('./error');
+const Promise = require('./promise');
+const command = require('./command');
+const fs = require('./fs');
+const error = require('./error');
// Convert a svg file to a pmg
function convertSVGToPNG(source, dest, options) {
@@ -20,7 +20,7 @@ function convertSVGToPNG(source, dest, options) {
.then(function() {
if (fs.existsSync(dest)) return;
- throw new Error('Error converting '+source+' into '+dest);
+ throw new Error('Error converting ' + source + ' into ' + dest);
});
}
@@ -42,19 +42,19 @@ function convertSVGBufferToPNG(buf, dest) {
function convertInlinePNG(source, dest) {
if (!/^data\:image\/png/.test(source)) return Promise.reject(new Error('Source is not a PNG data-uri'));
- var base64data = source.split('data:image/png;base64,')[1];
- var buf = new Buffer(base64data, 'base64');
+ const base64data = source.split('data:image/png;base64,')[1];
+ const buf = new Buffer(base64data, 'base64');
return fs.writeFile(dest, buf)
.then(function() {
if (fs.existsSync(dest)) return;
- throw new Error('Error converting '+source+' into '+dest);
+ throw new Error('Error converting ' + source + ' into ' + dest);
});
}
module.exports = {
- convertSVGToPNG: convertSVGToPNG,
- convertSVGBufferToPNG: convertSVGBufferToPNG,
- convertInlinePNG: convertInlinePNG
-}; \ No newline at end of file
+ convertSVGToPNG,
+ convertSVGBufferToPNG,
+ convertInlinePNG
+};
diff --git a/lib/utils/location.js b/packages/gitbook/src/utils/location.js
index 00d8004..6dc41ba 100644
--- a/lib/utils/location.js
+++ b/packages/gitbook/src/utils/location.js
@@ -1,11 +1,11 @@
-var url = require('url');
-var path = require('path');
+const url = require('url');
+const path = require('path');
// Is the url an external url
function isExternal(href) {
try {
return Boolean(url.parse(href).protocol) && !isDataURI(href);
- } catch(err) {
+ } catch (err) {
return false;
}
}
@@ -14,7 +14,7 @@ function isExternal(href) {
function isDataURI(href) {
try {
return Boolean(url.parse(href).protocol) && (url.parse(href).protocol === 'data:');
- } catch(err) {
+ } catch (err) {
return false;
}
}
@@ -27,9 +27,9 @@ function isRelative(href) {
// Return true if the link is an achor
function isAnchor(href) {
try {
- var parsed = url.parse(href);
+ const parsed = url.parse(href);
return !!(!parsed.protocol && !parsed.path && parsed.hash);
- } catch(err) {
+ } catch (err) {
return false;
}
}
@@ -67,14 +67,14 @@ function toAbsolute(_href, dir, outdir) {
return _href;
}
- outdir = outdir == undefined? dir : outdir;
+ outdir = outdir == undefined ? dir : outdir;
_href = normalize(_href);
dir = normalize(dir);
outdir = normalize(outdir);
// Path "_href" inside the base folder
- var hrefInRoot = normalize(path.join(dir, _href));
+ let hrefInRoot = normalize(path.join(dir, _href));
if (_href[0] == '/') {
hrefInRoot = normalize(_href.slice(1));
}
@@ -97,8 +97,8 @@ function toAbsolute(_href, dir, outdir) {
* @return {String}
*/
function relative(dir, file) {
- var isDirectory = file.slice(-1) === '/';
- return normalize(path.relative(dir, file)) + (isDirectory? '/': '');
+ const isDirectory = file.slice(-1) === '/';
+ return normalize(path.relative(dir, file)) + (isDirectory ? '/' : '');
}
/**
@@ -126,14 +126,14 @@ function areIdenticalPaths(p1, p2) {
}
module.exports = {
- areIdenticalPaths: areIdenticalPaths,
- isDataURI: isDataURI,
- isExternal: isExternal,
- isRelative: isRelative,
- isAnchor: isAnchor,
- normalize: normalize,
- toAbsolute: toAbsolute,
- relative: relative,
- relativeForFile: relativeForFile,
- flatten: flatten
+ areIdenticalPaths,
+ isDataURI,
+ isExternal,
+ isRelative,
+ isAnchor,
+ normalize,
+ toAbsolute,
+ relative,
+ relativeForFile,
+ flatten
};
diff --git a/lib/utils/logger.js b/packages/gitbook/src/utils/logger.js
index 6fac92b..25f8517 100644
--- a/lib/utils/logger.js
+++ b/packages/gitbook/src/utils/logger.js
@@ -1,9 +1,9 @@
-var is = require('is');
-var util = require('util');
-var color = require('bash-color');
-var Immutable = require('immutable');
+const is = require('is');
+const util = require('util');
+const color = require('bash-color');
+const Immutable = require('immutable');
-var LEVELS = Immutable.Map({
+const LEVELS = Immutable.Map({
DEBUG: 0,
INFO: 1,
WARN: 2,
@@ -11,7 +11,7 @@ var LEVELS = Immutable.Map({
DISABLED: 10
});
-var COLORS = Immutable.Map({
+const COLORS = Immutable.Map({
DEBUG: color.purple,
INFO: color.cyan,
WARN: color.yellow,
@@ -22,7 +22,7 @@ function Logger(write, logLevel) {
if (!(this instanceof Logger)) return new Logger(write, logLevel);
this._write = write || function(msg) {
- if(process.stdout) {
+ if (process.stdout) {
process.stdout.write(msg);
}
};
@@ -82,8 +82,8 @@ Logger.prototype.write = function(msg) {
/**
Format a string using the first argument as a printf-like format.
*/
-Logger.prototype.format = function() {
- return util.format.apply(util, arguments);
+Logger.prototype.format = function(...args) {
+ return util.format(...args);
};
/**
@@ -92,7 +92,7 @@ Logger.prototype.format = function() {
@param {String}
*/
Logger.prototype.writeLn = function(msg) {
- return this.write((msg || '')+'\n');
+ return this.write((msg || '') + '\n');
};
/**
@@ -100,17 +100,16 @@ Logger.prototype.writeLn = function(msg) {
@param {Number} level
*/
-Logger.prototype.log = function(level) {
+Logger.prototype.log = function(level, ...args) {
if (level < this.logLevel) return;
- var levelKey = LEVELS.findKey(function(v) {
+ const levelKey = LEVELS.findKey(function(v) {
return v === level;
});
- var args = Array.prototype.slice.apply(arguments, [1]);
- var msg = this.format.apply(this, args);
+ let msg = this.format(...args);
if (this.lastChar == '\n') {
- msg = COLORS.get(levelKey)(levelKey.toLowerCase()+':')+' '+msg;
+ msg = COLORS.get(levelKey)(levelKey.toLowerCase() + ':') + ' ' + msg;
}
return this.write(msg);
@@ -119,21 +118,20 @@ Logger.prototype.log = function(level) {
/**
Log/Print a line if level is allowed
*/
-Logger.prototype.logLn = function() {
+Logger.prototype.logLn = function(...args) {
if (this.lastChar != '\n') this.write('\n');
- var args = Array.prototype.slice.apply(arguments);
args.push('\n');
- return this.log.apply(this, args);
+ return this.log(...args);
};
/**
Log a confirmation [OK]
*/
-Logger.prototype.ok = function(level) {
- var args = Array.prototype.slice.apply(arguments, [1]);
- var msg = this.format.apply(this, args);
- if (arguments.length > 1) {
+Logger.prototype.ok = function(level, ...args) {
+ const msg = this.format(...args);
+
+ if (args.length > 0) {
this.logLn(level, color.green('>> ') + msg.trim().replace(/\n/g, color.green('\n>> ')));
} else {
this.log(level, color.green('OK'), '\n');
@@ -155,10 +153,10 @@ Logger.prototype.fail = function(level) {
@return {Promise}
*/
Logger.prototype.promise = function(level, p) {
- var that = this;
+ const that = this;
- return p.
- then(function(st) {
+ return p
+ .then(function(st) {
that.ok(level);
return st;
}, function(err) {
diff --git a/lib/utils/mergeDefaults.js b/packages/gitbook/src/utils/mergeDefaults.js
index 47a374b..b2e8c3d 100644
--- a/lib/utils/mergeDefaults.js
+++ b/packages/gitbook/src/utils/mergeDefaults.js
@@ -1,4 +1,4 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
/**
* Merge
@@ -7,8 +7,8 @@ var Immutable = require('immutable');
* @return {Object}
*/
function mergeDefaults(obj, src) {
- var objValue = Immutable.fromJS(obj);
- var srcValue = Immutable.fromJS(src);
+ const objValue = Immutable.fromJS(obj);
+ const srcValue = Immutable.fromJS(src);
return srcValue.mergeDeep(objValue).toJS();
}
diff --git a/lib/utils/path.js b/packages/gitbook/src/utils/path.js
index 26b6005..01c2cbf 100644
--- a/lib/utils/path.js
+++ b/packages/gitbook/src/utils/path.js
@@ -1,5 +1,5 @@
-var path = require('path');
-var error = require('./error');
+const path = require('path');
+const error = require('./error');
// Normalize a filename
function normalizePath(filename) {
@@ -23,24 +23,21 @@ function isInRoot(root, filename) {
// Resolve paths in a specific folder
// Throw error if file is outside this folder
-function resolveInRoot(root) {
- var input, result;
- var args = Array.prototype.slice.call(arguments, 1);
-
- input = args
+function resolveInRoot(root, ...args) {
+ const input = args
.reduce(function(current, p) {
// Handle path relative to book root ("/README.md")
if (p[0] == '/' || p[0] == '\\') return p.slice(1);
- return current? path.join(current, p) : path.normalize(p);
+ return current ? path.join(current, p) : path.normalize(p);
}, '');
- result = path.resolve(root, input);
+ const result = path.resolve(root, input);
if (!isInRoot(root, result)) {
throw new error.FileOutOfScopeError({
filename: result,
- root: root
+ root
});
}
@@ -66,9 +63,9 @@ function isPureRelative(filename) {
}
module.exports = {
- isInRoot: isInRoot,
- resolveInRoot: resolveInRoot,
+ isInRoot,
+ resolveInRoot,
normalize: normalizePath,
- setExtension: setExtension,
- isPureRelative: isPureRelative
+ setExtension,
+ isPureRelative
};
diff --git a/lib/utils/promise.js b/packages/gitbook/src/utils/promise.js
index b5cca4b..8cbbd47 100644
--- a/lib/utils/promise.js
+++ b/packages/gitbook/src/utils/promise.js
@@ -1,5 +1,5 @@
-var Q = require('q');
-var Immutable = require('immutable');
+const Q = require('q');
+const Immutable = require('immutable');
// Debugging for long stack traces
if (process.env.DEBUG || process.env.CI) {
@@ -14,7 +14,7 @@ if (process.env.DEBUG || process.env.CI) {
* @return {Promise<Mixed>}
*/
function reduce(arr, iter, base) {
- arr = Immutable.Iterable.isIterable(arr)? arr : Immutable.List(arr);
+ arr = Immutable.Iterable.isIterable(arr) ? arr : Immutable.List(arr);
return arr.reduce(function(prev, elem, key) {
return prev
@@ -99,7 +99,7 @@ function mapAsList(arr, iter) {
*/
function map(arr, iter) {
if (Immutable.Map.isMap(arr)) {
- var type = 'Map';
+ let type = 'Map';
if (Immutable.OrderedMap.isOrderedMap(arr)) {
type = 'OrderedMap';
}
@@ -129,12 +129,10 @@ function map(arr, iter) {
* @return {Funciton}
*/
function wrap(func) {
- return function() {
- var args = Array.prototype.slice.call(arguments, 0);
-
+ return function(...args) {
return Q()
.then(function() {
- return func.apply(null, args);
+ return func(...args);
});
};
}
diff --git a/lib/utils/reducedObject.js b/packages/gitbook/src/utils/reducedObject.js
index 7bcfd5b..196a72c 100644
--- a/lib/utils/reducedObject.js
+++ b/packages/gitbook/src/utils/reducedObject.js
@@ -1,4 +1,4 @@
-var Immutable = require('immutable');
+const Immutable = require('immutable');
/**
* Reduce the difference between a map and its default version
@@ -7,15 +7,15 @@ var Immutable = require('immutable');
* @return {Map} The properties of currentVersion that differs from defaultVersion
*/
function reducedObject(defaultVersion, currentVersion) {
- if(defaultVersion === undefined) {
+ if (defaultVersion === undefined) {
return currentVersion;
}
return currentVersion.reduce(function(result, value, key) {
- var defaultValue = defaultVersion.get(key);
+ const defaultValue = defaultVersion.get(key);
if (Immutable.Map.isMap(value)) {
- var diffs = reducedObject(defaultValue, value);
+ const diffs = reducedObject(defaultValue, value);
if (diffs.size > 0) {
return result.set(key, diffs);
diff --git a/lib/utils/timing.js b/packages/gitbook/src/utils/timing.js
index e6b0323..38ffd00 100644
--- a/lib/utils/timing.js
+++ b/packages/gitbook/src/utils/timing.js
@@ -1,31 +1,31 @@
-var Immutable = require('immutable');
-var is = require('is');
+const Immutable = require('immutable');
+const is = require('is');
+const Promise = require('./promise');
-var timers = {};
-var startDate = Date.now();
+const timers = {};
+const startDate = Date.now();
/**
- Mesure an operation
-
- @parqm {String} type
- @param {Promise} p
- @return {Promise}
-*/
+ * Mesure an operation
+ *
+ * @param {String} type
+ * @param {Promise|Function} p
+ * @return {Promise|Mixed} result
+ */
function measure(type, p) {
timers[type] = timers[type] || {
- type: type,
+ type,
count: 0,
total: 0,
min: undefined,
max: 0
};
- var start = Date.now();
+ const start = Date.now();
- return p
- .fin(function() {
- var end = Date.now();
- var duration = (end - start);
+ const after = () => {
+ const end = Date.now();
+ const duration = (end - start);
timers[type].count ++;
timers[type].total += duration;
@@ -37,35 +37,43 @@ function measure(type, p) {
}
timers[type].max = Math.max(timers[type].max, duration);
- });
+ };
+
+ if (Promise.isPromise(p)) {
+ return p.fin(after);
+ }
+
+ const result = p();
+ after();
+
+ return result;
}
/**
- Return a milliseconds number as a second string
-
- @param {Number} ms
- @return {String}
-*/
+ * Return a milliseconds number as a second string
+ *
+ * @param {Number} ms
+ * @return {String}
+ */
function time(ms) {
if (ms < 1000) {
return (ms.toFixed(0)) + 'ms';
}
- return (ms/1000).toFixed(2) + 's';
+ return (ms / 1000).toFixed(2) + 's';
}
/**
- Dump all timers to a logger
-
- @param {Logger} logger
-*/
+ * Dump all timers to a logger
+ * @param {Logger} logger
+ */
function dump(logger) {
- var prefix = ' > ';
- var measured = 0;
- var totalDuration = Date.now() - startDate;
+ const prefix = ' > ';
+ let measured = 0;
+ const totalDuration = Date.now() - startDate;
// Enable debug logging
- var logLevel = logger.getLevel();
+ const logLevel = logger.getLevel();
logger.setLevel('debug');
Immutable.Map(timers)
@@ -75,11 +83,10 @@ function dump(logger) {
return timer.total;
})
.forEach(function(timer) {
- var percent = (timer.total * 100) / totalDuration;
-
+ const percent = (timer.total * 100) / totalDuration;
logger.debug.ln((percent.toFixed(1)) + '% of time spent in "' + timer.type + '" (' + timer.count + ' times) :');
- logger.debug.ln(prefix + 'Total: ' + time(timer.total)+ ' | Average: ' + time(timer.total / timer.count));
+ logger.debug.ln(prefix + 'Total: ' + time(timer.total) + ' | Average: ' + time(timer.total / timer.count));
logger.debug.ln(prefix + 'Min: ' + time(timer.min) + ' | Max: ' + time(timer.max));
logger.debug.ln('---------------------------');
});
@@ -92,6 +99,6 @@ function dump(logger) {
}
module.exports = {
- measure: measure,
- dump: dump
+ measure,
+ dump
};
diff --git a/testing/setup.js b/packages/gitbook/testing/setup.js
index 1105002..ee1485e 100644
--- a/testing/setup.js
+++ b/packages/gitbook/testing/setup.js
@@ -1,17 +1,18 @@
-var is = require('is');
-var path = require('path');
-var fs = require('fs');
-var expect = require('expect');
-var cheerio = require('cheerio');
+const is = require('is');
+const path = require('path');
+const fs = require('fs');
+const expect = require('expect');
+const cheerio = require('cheerio');
expect.extend({
+
/**
* Check that a file is created in a directory:
* expect('myFolder').toHaveFile('hello.md');
*/
- toHaveFile: function(fileName) {
- var filePath = path.join(this.actual, fileName);
- var exists = fs.existsSync(filePath);
+ toHaveFile(fileName) {
+ const filePath = path.join(this.actual, fileName);
+ const exists = fs.existsSync(filePath);
expect.assert(
exists,
@@ -21,9 +22,9 @@ expect.extend({
);
return this;
},
- toNotHaveFile: function(fileName) {
- var filePath = path.join(this.actual, fileName);
- var exists = fs.existsSync(filePath);
+ toNotHaveFile(fileName) {
+ const filePath = path.join(this.actual, fileName);
+ const exists = fs.existsSync(filePath);
expect.assert(
!exists,
@@ -37,7 +38,7 @@ expect.extend({
/**
* Check that a value is defined (not null nor undefined)
*/
- toBeDefined: function() {
+ toBeDefined() {
expect.assert(
!(is.undefined(this.actual) || is.null(this.actual)),
'expected to be defined'
@@ -48,7 +49,7 @@ expect.extend({
/**
* Check that a value is defined (not null nor undefined)
*/
- toNotBeDefined: function() {
+ toNotBeDefined() {
expect.assert(
(is.undefined(this.actual) || is.null(this.actual)),
'expected %s to be not defined',
@@ -61,9 +62,9 @@ expect.extend({
* Check that a dom element exists in HTML
* @param {String} selector
*/
- toHaveDOMElement: function(selector) {
- var $ = cheerio.load(this.actual);
- var $el = $(selector);
+ toHaveDOMElement(selector) {
+ const $ = cheerio.load(this.actual);
+ const $el = $(selector);
expect.assert($el.length > 0, 'expected HTML to contains %s', selector);
}
diff --git a/scripts/bump.js b/scripts/bump.js
new file mode 100755
index 0000000..2a4d592
--- /dev/null
+++ b/scripts/bump.js
@@ -0,0 +1,42 @@
+#! /usr/bin/env node
+
+const fs = require('fs');
+const path = require('path');
+
+const lernaConfig = require('../lerna.json');
+
+// List all the packages
+const PACKAGES_DIR = path.resolve(__dirname, '../packages');
+const packages = fs.readdirSync(PACKAGES_DIR);
+
+function updateDependencies(dependencies) {
+ if (!dependencies) {
+ return;
+ }
+
+ Object.keys(dependencies).map((key) => {
+ if (!packages.includes(key)) {
+ return;
+ }
+
+ dependencies[key] = lernaConfig.version;
+ });
+}
+
+packages.forEach((name) => {
+ // Avoid .DS_Store
+ if (name[0] === '.') {
+ return;
+ }
+
+ const pkgPath = path.resolve(PACKAGES_DIR, name, 'package.json');
+ const pkg = require(pkgPath);
+
+ pkg.version = lernaConfig.version;
+ updateDependencies(pkg.dependencies);
+ updateDependencies(pkg.devDependencies);
+
+ const json = JSON.stringify(pkg, null, 2);
+
+ fs.writeFileSync(pkgPath, `${json}\n`, 'utf-8');
+});