Compare commits

...

109 commits

Author SHA1 Message Date
dependabot[bot]
35c1cd6141 Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-06 14:09:33 +02:00
dependabot[bot]
6a4308cb11 Bump ajv from 6.11.0 to 6.12.6
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.11.0 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.11.0...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 17:48:34 +01:00
Jakub Jirutka
a865a53327
Add FUNDING.yml 2021-12-28 11:35:23 +01:00
dependabot[bot]
e5bcf84b3c Bump tmpl from 1.0.4 to 1.0.5
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 18:55:20 +01:00
dependabot[bot]
3d93c7fdf5 Bump tar from 5.0.5 to 5.0.10
Bumps [tar](https://github.com/npm/node-tar) from 5.0.5 to 5.0.10.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v5.0.5...v5.0.10)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 18:53:23 +01:00
dependabot[bot]
050e4515ca Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 18:49:49 +01:00
Jakub Jirutka
dd783a0daf CI: Replace Travis with GitHub Actions 2021-06-12 01:31:49 +02:00
Jakub Jirutka
6c26d381f0 Bump katex from 0.11.1 to 0.13.11 2021-06-12 00:40:40 +02:00
Jakub Jirutka
e1ba5039e4 Bump ansi_up from 4.0.4 to 5.0.1 2021-06-12 00:24:30 +02:00
Jakub Jirutka
1defdcd4ca Don't use deprecated highlight.js API
Deprecated as of 10.7.0. highlight(lang, code, ...args) has been deprecated.

    Deprecated as of 10.7.0. Please use highlight(code, options) instead.
    https://github.com/highlightjs/highlight.js/issues/2277
2021-06-12 00:24:30 +02:00
Jakub Jirutka
ccf16ab7c6 Remove @types/highlight.js (not needed anymore)
highlight.js already provides TypeScript types.
2021-06-12 00:13:22 +02:00
Jakub Jirutka
3eee5ebd60 Bump highlight.js from 10.1.1 to 10.7.3 2021-06-11 23:58:20 +02:00
Jakub Jirutka
8bd46774dd Bump marked from 1.0.0 to 2.0.7
Resolves #22
2021-06-11 23:47:33 +02:00
dependabot[bot]
b7216ab089 Bump acorn from 6.4.0 to 6.4.2
Bumps [acorn](https://github.com/acornjs/acorn) from 6.4.0 to 6.4.2.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.4.0...6.4.2)

---
updated-dependencies:
- dependency-name: acorn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 23:47:33 +02:00
dependabot[bot]
a182883320 Bump glob-parent from 5.1.0 to 5.1.2
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.0 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.0...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 23:47:33 +02:00
dependabot[bot]
fd04dc508d Bump ws from 7.2.1 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.2.1 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.2.1...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 23:47:33 +02:00
dependabot[bot]
953ca11aa8 Bump browserslist from 4.8.5 to 4.16.6
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.8.5 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.8.5...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 23:47:33 +02:00
dependabot[bot]
9d528485ab Bump hosted-git-info from 2.8.5 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.5 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.5...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 23:47:33 +02:00
dependabot[bot]
32c109f1d0 Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 23:47:33 +02:00
dependabot[bot]
122471a52f Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-11 23:47:33 +02:00
Jakub Jirutka
fbecff633a Release version 0.3.0 2020-10-28 18:51:56 +01:00
Jakub Jirutka
5ec93ba9e4 Patch yarn-version-bump to fix compatibility with yarn 1.22.4 2020-10-28 18:50:52 +01:00
dependabot[bot]
9f43fc595c Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-28 18:37:06 +01:00
Jakub Jirutka
b363818364 CI: Add Node.js 14 to build matrix 2020-10-28 18:32:06 +01:00
Jakub Jirutka
f5a283ba72 Adjust ESLint rule @typescript-eslint/ban-types - allow {} and object 2020-06-21 13:19:44 +02:00
Jakub Jirutka
ec76650a93 Disable ESLint rule import/order
When there are local typings for a third-party module, this rule classifies
import from the module as local instead of external.

See Also:
- 7e35810c7f
- bbbcd2398e
- 381caf644d
2020-06-21 13:11:09 +02:00
Jakub Jirutka
6ca8858d64 Disable ESLint rule @typescript-eslint/no-invalid-void-type 2020-06-21 12:29:08 +02:00
Jakub Jirutka
6944ff1b82 Move ipynb2html's devDependencies from parent to ipynb2html package 2020-06-21 11:08:04 +02:00
Jakub Jirutka
7fbd5b5d8b Bump all devDependencies to their latest minor version 2020-06-21 01:23:40 +02:00
Jakub Jirutka
a31ddbe203 Enable more ESLint rules 2020-06-21 01:23:40 +02:00
Jakub Jirutka
2f3d7ae4c6 Fix default HTML title in CLI 2020-06-21 01:23:40 +02:00
Jakub Jirutka
49d16ec079 Use no delimiter for members in interfaces 2020-06-21 01:23:40 +02:00
Jakub Jirutka
d29f006090 Bump eslint and its plugins, update rules and fix new violations 2020-06-21 01:23:39 +02:00
Jakub Jirutka
0b098983b5 Bump @types/node from ^10.13.0 to ^10.17.25 2020-06-21 00:46:08 +02:00
Jakub Jirutka
5f7c2dfb4b Bump typescript from ~3.7.5 to ~3.9.5 2020-06-21 00:46:08 +02:00
Jakub Jirutka
06ab09f278 Bump rollup from 1.x to 2.x and its plugins 2020-06-21 00:43:49 +02:00
Jakub Jirutka
110c8ca82a Use property signature syntax for methods
See 4c9293bd5e/packages/eslint-plugin/docs/rules/method-signature-style.md
2020-06-20 12:47:35 +02:00
Jakub Jirutka
d9bea2feb2 Bump highlight.js to 10.1.1 2020-06-17 00:39:07 +02:00
Jakub Jirutka
76c9a1e67f Bump node-html-parser from ^1.2.14 to ^1.2.19 2020-06-17 00:37:12 +02:00
Jakub Jirutka
9685b5d8b8 Bump marked to 1.0.0
The latest version is 1.1.0 but they changed rendering of img from
`<img ...>` to `<img ... />` which breaks our tests.
2020-06-17 00:37:07 +02:00
Jakub Jirutka
1d9ce38eb4 Bump @types/marked from ^0.7.2 to ^0.7.4 2020-06-15 18:07:56 +02:00
Jakub Jirutka
e4549711f1 Fix font-size of <h1> inside <section>
See https://stackoverflow.com/a/26291186/2217862.
2020-04-19 19:10:15 +02:00
Jakub Jirutka
3a312385f6 Wrap cells in <section> instead of <div> 2020-04-19 18:50:36 +02:00
Jakub Jirutka
19b41d9c22 Allow to include own style(s) into HTML produced by CLI
Resolves #6
2020-04-19 18:23:50 +02:00
Jakub Jirutka
6461e84fe7 Bump @babel/* from ^7.8.7 to ^7.9.0
This should fix build error on Node.js 13,
https://www.reddit.com/r/Nuxt/comments/g32jr5/babel_breaking_changes/.
2020-04-19 18:19:27 +02:00
Jakub Jirutka
cc2da7398e Release version 0.2.1 2020-04-10 17:40:21 +02:00
Jakub Jirutka
294e9c1f83 Bump highlight.js from ^9.16.2 to ^9.18.1 2020-04-10 17:39:37 +02:00
Jakub Jirutka
20008c9834 Replace deprecated dependency highlightjs with highlight.js 2020-04-10 17:27:52 +02:00
Jakub Jirutka
381caf644d Add types/ to compilerOptions.paths in tsconfigs
This basically reverts commit 7e35810c7f
which was a mistake - I forgot that there are no typings in types/,
that's why it worked...
2020-04-08 18:04:35 +02:00
Jakub Jirutka
a901a0ad68 Release version 0.2.0 2020-04-08 17:45:57 +02:00
Jakub Jirutka
fcdfb50698 Bump minimist from ^1.2.3 to ^1.2.5 2020-04-08 17:41:55 +02:00
Jakub Jirutka
ca6e88159e Strip accents from generated header ids 2020-04-08 17:26:27 +02:00
Jakub Jirutka
af91c38a02 Add tests for markdownRenderer 2020-04-08 17:23:19 +02:00
Jakub Jirutka
40011569ab Backport patch for node-html-parser to fix rendering of void tags 2020-04-08 17:23:19 +02:00
Jakub Jirutka
03f89b2196 Install node-html-parser as dev dependency 2020-04-08 17:23:18 +02:00
Jakub Jirutka
1f5e0fdab8 Strip math from generated alt, id, href, and title attributes 2020-04-08 17:23:18 +02:00
Jakub Jirutka
e1b817b13b Backport patch for @types/marked to fix type on nullable parameters
https://github.com/DefinitelyTyped/DefinitelyTyped/pull/43732
2020-04-08 16:37:03 +02:00
Jakub Jirutka
e7402a80ea Add anchors to Markdown headings 2020-04-08 12:25:22 +02:00
Jakub Jirutka
498112834c Use markdownRenderer even when katex is not available 2020-04-08 12:25:22 +02:00
Jakub Jirutka
e6b9ecf96b Change options for ESLint rule @typescript-eslint/unbound-method 2020-04-08 12:24:24 +02:00
Jakub Jirutka
ad741eaf48 Make toMatchElement compatible with other DOM implementations 2020-04-08 12:24:23 +02:00
Jakub Jirutka
6eebe39b4d Fix swapped arguments in toMatchElement 2020-04-08 01:26:44 +02:00
Jakub Jirutka
c13cb544cb Change structure of tsconfigs for tests to fix issues in VSCode 2020-04-07 22:49:45 +02:00
Jakub Jirutka
bbbcd2398e Enable ESLint rule import/order 2020-04-07 22:46:47 +02:00
Jakub Jirutka
7e35810c7f Remove types/ from compilerOptions.paths in tsconfigs
It's not needed here and it breaks ESLint rule import/order.
2020-04-07 22:45:10 +02:00
Jakub Jirutka
4c47e54acf Bump marked from ^0.8.0 to ^0.8.2
This version fixes insufficient sanitization of IDs.
2020-04-07 01:02:50 +02:00
dependabot[bot]
dbd6bc8821 Bump minimist from 1.2.0 to 1.2.3
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.3.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-06 19:44:26 +02:00
Jakub Jirutka
51e625dd55 Replace eslint-import-resolver-ts with eslint-import-resolver-typescript 2020-03-30 18:11:53 +02:00
Jakub Jirutka
ea852d43f7 Add meta tag generator to HTML template for CLI 2020-03-27 23:55:16 +01:00
Jakub Jirutka
47dc53b1a7 Bump babel to ^7.8.7 to fix build error on Node.js 13 2020-03-07 17:05:20 +01:00
Jakub Jirutka
a17cbefa51 CI: Add Node.js 13 and lts/* to build matrix 2020-03-07 16:10:16 +01:00
Jakub Jirutka
001219b173 Fix base header level in generated README.md 2020-03-07 13:07:10 +01:00
Jakub Jirutka
56608c2704 Use ESLint cache 2020-02-07 01:08:25 +01:00
Jakub Jirutka
ddef7d0da5 Fix package script "lint" in packages/* 2020-02-07 00:59:07 +01:00
Jakub Jirutka
1e0400a160 Switch ESLint rule no-non-null-assertion to warn 2020-02-06 12:08:52 +01:00
Jakub Jirutka
38048f84de Replace ESLint rule no-unused-vars-experimental with no-unused-vars
The experimental rule leverages the TypeScript compiler's unused
variable checks to report. Thus it correctly identifies even import
used for compilerOptions.jsxFactory etc.
2020-02-06 12:02:55 +01:00
Jakub Jirutka
ed62ee01e3 Release version 0.1.0 2020-01-23 18:03:26 +01:00
Jakub Jirutka
9c452d370c License: Bump year 2020-01-23 18:03:11 +01:00
Jakub Jirutka
0bf96274f9 Upgrade yarn.lock 2020-01-23 17:54:04 +01:00
Jakub Jirutka
28735b24c2 Replace rollup-plugin-node-resolve with @rollup/plugin-node-resolve 2020-01-23 17:50:56 +01:00
Jakub Jirutka
f7e794f0fe Replace rollup-plugin-commonjs with @rollup/plugin-commonjs 2020-01-23 17:50:20 +01:00
Jakub Jirutka
063847f0c7 Bump wsrun from 5.0.2 to 5.2.0 2020-01-23 17:34:28 +01:00
Jakub Jirutka
fd5d79319c Bump eslint and its plugins, fix new code-style violations 2020-01-23 17:31:46 +01:00
Jakub Jirutka
b7d33396fb Bump typescript from 3.6.4 to 3.7.5, ttypescript and ts-node 2020-01-23 17:05:14 +01:00
Jakub Jirutka
7e06fdc664 Bump babel from 7.6.3 to 7.8.3 and core-js from 3.2.1 to 3.6.4 2020-01-23 16:25:14 +01:00
Jakub Jirutka
bc4d2e1d9a Bump jest and ts-jest from 24.x to 25.x 2020-01-23 15:29:06 +01:00
Jakub Jirutka
d863e8cbaf Bump rollup and its plugins 2020-01-23 14:54:40 +01:00
Jakub Jirutka
35b26db42b Bump common-path-prefix from 2.0.0 to 3.0.0 2020-01-23 14:40:42 +01:00
Jakub Jirutka
1b6d3b2524 Bump marked from 0.7.0 to 0.8.0 2020-01-23 12:34:38 +01:00
Jakub Jirutka
9e8c3752b0 Fix readNotebookTitle to be compatible with marked 0.8.0 2020-01-23 12:07:58 +01:00
Jakub Jirutka
049703e14c Bump highlightjs, katex and source-map-support 2020-01-23 12:07:58 +01:00
Jakub Jirutka
1ccabd4d07 Add node_modules/ to .eslintignore
I thought that it's by default, but apparently it's not. Maybe only the
root node_modules/ is ignored by default? This change speeds up eslint
by factor 2.
2020-01-23 12:06:21 +01:00
dependabot[bot]
fb539853da Bump handlebars from 4.2.0 to 4.5.3
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.2.0 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.2.0...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-01-23 10:59:41 +01:00
Jakub Jirutka
712c0dbb0c Add prefix "render" to NbRenderer's methods 2020-01-22 16:57:27 +01:00
Jakub Jirutka
6c2fa15393 Refactor renderer object to class
It's more flexible in terms of extensibility and more familiar for devs.
Avoiding classes doesn't seem to be really worth it in this particular
case.
2020-01-22 16:19:07 +01:00
Jakub Jirutka
31173df623 Rename type Options in renderer to NbRendererOpts 2020-01-22 16:13:40 +01:00
Jakub Jirutka
93a559493f Rename dataRenderersOrder to dataTypesPriority 2019-11-08 19:31:27 +01:00
Jakub Jirutka
875e57639e Rename template.ts to page.ts 2019-11-04 10:43:04 +01:00
Jakub Jirutka
842f652ca5 Increase margin before headings in CLI styles 2019-11-04 01:37:41 +01:00
Jakub Jirutka
d658d9b5b7 Use box-shadow on .nb-notebook in CLI styles 2019-11-04 01:37:41 +01:00
Jakub Jirutka
8427698e21 Improve CLI styles to be more consistent across different browsers 2019-11-04 01:37:41 +01:00
Jakub Jirutka
638666c723 Add rules for tables into CLI styles 2019-11-04 01:37:41 +01:00
Jakub Jirutka
eeee0decc6 Extract inline style from template.ts to page.css 2019-11-04 01:37:41 +01:00
Jakub Jirutka
74a27b318f Improve styles responsivity 2019-11-04 01:37:41 +01:00
Jakub Jirutka
b7cbf2230b Remove CSS class nb-worksheet
I've added it only for backward compatibility with styles designed for
notebook.js, but actually even nbpreview (from the same author as
notebook.js) doesn't use it. Let's get rid of it.
2019-11-04 01:37:41 +01:00
Jakub Jirutka
37ce69b9d1 Add reference stylesheet and use it instead of nbpreview's 2019-11-04 01:37:40 +01:00
Jakub Jirutka
1a8142f641 Fix running CLI directly from installed npm package 2019-11-03 23:25:46 +01:00
Jakub Jirutka
68e382d05b Remove x permission from cli.ts 2019-11-03 23:25:46 +01:00
Jakub Jirutka
0da10ec702 Readme: Exclude section Installation from npm Readme 2019-10-29 01:43:43 +01:00
60 changed files with 4156 additions and 3446 deletions

View file

@ -1,7 +1,9 @@
node_modules/
/.*cache/ /.*cache/
/.tmp/ /.tmp/
/coverage/ /coverage/
/dist/ /dist/
/packages/*/bin/
/packages/*/coverage/ /packages/*/coverage/
/packages/*/dist/ /packages/*/dist/
/packages/*/lib/ /packages/*/lib/

View file

@ -1,4 +1,6 @@
const tsconfigs = [ const tsconfigs = [
'tsconfig.json',
'*/tsconfig.json',
'packages/*/tsconfig.json', 'packages/*/tsconfig.json',
'packages/*/test/tsconfig.json', 'packages/*/test/tsconfig.json',
] ]
@ -18,15 +20,14 @@ module.exports = {
}, },
settings: { settings: {
'import/resolver': { 'import/resolver': {
// Use eslint-import-resolver-ts to obey "paths" in tsconfig.json. // Use eslint-import-resolver-typescript to obey "paths" in tsconfig.json.
ts: { typescript: {
directory: tsconfigs, directory: tsconfigs,
}, },
}, },
}, },
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'standard-with-typescript', 'standard-with-typescript',
@ -35,48 +36,63 @@ module.exports = {
], ],
rules: { rules: {
'comma-dangle': ['error', 'always-multiline'], 'comma-dangle': ['error', 'always-multiline'],
// Disable in favour of TypeScript rule.
'func-call-spacing': 'off',
'linebreak-style': ['error', 'unix'], 'linebreak-style': ['error', 'unix'],
'lines-between-class-members': 'off', // Changed from error to warn and enabled ignoreEOLComments.
// Disable in favour of TypeScript rule.
'no-extra-semi': 'off',
'no-multi-spaces': ['warn', { 'no-multi-spaces': ['warn', {
ignoreEOLComments: true, ignoreEOLComments: true,
}], }],
// Changed from error to warn and adjusted options.
'no-multiple-empty-lines': ['warn', { 'no-multiple-empty-lines': ['warn', {
max: 2, max: 2,
maxEOF: 1, maxEOF: 1,
maxBOF: 1, maxBOF: 1,
}], }],
'no-template-curly-in-string': 'off', 'no-template-curly-in-string': 'off',
// Changed from 'after' to 'before'.
'operator-linebreak': ['error', 'before'], 'operator-linebreak': ['error', 'before'],
// Changed from error and all 'never' to warn and switches 'never'.
'padded-blocks': ['warn', { 'padded-blocks': ['warn', {
switches: 'never', switches: 'never',
}], }],
// Changed from 'as-needed' to 'consistent-as-needed'.
'quote-props': ['error', 'consistent-as-needed'], 'quote-props': ['error', 'consistent-as-needed'],
// Disable in favour of TypeScript rule.
'semi': 'off',
// Import // Import
// Some packages have wrong type declarations.
'import/default': 'off', 'import/default': 'off',
'import/newline-after-import': 'warn', 'import/newline-after-import': 'warn',
'import/no-absolute-path': 'error',
// This rule disallows using both wildcard and selective imports from the same module. // This rule disallows using both wildcard and selective imports from the same module.
'import/no-duplicates': 'off', 'import/no-duplicates': 'off',
// Some packages has it wrong in type declarations (e.g. katex, marked). // Some packages have it wrong in type declarations (e.g. katex, marked).
'import/no-named-as-default-member': 'off', 'import/no-named-as-default-member': 'off',
// TypeScript // TypeScript
// Changed options.
'@typescript-eslint/ban-types': ['error', {
// Allow to use {} and object - they are actually useful.
types: {
'{}': false,
'object': false,
},
extendDefaults: true,
}],
'@typescript-eslint/class-literal-property-style': ['error', 'fields'],
// Changed from error to off.
'@typescript-eslint/consistent-type-definitions': 'off', '@typescript-eslint/consistent-type-definitions': 'off',
// Changed from error to off.
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-member-accessibility': ['warn', { '@typescript-eslint/explicit-member-accessibility': ['error', {
accessibility: 'no-public', accessibility: 'no-public',
overrides: { overrides: {
parameterProperties: 'off', parameterProperties: 'off',
}, },
}], }],
'@typescript-eslint/func-call-spacing': ['error', 'never'], // Changed from warn to error and adjusted options.
'@typescript-eslint/explicit-module-boundary-types': ['error', {
allowArgumentsExplicitlyTypedAsAny: true,
}],
'@typescript-eslint/indent': ['error', 2, { '@typescript-eslint/indent': ['error', 2, {
SwitchCase: 1, SwitchCase: 1,
VariableDeclarator: 1, VariableDeclarator: 1,
@ -95,31 +111,58 @@ module.exports = {
flatTernaryExpressions: true, flatTernaryExpressions: true,
ignoreComments: false, ignoreComments: false,
}], }],
// Changed from error to warn.
'@typescript-eslint/lines-between-class-members': 'warn',
// Changed delimiter for type literals from none to comma.
// The reason is just aesthetic symmetry with object literals.
'@typescript-eslint/member-delimiter-style': ['error', { '@typescript-eslint/member-delimiter-style': ['error', {
multiline: { delimiter: 'comma', requireLast: true }, multiline: { delimiter: 'comma', requireLast: true },
singleline: { delimiter: 'comma', requireLast: false }, singleline: { delimiter: 'comma', requireLast: false },
overrides: {
interface: {
multiline: { delimiter: 'none' },
},
},
}], }],
'@typescript-eslint/member-ordering': 'warn',
// Changed from warn to off.
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
// Changed from error to warn.
'@typescript-eslint/no-extra-semi': 'warn',
// It disallows using void even in valid cases.
'@typescript-eslint/no-invalid-void-type': 'off',
// Changed from error to warn.
'@typescript-eslint/no-namespace': 'warn', '@typescript-eslint/no-namespace': 'warn',
// Changed from error to warn.
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-require-imports': 'error', '@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-unused-vars': ['error', { // Changed from error to warn.
argsIgnorePattern: '^_', '@typescript-eslint/no-unsafe-assignment': 'warn',
}], // Changed from error to warn.
'@typescript-eslint/no-unsafe-member-access': 'warn',
// Disabled in favour of the next rule.
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars-experimental': 'error',
// Changed options.
'@typescript-eslint/no-use-before-define': ['error', { '@typescript-eslint/no-use-before-define': ['error', {
classes: true,
functions: false, functions: false,
typedefs: false, typedefs: false,
variables: true,
}], }],
'@typescript-eslint/prefer-for-of': 'warn', '@typescript-eslint/prefer-for-of': 'warn',
// Changed from error to warn.
'@typescript-eslint/prefer-includes': 'warn', '@typescript-eslint/prefer-includes': 'warn',
// Changed from error to warn.
'@typescript-eslint/prefer-regexp-exec': 'warn', '@typescript-eslint/prefer-regexp-exec': 'warn',
'@typescript-eslint/prefer-string-starts-ends-with': 'warn', '@typescript-eslint/prefer-string-starts-ends-with': 'warn',
'@typescript-eslint/promise-function-async': ['error', { // It has too many false positives.
allowAny: true, '@typescript-eslint/restrict-template-expressions': 'off',
}], // Changed from error to off.
'@typescript-eslint/semi': ['error', 'never'],
'@typescript-eslint/strict-boolean-expressions': 'off', '@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/switch-exhaustiveness-check': 'error',
// Changed from error to warn and adjusted options.
'@typescript-eslint/unbound-method': ['warn', {
ignoreStatic: true,
}],
}, },
overrides: [ overrides: [
{ {
@ -127,7 +170,14 @@ module.exports = {
rules: { rules: {
// Allow to format arrays for parametrized tests as tables. // Allow to format arrays for parametrized tests as tables.
'array-bracket-spacing': 'off', 'array-bracket-spacing': 'off',
'comma-spacing': 'off', 'comma-dangle': ['error', {
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'always-multiline',
// Changed to not require comma in a multiline expect().
functions: 'only-multiline',
}],
'object-curly-spacing': 'off', 'object-curly-spacing': 'off',
'no-multi-spaces': 'off', 'no-multi-spaces': 'off',
'standard/array-bracket-even-spacing': 'off', 'standard/array-bracket-even-spacing': 'off',
@ -135,7 +185,10 @@ module.exports = {
'space-in-parens': 'off', 'space-in-parens': 'off',
// jest.mock() must be above imports. // jest.mock() must be above imports.
'import/first': 'off', 'import/first': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/comma-spacing': 'off',
// False positive on expect() functions.
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-return': 'warn',
}, },
}, },
], ],

3
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: jirutka

68
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,68 @@
name: CI
on:
- push
- pull_request
jobs:
test:
name: Test on Node.js ${{ matrix.node-version }}
runs-on: ubuntu-20.04
strategy:
matrix:
node-version: [10, 12, 13, 14]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # fetch all history to make `git describe` work
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn build
- run: yarn bundle
- run: yarn test
- run: yarn lint
publish:
name: Publish to npmjs and GitHub Releases
needs: [test]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # fetch all history to make `git describe` work
- run: sudo apt-get install asciidoctor pandoc
- uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- run: yarn install
- name: Generate source tarball
run: ./scripts/create-src-tarball dist/ipynb2html-${GITHUB_REF/refs\/tags\//}-src.tar.gz
- run: yarn build
- run: yarn bundle
- name: Publish packages to npmjs
run: yarn publish-all --non-interactive
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Upload tarballs to Releases
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
fail_on_unmatched_files: true
files: |
dist/*.tar.gz
packages/ipynb2html-cli/dist/*.tar.gz
packages/ipynb2html-cli/dist/*.zip

1
.gitignore vendored
View file

@ -7,6 +7,7 @@
/.*cache/ /.*cache/
/.tmp/ /.tmp/
node_modules/ node_modules/
.eslintcache
.tsbuildinfo .tsbuildinfo
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
*.log *.log

View file

@ -1,54 +0,0 @@
dist: bionic
language: node_js
node_js:
- '10'
- '12'
- node
addons:
apt:
packages:
- asciidoctor
- pandoc
env:
global:
- secure: "DrPA1eYBRjS+1/w/sRq/r5wQyOewuh1PGPzFtQR62TJeAyO6AvKQ6wfesxMA898b+0D3SCNxrCVK12XB3auySEOZocQuN7N51hsteA/QtPoBBbnoHy8Dap2YbiJ5fbCVnM/Wl/Z2rZmQWFBM3rmqXggCyEhKEw3kkz8WMm/7UCGVxmoHUelpMnDEII0RiJdPCGT19IA90KpJDsqbSzTVY+TsqjSNuN91LQ23ApwSHKklvbvKWxcgrtAzJDXLeS9CS3QqSHucurOM2Kpv0umOkBzds4da+NtWKYZC3XxThmMB5wT7b60EZPIc/iFftQFy2qiDAFxeGN+j9kwsNX68aXl4MuCGlzdvGj0KkeXYYhl1Jusc30uTzXYMlz2b3u+AcsMLLxFs2HvUU94SpAfe9VrarSnQK+6CZz0eCtF/NCCi6J9GqlBTsqzZDmdVaJpFDG1FidC4Ka9FcteKcWXqffowQ5KjIhqaearSmRESMqepV7T8tDUCb217PE0C+L0NGfg6RaY4DtGsJAawDeh/09aXrP6NakAKjWUfaJqjhkMexB8JTb+yanjVXsgj4VUfvTgvWjMi+yU3DIfopL+mawvgckRRL9DTEIf5ICjSruyEH8FWEz+kMzas41zboabR12YMLuorHfgZu31DiDsJkSD292t/lwWW6oybRl0iJBk=" # NPM_TOKEN=ceed......2bf2
cache: yarn
install:
- yarn install
- test -z "$TRAVIS_TAG" || ./scripts/create-src-tarball dist/ipynb2html-$TRAVIS_TAG-src.tar.gz
script:
- yarn run build
- yarn run bundle
- yarn run test
- yarn run lint
before_deploy:
- echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >> "$HOME"/.npmrc
deploy:
- provider: script
script: yarn publish-all --non-interactive
skip_cleanup: true
on:
repo: jirutka/ipynb2html
tags: true
node_js: '10'
- provider: releases
api_key:
secure: j5Ks+lUUE2NkUKKC4A+FRKVia3oRyanMKp25jZwF2jLOQxZp0/BggNOduw7zMLzPizmMbz7CkYV/pLyXH+rwCjOdQPnSGLorzfp6ztzG49SqPUK5i5PdSctYT7+C2HdYR25xtw6f06HXzLWTIeYb0JG+5pUCkI4R+z1JEqZhx3PMOkAx9Ec9UoYNKcbHe1E78y8zH0pDccVJ1ejrbmizEqZALpYNR+qCWhTfLVbwYlpJJGIKCIXVYI9tU/3y/6JOO+gCX2m8jqNVBPrwWWD//gGExn2lR6BK0WiXTE71FbM7GyVjoeh93Zg1WifF+7DBtaTI9qs+928afLn28AcWqB6P/HgoRgt/4oLvJU2hkdkIoNqQONqYZeglW1A5qYp6T3nURLpcn1cg2yBD6vNi8cet4ntgOsPu9Soa8pzsm9xcITMJNUlFOEWyYPyvgTXHUjZwbZvrZHRVYeHrDOuq9EEAz+11u1FYG49uSBZf3+H6CmL7n2qeoYhlMhT94bgIkF4ByFmP+OTUcMcWvDdA7uD8JqPMaxQI0hhjrszxPT7C5YNXsJ1Q0vU2zZSMF74fygsXlnr6VeqwxSzgF/6mAAuSwtKSg/pFPPLdevIeehw4oLCz+ZdxVk5kpfymKvqvueDdMMoV4Fse4Yhv81Z76tAPM35TtqJa77DJwqOVOCE=
file_glob: true
file:
- dist/*.tar.gz
- packages/ipynb2html-cli/dist/*.tar.gz
- packages/ipynb2html-cli/dist/*.zip
skip_cleanup: true
on:
repo: jirutka/ipynb2html
tags: true
node_js: '10'

View file

@ -1,6 +1,6 @@
The MIT License The MIT License
Copyright 2019 Jakub Jirutka <jakub@jirutka.cz>. Copyright 2019-2020 Jakub Jirutka <jakub@jirutka.cz>.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -2,15 +2,15 @@
:npm-name: ipynb2html :npm-name: ipynb2html
:gh-name: jirutka/{npm-name} :gh-name: jirutka/{npm-name}
:gh-branch: master :gh-branch: master
:version: 0.1.0-beta.8 :version: 0.3.0
:ansiup-version: 4.0.4 :ansiup-version: 5.0.1
:hljs-version: 9.15.10 :hljs-version: 10.7.3
:katex-version: 0.11.1 :katex-version: 0.13.11
:marked-version: 0.7.0 :marked-version: 2.0.7
:vs-marketplace-uri: https://marketplace.visualstudio.com/items?itemName= :vs-marketplace-uri: https://marketplace.visualstudio.com/items?itemName=
ifdef::env-github[] ifdef::env-github[]
image:https://travis-ci.com/{gh-name}.svg?branch={gh-branch}[Build Status, link="https://travis-ci.com/{gh-name}"] image:https://github.com/{gh-name}/workflows/CI/badge.svg[CI Status, link=https://github.com/{gh-name}/actions?query=workflow%3A%22CI%22]
endif::env-github[] endif::env-github[]
{npm-name} is a converter (renderer) of the https://nbformat.readthedocs.io/en/stable/[Jupyter Notebook Format] 4.0+ to static HTML. {npm-name} is a converter (renderer) of the https://nbformat.readthedocs.io/en/stable/[Jupyter Notebook Format] 4.0+ to static HTML.
@ -47,6 +47,8 @@ This package builds on the {npm-name}-core and provides a complete, ready-to-go
* https://github.com/IonicaBizau/anser[anser] as ANSI sequences renderer, * https://github.com/IonicaBizau/anser[anser] as ANSI sequences renderer,
* https://github.com/highlightjs/highlight.js[highlight.js] as syntax highlighter. * https://github.com/highlightjs/highlight.js[highlight.js] as syntax highlighter.
It also provides a reference stylesheet which you can find in `dist/notebook.min.css` (or non-minified link:packages/{npm-name}/styles/notebook.css[`styles/notebook.css`]).
=== {npm-name}-cli === {npm-name}-cli
@ -58,6 +60,8 @@ endif::env-github[]
This package provides a CLI interface for {npm-name}. This package provides a CLI interface for {npm-name}.
ifndef::npm-readme[]
== Installation == Installation
All the <<Packages, packages>> can be installed using `npm` or `yarn` from https://www.npmjs.com/[npmjs.com]. All the <<Packages, packages>> can be installed using `npm` or `yarn` from https://www.npmjs.com/[npmjs.com].
@ -73,6 +77,8 @@ It requires only Node.js (version 10 or newer) to be installed on the system.
The archive also contains source maps (useful for debugging). The archive also contains source maps (useful for debugging).
endif::[]
== Usage == Usage
@ -126,6 +132,7 @@ You can link it from https://www.jsdelivr.com/[jsDelivr CDN], for example:
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/{npm-name}@{version}/dist/notebook.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@{katex-version}/dist/katex.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@{katex-version}/dist/katex.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@{hljs-version}/build/styles/default.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@{hljs-version}/build/styles/default.min.css" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/{npm-name}@{version}/dist/{npm-name}-full.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/{npm-name}@{version}/dist/{npm-name}-full.min.js" crossorigin="anonymous"></script>
@ -146,6 +153,7 @@ document.body.appendChild(element)
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/{npm-name}@{version}/dist/notebook.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@{katex-version}/dist/katex.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@{katex-version}/dist/katex.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@{hljs-version}/build/styles/default.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@{hljs-version}/build/styles/default.min.css" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/{npm-name}@{version}/dist/{npm-name}-full.min.js" crossorigin="anonymous" <script defer src="https://cdn.jsdelivr.net/npm/{npm-name}@{version}/dist/{npm-name}-full.min.js" crossorigin="anonymous"

View file

@ -4,17 +4,14 @@
<meta charset="utf-8" lang="cs"> <meta charset="utf-8" lang="cs">
<meta name="viewport" content="initial-scale=1"> <meta name="viewport" content="initial-scale=1">
<title>Example using ipynb2html-full.js</title> <title>Example using ipynb2html-full.js</title>
<link rel="stylesheet" href="../packages/ipynb2html/dist/notebook.min.css">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/default.min.css" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/styles/default.min.css"
crossorigin="anonymous">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/jsvine/nbpreview@c54381b/css/vendor/notebook.css"
crossorigin="anonymous"> crossorigin="anonymous">
<script defer src="../packages/ipynb2html/dist/ipynb2html-full.js" onload="ipynb2html.autoRender();"></script> <script defer src="../packages/ipynb2html/dist/ipynb2html-full.js" onload="ipynb2html.autoRender();"></script>
<style> <style>

View file

@ -4,22 +4,19 @@
<meta charset="utf-8" lang="cs"> <meta charset="utf-8" lang="cs">
<meta name="viewport" content="initial-scale=1"> <meta name="viewport" content="initial-scale=1">
<title>Example using ipynb2html.js</title> <title>Example using ipynb2html.js</title>
<link rel="stylesheet" href="../packages/ipynb2html/dist/notebook.min.css">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/default.min.css" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/styles/default.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<link <script src="https://cdn.jsdelivr.net/npm/marked@2.0.7/marked.min.js" crossorigin="anonymous"></script>
rel="stylesheet" <script src="https://cdn.jsdelivr.net/npm/ansi_up@5.0.1/ansi_up.js" crossorigin="anonymous"></script>
href="https://cdn.jsdelivr.net/gh/jsvine/nbpreview@c54381b/css/vendor/notebook.css" <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/highlight.min.js" crossorigin="anonymous"></script>
crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@0.7.0/marked.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/ansi_up@4.0.4/ansi_up.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/highlight.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js" crossorigin="anonymous"></script>
<script defer src="../packages/ipynb2html/dist/ipynb2html.js" onload="ipynb2html.autoRender();"></script> <script defer src="../packages/ipynb2html/dist/ipynb2html.js" onload="ipynb2html.autoRender();"></script>
<style> <style>
html, body { html, body {

View file

@ -63,7 +63,7 @@ module.exports = {
globals: { globals: {
'ts-jest': { 'ts-jest': {
compiler: 'ttypescript', compiler: 'ttypescript',
tsConfig: '<rootDir>/tsconfig.json', tsConfig: '<rootDir>/test/tsconfig.json',
}, },
}, },

View file

@ -1,12 +1,12 @@
{ {
"name": "ipynb2html-parent", "name": "ipynb2html-parent",
"version": "0.1.0-beta.8", "version": "0.3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "ttsc --build", "build": "ttsc --build",
"bundle": "wsrun --exclude-missing bundle", "bundle": "wsrun --exclude-missing bundle",
"clean": "rimraf coverage/ dist/ lib/ *.log && wsrun clean", "clean": "rimraf coverage/ dist/ lib/ .eslintcache *.log && wsrun clean",
"lint": "eslint --ext .ts,.tsx,.js .", "lint": "eslint --cache --ext .ts,.tsx,.js .",
"postinstall": "patch-package && run-s build", "postinstall": "patch-package && run-s build",
"publish-all": "wsrun --serial publish", "publish-all": "wsrun --serial publish",
"test": "jest --detectOpenHandles --coverage --verbose", "test": "jest --detectOpenHandles --coverage --verbose",
@ -17,55 +17,52 @@
"node": ">=10.13.0" "node": ">=10.13.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.6.3", "@babel/core": "^7.10.3",
"@babel/preset-env": "^7.6.3", "@babel/preset-env": "^7.10.3",
"@rollup/plugin-babel": "^5.0.3",
"@rollup/plugin-commonjs": "^13.0.0",
"@rollup/plugin-node-resolve": "^8.0.1",
"@types/dedent": "^0.7.0", "@types/dedent": "^0.7.0",
"@types/highlightjs": "^9.12.0", "@types/jest": "^24.9.1",
"@types/jest": "^24.0.18", "@types/node": "^10.17.25",
"@types/katex": "^0.10.2", "@typescript-eslint/eslint-plugin": "^3.3.0",
"@types/marked": "^0.6.5", "@typescript-eslint/parser": "^3.3.0",
"@types/node": "^10.13.0",
"@typescript-eslint/eslint-plugin": "^2.4.0",
"@typescript-eslint/parser": "^2.4.0",
"ansi_up": "^4.0.4",
"arrify": "^2.0.1", "arrify": "^2.0.1",
"common-path-prefix": "^2.0.0", "common-path-prefix": "^3.0.0",
"core-js": "^3.2.1", "core-js": "^3.6.5",
"csso-cli": "^3.0.0",
"dedent": "^0.7.0", "dedent": "^0.7.0",
"eslint": "^6.5.1", "eslint": "^7.3.0",
"eslint-config-standard-with-typescript": "^10.0.0", "eslint-config-standard-with-typescript": "^18.0.2",
"eslint-import-resolver-ts": "^0.4.0", "eslint-import-resolver-typescript": "^2.0.0",
"eslint-plugin-import": "^2.18.0", "eslint-plugin-import": "^2.21.2",
"eslint-plugin-node": "^10.0.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"jest": "^24.9.0", "jest": "^25.5.4",
"jest-chain": "^1.1.2", "jest-chain": "^1.1.5",
"node-html-parser": "^1.2.19",
"nodom": "^2.3.0", "nodom": "^2.3.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"patch-package": "^6.2.0", "patch-package": "^6.2.2",
"postinstall-postinstall": "^2.0.0", "postinstall-postinstall": "^2.1.0",
"rimraf": "^3.0.0", "rimraf": "^3.0.2",
"rollup": "^1.23.0", "rollup": "^2.17.1",
"rollup-plugin-add-git-msg": "^1.0.3", "rollup-plugin-add-git-msg": "^1.1.0",
"rollup-plugin-babel": "^4.3.3", "rollup-plugin-executable": "^1.6.0",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-externals": "^2.2.0",
"rollup-plugin-executable": "^1.5.1",
"rollup-plugin-node-externals": "^2.0.1",
"rollup-plugin-node-license": "^0.2.0", "rollup-plugin-node-license": "^0.2.0",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-terser": "^6.1.0",
"rollup-plugin-strip-shebang": "^1.2.7", "rollup-plugin-typescript2": "^0.27.1",
"rollup-plugin-terser": "^5.1.2", "tar": "^5.0.10",
"rollup-plugin-typescript2": "^0.24.3", "ts-jest": "^25.5.1",
"tar": "^5.0.5", "ts-node": "^8.10.2",
"ts-jest": "^24.1.0",
"ts-node": "^8.4.1",
"ts-transformer-export-default-name": "^0.1.0", "ts-transformer-export-default-name": "^0.1.0",
"ts-transformer-inline-file": "^0.1.1", "ts-transformer-inline-file": "^0.1.1",
"ttypescript": "^1.5.7", "ttypescript": "^1.5.10",
"typescript": "~3.6.4", "typescript": "~3.9.5",
"wsrun": "^5.0.2", "wsrun": "^5.2.1",
"yarn-version-bump": "^0.0.3", "yarn-version-bump": "^0.0.3",
"yazl": "^2.5.1" "yazl": "^2.5.1"
}, },

View file

@ -0,0 +1,2 @@
#!/usr/bin/env node
require('../lib/index').default(process.argv.slice(2))

View file

@ -1,6 +1,6 @@
{ {
"name": "ipynb2html-cli", "name": "ipynb2html-cli",
"version": "0.1.0-beta.8", "version": "0.3.0",
"description": "CLI tool for converting Jupyter Notebooks to static HTML", "description": "CLI tool for converting Jupyter Notebooks to static HTML",
"author": "Jakub Jirutka <jakub@jirutka.cz>", "author": "Jakub Jirutka <jakub@jirutka.cz>",
"license": "MIT", "license": "MIT",
@ -19,19 +19,20 @@
"notebook" "notebook"
], ],
"bin": { "bin": {
"ipynb2html": "lib/index.js" "ipynb2html": "bin/ipynb2html"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
"files": [ "files": [
"bin",
"lib", "lib",
"src" "src"
], ],
"scripts": { "scripts": {
"build": "ttsc --build", "build": "ttsc --build",
"bundle": "rollup -c && ./scripts/pack-bundle", "bundle": "rollup -c && ./scripts/pack-bundle",
"clean": "rimraf coverage/ dist/ lib/ .tsbuildinfo", "clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
"lint": "eslint --ext .ts,.tsx,.js .", "lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
"prepublishOnly": "run-s readme2md", "prepublishOnly": "run-s readme2md",
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md", "readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
"watch-ts": "ttsc --build --watch" "watch-ts": "ttsc --build --watch"
@ -40,14 +41,14 @@
"node": ">=10.13.0" "node": ">=10.13.0"
}, },
"dependencies": { "dependencies": {
"ipynb2html": "0.1.0-beta.8", "ipynb2html": "0.3.0",
"minimist": "^1.2.0", "minimist": "^1.2.6",
"minimist-options": "^4.0.2", "minimist-options": "^4.0.2",
"nodom": "^2.3.0", "nodom": "^2.3.0",
"source-map-support": "^0.5.13" "source-map-support": "^0.5.16"
}, },
"devDependencies": { "devDependencies": {
"@types/minimist": "^1.2.0", "@types/minimist": "^1.2.0",
"@types/source-map-support": "^0.5.0" "@types/source-map-support": "^0.5.1"
} }
} }

View file

@ -1,12 +1,11 @@
import addGitMsg from 'rollup-plugin-add-git-msg' import addGitMsg from 'rollup-plugin-add-git-msg'
import commonjs from 'rollup-plugin-commonjs' import commonjs from '@rollup/plugin-commonjs'
import executable from 'rollup-plugin-executable' import executable from 'rollup-plugin-executable'
import externals from 'rollup-plugin-node-externals' import externals from 'rollup-plugin-node-externals'
import license from 'rollup-plugin-node-license' import license from 'rollup-plugin-node-license'
import resolve from 'rollup-plugin-node-resolve' import resolve from '@rollup/plugin-node-resolve'
import { terser } from 'rollup-plugin-terser' import { terser } from 'rollup-plugin-terser'
import typescript from 'rollup-plugin-typescript2' import typescript from 'rollup-plugin-typescript2'
import stripShebang from 'rollup-plugin-strip-shebang'
import ttypescript from 'ttypescript' import ttypescript from 'ttypescript'
import pkg from './package.json' import pkg from './package.json'
@ -15,10 +14,6 @@ import pkg from './package.json'
export default { export default {
input: 'src/index.ts', input: 'src/index.ts',
plugins: [ plugins: [
// Rollup doesn't like shebangs, let's strip it.
stripShebang({
include: 'src/index.ts',
}),
// Transpile TypeScript sources to JS. // Transpile TypeScript sources to JS.
typescript({ typescript({
typescript: ttypescript, typescript: ttypescript,
@ -31,9 +26,6 @@ export default {
incremental: true, incremental: true,
}, },
}, },
// This is needed for node-license plugin. :(
// https://github.com/ezolenko/rollup-plugin-typescript2#plugins-using-asyncawait
objectHashIgnoreUnknownHack: true,
clean: true, clean: true,
}), }),
// Make node builtins external. // Make node builtins external.

48
packages/ipynb2html-cli/src/cli.ts Executable file → Normal file
View file

@ -3,14 +3,17 @@ import minimist from 'minimist'
import minimistOptions from 'minimist-options' import minimistOptions from 'minimist-options'
import { Document } from 'nodom' import { Document } from 'nodom'
import { exit } from 'process' import { exit } from 'process'
import { $INLINE_JSON } from 'ts-transformer-inline-file' import { $INLINE_FILE, $INLINE_JSON } from 'ts-transformer-inline-file'
import * as ipynb2html from 'ipynb2html' import * as ipynb2html from 'ipynb2html'
import template from './template' import renderPage from './page'
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { version, bugs: bugsUrl } = $INLINE_JSON('../package.json') const { version, bugs: bugsUrl } = $INLINE_JSON('../package.json')
const notebookCss = $INLINE_FILE('../../ipynb2html/styles/notebook.css')
const pageCss = $INLINE_FILE('./page.css')
const progName = 'ipynb2html' const progName = 'ipynb2html'
const helpMsg = `\ const helpMsg = `\
@ -24,13 +27,20 @@ Arguments:
provided, the output will be written to STDOUT. provided, the output will be written to STDOUT.
Options: Options:
-d --debug Print debug messages. -d --debug Print debug messages.
-h --help Show this message and exit.
-V --version Print version and exit. -s --style <file,...> Comma separated stylesheet(s) to embed into the output
HTML. The stylesheet may be a path to a CSS file,
"@base" for the base ipynb2html style, or "@default"
for the default full page style. Default is @default.
-h --help Show this message and exit.
-V --version Print version and exit.
Exit Codes: Exit Codes:
1 Generic error code. 1 Generic error code.
2 Missing required arguments or invalid option. 2 Missing required arguments or invalid option.
Please report bugs at <${bugsUrl}>. Please report bugs at <${bugsUrl}>.
` `
@ -39,9 +49,14 @@ function logErr (msg: string): void {
console.error(`${progName}: ${msg}`) console.error(`${progName}: ${msg}`)
} }
function arrify <T> (obj: T | T[]): T[] {
return Array.isArray(obj) ? obj : [obj]
}
function parseCliArgs (argv: string[]) { function parseCliArgs (argv: string[]) {
const opts = minimist(argv, minimistOptions({ const opts = minimist(argv, minimistOptions({
debug: { alias: 'd', type: 'boolean' }, debug: { alias: 'd', type: 'boolean' },
style: { alias: 's', type: 'string', default: '@default' },
version: { alias: 'V', type: 'boolean' }, version: { alias: 'V', type: 'boolean' },
help: { alias: 'h', type: 'boolean' }, help: { alias: 'h', type: 'boolean' },
arguments: 'string', arguments: 'string',
@ -73,24 +88,35 @@ function parseCliArgs (argv: string[]) {
const [input, output] = opts._ const [input, output] = opts._
return { return {
styles: arrify(opts.style).join(',').split(/,\s*/),
debug: opts.debug as boolean, debug: opts.debug as boolean,
input: input === '-' ? 0 : input, // 0 = stdin input: input === '-' ? 0 : input, // 0 = stdin
output, output,
} }
} }
function loadStyle (name: string): string {
switch (name) {
case '@base': return notebookCss
case '@default': return pageCss + notebookCss
default: return fs.readFileSync(name, 'utf8')
}
}
export default (argv: string[]): void => { export default (argv: string[]): void => {
const opts = parseCliArgs(argv) const opts = parseCliArgs(argv)
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const notebook = JSON.parse(fs.readFileSync(opts.input, 'utf-8')) const notebook = JSON.parse(fs.readFileSync(opts.input, 'utf-8'))
const style = opts.styles.map(loadStyle).join('\n')
const title = ipynb2html.readNotebookTitle(notebook) || 'Notebook' const title = ipynb2html.readNotebookTitle(notebook) || 'Notebook'
const render = ipynb2html.createRenderer(new Document()) const renderNotebook = ipynb2html.createRenderer(new Document())
const contents = render(notebook).outerHTML const contents = renderNotebook(notebook).outerHTML
const html = template(contents, title) const html = renderPage({ contents, title, style })
if (opts.output) { if (opts.output) {
fs.writeFileSync(opts.output, html) fs.writeFileSync(opts.output, html)
@ -101,7 +127,7 @@ export default (argv: string[]): void => {
if (opts.debug) { if (opts.debug) {
console.debug(err) console.debug(err)
} else { } else {
logErr(err.message) logErr((err as Error).message)
} }
return exit(1) return exit(1)
} }

View file

@ -1,10 +1,9 @@
#!/usr/bin/env node
import sourceMapSupport from 'source-map-support' import sourceMapSupport from 'source-map-support'
import cli from './cli' import cli from './cli'
// Allow to disable sourcemap when running from pkg bundle. // Allow to disable sourcemap when running from pkg bundle.
if (!/^(0|disable|false|no|off)$/i.test(process.env.NODE_SOURCEMAP || '')) { if (!/^(0|disable|false|no|off)$/i.test(process.env.NODE_SOURCEMAP ?? '')) {
sourceMapSupport.install({ environment: 'node' }) sourceMapSupport.install({ environment: 'node' })
} }

View file

@ -0,0 +1,95 @@
html, body {
margin: 0;
padding: 0;
background-color: #dedede;
color: #2a2a2a;
font-family: sans-serif;
}
code,
pre {
font-size: 0.85rem;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 1.6em;
position: relative;
}
h1 {
font-size: 2em;
}
h1 .anchor,
h2 .anchor,
h3 .anchor,
h4 .anchor,
h5 .anchor,
h6 .anchor {
display: block;
width: 1em;
left: -1em;
height: 100%;
position: absolute;
}
h1:hover .anchor,
h2:hover .anchor,
h3:hover .anchor,
h4:hover .anchor,
h5:hover .anchor,
h6:hover .anchor {
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50'%3E%3Cg fill='none' stroke='%23000' stroke-width='3'%3E%3Cpath d='M26.1 22a5.8 5.8 0 00-8.1 0l-9.6 9.5a5.8 5.8 0 008.2 8.2l5.8-5.9'/%3E%3Cpath d='M23.6 28a5.8 5.8 0 008.2 0l9.5-9.5a5.8 5.8 0 00-8.1-8.2l-5.8 5.9'/%3E%3C/g%3E%3C/svg%3E") no-repeat 100% center;
}
table {
border-collapse: collapse;
border: 1px solid #cfcfcf;
}
th {
font-weight: 600;
}
td,
th {
padding: 0.2em 0.4em;
border: 1px solid #cfcfcf;
}
thead tr,
tbody tr:nth-child(even) {
background-color: #f7f7f7;
}
thead tr:hover,
tbody tr:hover {
background-color: #cfcfcf;
}
.nb-notebook {
max-width: 45rem;
margin: 1rem auto;
padding: 3rem 6rem;
background-color: white;
box-shadow: 4px 4px 8px #cfcfcf, -4px -4px 8px #cfcfcf;
}
.nb-output table {
font-size: 0.9em;
}
@media (max-width: 900px) {
.nb-notebook {
margin: 0;
padding-left: 5rem;
padding-right: 3rem;
max-width: none;
box-shadow: none;
}
}
@media (max-width: 768px) {
.nb-notebook {
padding: 1rem 5% 2rem 5%;
}
}

View file

@ -0,0 +1,34 @@
import { version } from 'ipynb2html'
export type Options = {
contents: string,
title: string,
style: string,
}
export default ({ contents, title, style }: Options): string => `\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta name="generator" content="ipynb2html ${version}">
<title>${title}</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/styles/default.min.css"
integrity="sha384-s4RLYRjGGbVqKOyMGGwfxUTMOO6D7r2eom7hWZQ6BjK2Df4ZyfzLXEkonSm0KLIQ"
crossorigin="anonymous">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css"
integrity="sha384-Um5gpz1odJg5Z4HAmzPtgZKdTBHZdw8S29IecapCSB31ligYPhHQZMIlWLYQGVoc"
crossorigin="anonymous">
<style>
${style.replace(/\n\n/g, '\n').replace(/\n$/, '').replace(/^/gm, ' ')}
</style>
</head>
<body>
${contents}
</body>
</html>
`

View file

@ -1,38 +0,0 @@
export default (contents: string, title: string): string => `\
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<title>${title}</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/default.min.css"
integrity="sha384-ut3ELVx81ErZQaaMTknSmGb0CEGAKoBFTamRcY1ddG4guN0aoga4C+B6B7Kv1Ll1"
crossorigin="anonymous">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.11.0/dist/katex.min.css"
integrity="sha384-BdGj8xC2eZkQaxoQ8nSLefg4AV4/AwB3Fj+8SUSo7pnKP6Eoy18liIKTPn9oBYNG"
crossorigin="anonymous">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/jsvine/nbpreview@c54381b/css/vendor/notebook.css"
integrity="sha384-8nb3et+RBCn64rFFK06Oie9mX/D8c59Kg5hha4g9uUehuzCn9DFXGPZ94WYEBb9Z"
crossorigin="anonymous">
<style>
html, body {
margin: 0;
padding: 0;
}
.nb-notebook {
width: 99%;
max-width: 750px;
margin: 0 auto;
padding: 3em 6em 1em 6em;
background-color: white;
}
</style>
</head>
<body>
${contents}
</body>
</html>
`

View file

@ -5,11 +5,6 @@
"outDir": "./lib", "outDir": "./lib",
"tsBuildInfoFile": "./.tsbuildinfo", "tsBuildInfoFile": "./.tsbuildinfo",
"baseUrl": ".", "baseUrl": ".",
"paths": {
"*": ["../../types/*"],
"@/*": ["./src/*"],
"~/*": ["../../*"],
},
}, },
"include": [ "include": [
"./src", "./src",

View file

@ -1,6 +1,6 @@
{ {
"name": "ipynb2html-core", "name": "ipynb2html-core",
"version": "0.1.0-beta.8", "version": "0.3.0",
"description": "Convert Jupyter Notebook to static HTML", "description": "Convert Jupyter Notebook to static HTML",
"author": "Jakub Jirutka <jakub@jirutka.cz>", "author": "Jakub Jirutka <jakub@jirutka.cz>",
"license": "MIT", "license": "MIT",
@ -25,8 +25,8 @@
], ],
"scripts": { "scripts": {
"build": "ttsc --build", "build": "ttsc --build",
"clean": "rimraf coverage/ lib/ .tsbuildinfo", "clean": "rimraf coverage/ lib/ .eslintcache .tsbuildinfo",
"lint": "eslint --ext .ts,.tsx,.js .", "lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
"prepublishOnly": "run-s readme2md", "prepublishOnly": "run-s readme2md",
"test": "jest --detectOpenHandles --coverage --verbose", "test": "jest --detectOpenHandles --coverage --verbose",
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md", "readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",

View file

@ -4,8 +4,8 @@ type Attributes = { [k: string]: string }
// for this module's function. // for this module's function.
export type MinimalElement = { export type MinimalElement = {
innerHTML: string, innerHTML: string,
setAttribute (name: string, value: string): void, setAttribute: (name: string, value: string) => void,
appendChild (child: any): any, appendChild: (child: any) => unknown,
} }
export type ElementCreator<TElement = HTMLElement> = export type ElementCreator<TElement = HTMLElement> =

View file

@ -16,7 +16,7 @@ export default <TElement> (opts: Options<TElement>): DataRenderer<TElement> => {
const { elementCreator: el, mathRenderer: renderMath } = opts const { elementCreator: el, mathRenderer: renderMath } = opts
return (data: string): TElement => { return (data: string): TElement => {
const math = (extractMathRx.exec(data) || [])[1] const math = (extractMathRx.exec(data) ?? [])[1]
return math return math
? el('div', ['latex-output'], renderMath(math)) ? el('div', ['latex-output'], renderMath(math))
: el('div', ['html-output'], data) : el('div', ['html-output'], data)

View file

@ -3,6 +3,6 @@ import * as mathExtractor from './mathExtractor'
export { default as createElementCreator, ElementCreator, MinimalElement } from './elementCreator' export { default as createElementCreator, ElementCreator, MinimalElement } from './elementCreator'
export { default as createHtmlRenderer } from './htmlRenderer' export { default as createHtmlRenderer } from './htmlRenderer'
export * from './nbformat' export * from './nbformat'
export { default as createNbRenderer, DataRenderer, NbRenderer, Options as NbRendererOpts } from './renderer' export { default as NbRenderer, DataRenderer, NbRendererOpts } from './renderer'
export { mathExtractor } export { mathExtractor }
export { default as version } from './version' export { default as version } from './version'

View file

@ -1,36 +1,53 @@
const htmlEntities = { const htmlEntities: Record<string, string> = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
} }
/** type Callable = (...args: any[]) => any
* Creates a "callable object" with the given properties. In fact, it creates
* a function that calls `obj[funcName]` and copies all the enumerable
* properties of the *template* to the created function.
*
* @param {string} funcName Name of the function property of the *template*.
* @param {Object} template The source object from which to copy enumerable properties.
* @return A function with all enumerable properties of the *template*.
*/
export function callableObject <T, K extends keyof T> (
funcName: K,
template: T,
): T[K] extends Function ? T & T[K] : never {
const fn = function (...args: any[]) { type CallableConstructor = new <T> () => T extends { __call__: Callable }
return (template[funcName] as any)(...args) ? T['__call__']
: 'subclass does not implement method __call__'
/* eslint-disable @typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access,
@typescript-eslint/ban-types */
export const CallableInstance: CallableConstructor = function Callable (
this: object,
): Callable {
const func = this.constructor.prototype.__call__ as Callable
const cls = function (...args: any[]) {
return func.apply(cls, args) as unknown
} }
return Object.assign(fn, template) as any Object.setPrototypeOf(cls, this.constructor.prototype)
}
Object.defineProperties(cls, {
name: {
value: this.constructor.name,
configurable: true,
},
length: {
value: func.length,
configurable: true,
},
})
return cls
} as any
CallableInstance.prototype = Object.create(Function.prototype)
/* eslint-enable @typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access,
@typescript-eslint/ban-types */
/** /**
* Escapes characters with special meaning in HTML with the corresponding * Escapes characters with special meaning in HTML with the corresponding
* HTML entities. * HTML entities.
*/ */
export function escapeHTML (str: string): string { export function escapeHTML (str: string): string {
return str.replace(/[&<>]/g, c => (htmlEntities as any)[c]) return str.replace(/[&<>]/g, c => htmlEntities[c])
} }
/** /**

View file

@ -191,7 +191,7 @@ export function extractMath (text: string): [string, MathExpression[]] {
} }
} }
if (lastIdx) { if (lastIdx) {
processMath(unescape, math, blocks, startIdx || 0, lastIdx) processMath(unescape, math, blocks, startIdx ?? 0, lastIdx)
startIdx = endDelim = lastIdx = null startIdx = endDelim = lastIdx = null
} }
return [unescape(blocks.join('')), math.map(parseDelimitedMath)] return [unescape(blocks.join('')), math.map(parseDelimitedMath)]
@ -204,6 +204,6 @@ export function extractMath (text: string): [string, MathExpression[]] {
*/ */
export function restoreMath (text: string, math: string[]): string { export function restoreMath (text: string, math: string[]): string {
return text return text
.replace(/@@([1-9][0-9]*)@@/g, (_, n) => math[Number(n) - 1]) .replace(/@@([1-9][0-9]*)@@/g, (_, n) => math[Number(n) - 1] ?? '')
.replace(/@@0(\d+)@@/g, (_, n) => `@@${n}@@`) .replace(/@@0(\d+)@@/g, (_, n) => `@@${n}@@`)
} }

View file

@ -1,78 +1,79 @@
// These types are based on https://github.com/jupyter/nbformat/blob/b6b5a18e5a40d37f1cc0f71f65108288bdec9bb7/nbformat/v4/nbformat.v4.schema.json. // These types are based on https://github.com/jupyter/nbformat/blob/b6b5a18e5a40d37f1cc0f71f65108288bdec9bb7/nbformat/v4/nbformat.v4.schema.json.
// This file was originally generated using json-schema-to-typescript 7.0.0 and // This file was originally generated using json-schema-to-typescript 7.0.0 and
// then manually polished. // then manually polished.
/* eslint-disable @typescript-eslint/member-ordering */
/** Jupyter Notebook v4.3. */ /** Jupyter Notebook v4.3. */
export interface Notebook { export interface Notebook {
/** Notebook root-level metadata. */ /** Notebook root-level metadata. */
metadata: NotebookMetadata, metadata: NotebookMetadata
/** Notebook format (minor number). Incremented for backward compatible changes to the notebook format. */ /** Notebook format (minor number). Incremented for backward compatible changes to the notebook format. */
nbformat_minor: number, nbformat_minor: number
/** Notebook format (major number). Incremented between backwards incompatible changes to the notebook format. */ /** Notebook format (major number). Incremented between backwards incompatible changes to the notebook format. */
nbformat: 4, nbformat: 4
/** Array of cells of the current notebook. */ /** Array of cells of the current notebook. */
cells: Cell[], cells: Cell[]
} }
/** Notebook root-level metadata. */ /** Notebook root-level metadata. */
export interface NotebookMetadata { export interface NotebookMetadata {
/** Kernel information. */ /** Kernel information. */
kernelspec?: KernelSpec, kernelspec?: KernelSpec
/** Kernel information. */ /** Kernel information. */
language_info?: LanguageInfo, language_info?: LanguageInfo
/** Original notebook format (major number) before converting the notebook between versions. This should never be written to a file. */ /** Original notebook format (major number) before converting the notebook between versions. This should never be written to a file. */
orig_nbformat?: number, orig_nbformat?: number
/** The title of the notebook document */ /** The title of the notebook document */
title?: string, title?: string
/** The author(s) of the notebook document */ /** The author(s) of the notebook document */
authors?: any[], authors?: any[]
/** Extra properties. */ /** Extra properties. */
[key: string]: any, [key: string]: any
} }
/** Kernel information. */ /** Kernel information. */
export interface KernelSpec { export interface KernelSpec {
/** Name of the kernel specification. */ /** Name of the kernel specification. */
name: string, name: string
/** Name to display in UI. */ /** Name to display in UI. */
display_name: string, display_name: string
/** Extra properties. */ /** Extra properties. */
[key: string]: any, [key: string]: any
} }
/** Kernel information. */ /** Kernel information. */
export interface LanguageInfo { export interface LanguageInfo {
/** The programming language which this kernel runs. */ /** The programming language which this kernel runs. */
name: string, name: string
/** The codemirror mode to use for code in this language. */ /** The codemirror mode to use for code in this language. */
codemirror_mode?: string | { [k: string]: any }, codemirror_mode?: string | { [k: string]: any }
/** The file extension for files in this language. */ /** The file extension for files in this language. */
file_extension?: string, file_extension?: string
/** The mimetype corresponding to files in this language. */ /** The mimetype corresponding to files in this language. */
mimetype?: string, mimetype?: string
/** The pygments lexer to use for code in this language. */ /** The pygments lexer to use for code in this language. */
pygments_lexer?: string, pygments_lexer?: string
/** Extra properties. */ /** Extra properties. */
[key: string]: any, [key: string]: any
} }
@ -89,46 +90,46 @@ export enum CellType {
interface BaseCell { interface BaseCell {
/** String identifying the type of cell. */ /** String identifying the type of cell. */
cell_type: CellType, cell_type: CellType
/** Cell-level metadata. */ /** Cell-level metadata. */
metadata: CellMetadata, metadata: CellMetadata
/** Contents of the cell, represented as an array of lines. */ /** Contents of the cell, represented as an array of lines. */
source: MultilineString, source: MultilineString
} }
/** Notebook raw nbconvert cell. */ /** Notebook raw nbconvert cell. */
export interface RawCell extends BaseCell { export interface RawCell extends BaseCell {
/** String identifying the type of cell. */ /** String identifying the type of cell. */
cell_type: CellType.Raw, cell_type: CellType.Raw
/** Cell-level metadata. */ /** Cell-level metadata. */
metadata: CellMetadata & { metadata: CellMetadata & {
/** Raw cell metadata format for nbconvert. */ /** Raw cell metadata format for nbconvert. */
format?: string, format?: string,
}, }
/** Media attachments (e.g. inline images), stored as mimebundle keyed by filename. */ /** Media attachments (e.g. inline images), stored as mimebundle keyed by filename. */
attachments?: MediaAttachments, attachments?: MediaAttachments
} }
/** Notebook markdown cell. */ /** Notebook markdown cell. */
export interface MarkdownCell extends BaseCell { export interface MarkdownCell extends BaseCell {
/** String identifying the type of cell. */ /** String identifying the type of cell. */
cell_type: CellType.Markdown, cell_type: CellType.Markdown
/** Media attachments (e.g. inline images), stored as mimebundle keyed by filename. */ /** Media attachments (e.g. inline images), stored as mimebundle keyed by filename. */
attachments?: MediaAttachments, attachments?: MediaAttachments
} }
/** Notebook code cell. */ /** Notebook code cell. */
export interface CodeCell extends BaseCell { export interface CodeCell extends BaseCell {
/** String identifying the type of cell. */ /** String identifying the type of cell. */
cell_type: CellType.Code, cell_type: CellType.Code
/** Cell-level metadata. */ /** Cell-level metadata. */
metadata: CellMetadata & { metadata: CellMetadata & {
@ -138,38 +139,38 @@ export interface CodeCell extends BaseCell {
/** Whether the cell's output is scrolled, unscrolled, or autoscrolled. */ /** Whether the cell's output is scrolled, unscrolled, or autoscrolled. */
scrolled?: true | false | 'auto', scrolled?: true | false | 'auto',
}, }
/** Execution, display, or stream outputs. */ /** Execution, display, or stream outputs. */
outputs: Output[], outputs: Output[]
/** The code cell's prompt number. Will be null if the cell has not been run. */ /** The code cell's prompt number. Will be null if the cell has not been run. */
execution_count: number | null, execution_count: number | null
} }
export interface CellMetadata { export interface CellMetadata {
/** Official Jupyter Metadata for Raw Cells */ /** Official Jupyter Metadata for Raw Cells */
jupyter?: { [k: string]: any }, jupyter?: { [k: string]: any }
/** /**
* The cell's name. If present, must be a non-empty string. Cell names are expected to be unique * The cell's name. If present, must be a non-empty string. Cell names are expected to be unique
* across all the cells in a given notebook. This criterion cannot be checked by the json schema * across all the cells in a given notebook. This criterion cannot be checked by the json schema
* and must be established by an additional check. * and must be established by an additional check.
*/ */
name?: string, name?: string
/** The cell's tags. Tags must be unique, and must not contain commas. */ /** The cell's tags. Tags must be unique, and must not contain commas. */
tags?: string[], tags?: string[]
/** Extra properties. */ /** Extra properties. */
[key: string]: any, [key: string]: any
} }
export interface MediaAttachments { export interface MediaAttachments {
/** The attachment's data stored as a mimebundle. */ /** The attachment's data stored as a mimebundle. */
[filename: string]: MimeBundle, [filename: string]: MimeBundle
} }
@ -188,62 +189,62 @@ export enum OutputType {
export interface ExecuteResult { export interface ExecuteResult {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.ExecuteResult, output_type: OutputType.ExecuteResult
/** A result's prompt number. */ /** A result's prompt number. */
execution_count: number | null, execution_count: number | null
/** A mime-type keyed dictionary of data */ /** A mime-type keyed dictionary of data */
data: MimeBundle, data: MimeBundle
/** Cell output metadata. */ /** Cell output metadata. */
metadata: { metadata: {
[k: string]: any, [k: string]: any,
}, }
} }
/** Data displayed as a result of code cell execution. */ /** Data displayed as a result of code cell execution. */
export interface DisplayData { export interface DisplayData {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.DisplayData, output_type: OutputType.DisplayData
/** A mime-type keyed dictionary of data */ /** A mime-type keyed dictionary of data */
data: MimeBundle, data: MimeBundle
/** Cell output metadata. */ /** Cell output metadata. */
metadata: { metadata: {
[k: string]: any, [k: string]: any,
}, }
} }
/** Stream output from a code cell. */ /** Stream output from a code cell. */
export interface StreamOutput { export interface StreamOutput {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.Stream, output_type: OutputType.Stream
/** The name of the stream (stdout, stderr). */ /** The name of the stream (stdout, stderr). */
name: string, name: string
/** The stream's text output, represented as an array of strings. */ /** The stream's text output, represented as an array of strings. */
text: MultilineString, text: MultilineString
} }
/** Output of an error that occurred during code cell execution. */ /** Output of an error that occurred during code cell execution. */
export interface ErrorOutput { export interface ErrorOutput {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.Error, output_type: OutputType.Error
/** The name of the error. */ /** The name of the error. */
ename: string, ename: string
/** The value, or message, of the error. */ /** The value, or message, of the error. */
evalue: string, evalue: string
/** The error's traceback, represented as an array of strings. */ /** The error's traceback, represented as an array of strings. */
traceback: string[], traceback: string[]
} }
@ -252,7 +253,7 @@ export interface ErrorOutput {
export interface MimeBundle { export interface MimeBundle {
/** mimetype output (e.g. text/plain), represented as either an array of strings or a string. */ /** mimetype output (e.g. text/plain), represented as either an array of strings or a string. */
[mediaType: string]: MultilineString, [mediaType: string]: MultilineString
} }
export type MultilineString = string | string[] export type MultilineString = string | string[]

View file

@ -1,6 +1,6 @@
// This code is originally based on notebookjs 0.4.2 distributed under the MIT license. // This code is originally based on notebookjs 0.4.2 distributed under the MIT license.
import { ElementCreator } from './elementCreator' import { ElementCreator } from './elementCreator'
import { callableObject, escapeHTML, identity } from './internal/utils' import { CallableInstance, escapeHTML, identity } from './internal/utils'
import { import {
Cell, Cell,
CellType, CellType,
@ -17,18 +17,18 @@ import {
} from './nbformat' } from './nbformat'
export type Options<TElement = HTMLElement> = { export type NbRendererOpts<TElement = HTMLElement> = {
/** /**
* An object with additional data renderers indexed by a media type. * An object with additional data renderers indexed by a media type.
*/ */
dataRenderers?: DataRenderers<TElement>, dataRenderers?: DataRenderers<TElement>,
/** /**
* An array of the supported media types in the priority order. When a cell * An array of the supported MIME types in the priority order. When a cell
* contains multiple representations of the data, the one with the media type * contains multiple representations of the data, the one with the media type
* that has the lowest index in this array will be rendered. The default is * that has the lowest index in this array will be rendered. The default is
* `Object.keys({ ...dataRenderers, ...builtinRenderers })`. * `Object.keys({ ...dataRenderers, ...builtinRenderers })`.
*/ */
dataRenderersOrder?: string[], dataTypesPriority?: string[],
/** /**
* A function for converting ANSI escape sequences in the given *text* to HTML. * A function for converting ANSI escape sequences in the given *text* to HTML.
* It gets the text from the cell as-is, without prior escaping, so it must * It gets the text from the cell as-is, without prior escaping, so it must
@ -48,7 +48,7 @@ export type Options<TElement = HTMLElement> = {
markdownRenderer?: (markup: string) => string, markdownRenderer?: (markup: string) => string,
} }
export type DataRenderer<TElement = HTMLElement> = (data: string) => TElement export type DataRenderer<TElement = HTMLElement> = (this: NbRenderer<TElement> | void, data: string) => TElement
type DataRenderers<TElement> = { [mediaType: string]: DataRenderer<TElement> } type DataRenderers<TElement> = { [mediaType: string]: DataRenderer<TElement> }
@ -83,155 +83,173 @@ function executionCountAttrs ({ execution_count: count }: CodeCell): { [k: strin
} }
function notebookLanguage ({ metadata: meta }: Notebook): string { function notebookLanguage ({ metadata: meta }: Notebook): string {
return (meta.language_info && meta.language_info.name) || 'python' return meta.language_info?.name ?? 'python'
} }
/** class NbRenderer <TElement> extends CallableInstance<NbRenderer<TElement>> {
* Builds a Notebook renderer function with the given options. It returns a
* "callable object" that exposes a renderer function for each of the
* Notebook's AST node. You can easily replace any of the functions to modify
* behaviour of the renderer.
*
* @param {ElementCreator} elementCreator The function that will be used for
* building all HTML elements.
* @param {Options} opts
* @return {NbRenderer}
* @template TElement Type of the element object that *elementCreator* produces.
*/
function buildRenderer <TElement> (elementCreator: ElementCreator<TElement>, opts: Options<TElement> = {}) {
const renderMarkdown = opts.markdownRenderer || identity
const renderAnsiCodes = opts.ansiCodesRenderer || escapeHTML
const highlightCode = opts.codeHighlighter || escapeHTML
const el = elementCreator readonly el: ElementCreator<TElement>
const el2 = (tag: string, classes: string[]) => (data: string) => el(tag, classes, data) readonly renderMarkdown: NonNullable<NbRendererOpts['markdownRenderer']>
readonly renderAnsiCodes: NonNullable<NbRendererOpts['ansiCodesRenderer']>
readonly highlightCode: NonNullable<NbRendererOpts['codeHighlighter']>
readonly dataRenderers: DataRenderers<TElement>
readonly dataTypesPriority: string[]
const embeddedImageEl = (format: string) => (data: string) => el('img', { /**
class: 'image-output', * Creates a Notebook renderer with the given options. The constructed object
src: `data:image/${format};base64,${data.replace(/\n/g, '')}`, * is "callable", i.e. you can treat it as a function.
}) *
* @example
* const renderer = new NbRenderer(document.createElement.bind(document))
* console.log(renderer(notebook).outerHTML)
*
* @param {ElementCreator} elementCreator The function that will be used for
* building all HTML elements.
* @param {NbRendererOpts} opts The renderer's options.
*/
constructor (elementCreator: ElementCreator<TElement>, opts: NbRendererOpts<TElement> = {}) {
super()
// opts.dataRenderers is intentionally included twice; to get the user's this.el = elementCreator
// provided renderers in the default dataRenderersOrder before the built-in this.renderMarkdown = opts.markdownRenderer ?? identity
// renderers and at the same time allow to override any built-in renderer. this.renderAnsiCodes = opts.ansiCodesRenderer ?? escapeHTML
const dataRenderers: DataRenderers<TElement> = { this.highlightCode = opts.codeHighlighter ?? escapeHTML
...opts.dataRenderers,
'image/png': embeddedImageEl('png'),
'image/jpeg': embeddedImageEl('jpeg'),
'image/svg+xml': el2('div', ['svg-output']),
'text/svg+xml': (data) => dataRenderers['image/svg+xml'](data),
'text/html': el2('div', ['html-output']),
'text/markdown': (data) => dataRenderers['text/html'](renderMarkdown(data)),
'text/latex': el2('div', ['latex-output']),
'application/javascript': el2('script', []),
'text/plain': (data) => el('pre', ['text-output'], escapeHTML(data)),
...opts.dataRenderers,
}
const dataRenderersOrder = opts.dataRenderersOrder || Object.keys(dataRenderers)
const resolveDataType = (output: DisplayData | ExecuteResult) => { const el2 = (tag: string, classes: string[]) => (data: string) => this.el(tag, classes, data)
return dataRenderersOrder.find(type => output.data[type] && dataRenderers[type])
const embeddedImageEl = (format: string) => (data: string) => this.el('img', {
class: 'image-output',
src: `data:image/${format};base64,${data.replace(/\n/g, '')}`,
})
// opts.dataRenderers is intentionally included twice; to get the user's
// provided renderers in the default dataTypesPriority before the built-in
// renderers and at the same time allow to override any built-in renderer.
this.dataRenderers = {
...opts.dataRenderers,
'image/png': embeddedImageEl('png'),
'image/jpeg': embeddedImageEl('jpeg'),
'image/svg+xml': el2('div', ['svg-output']),
'text/svg+xml': (data) => this.dataRenderers['image/svg+xml'].call(this, data),
'text/html': el2('div', ['html-output']),
'text/markdown': (data) => this.el('div', ['html-output'], this.renderMarkdown(data)),
'text/latex': el2('div', ['latex-output']),
'application/javascript': el2('script', []),
'text/plain': (data) => this.el('pre', ['text-output'], escapeHTML(data)),
...opts.dataRenderers,
}
this.dataTypesPriority = opts.dataTypesPriority ?? Object.keys(this.dataRenderers)
} }
const r = callableObject('Notebook', { /**
Notebook: (notebook: Notebook): TElement => { * Renders the given Jupyter *notebook*.
const children = notebook.cells.map(cell => r.Cell(cell, notebook)) */
// Class "worksheet" is for backward compatibility with notebook.js. __call__ (notebook: Notebook): TElement {
return el('div', ['notebook', 'worksheet'], children) return this.render(notebook)
}, }
Cell: (cell: Cell, notebook: Notebook): TElement => { /**
switch (cell.cell_type) { * Renders the given Jupyter *notebook*.
case CellType.Code: return r.CodeCell(cell, notebook) */
case CellType.Markdown: return r.MarkdownCell(cell, notebook) render (notebook: Notebook): TElement {
case CellType.Raw: return r.RawCell(cell, notebook) const children = notebook.cells.map(cell => this.renderCell(cell, notebook))
default: return el('div', [], '<!-- Unsupported cell type -->') return this.el('div', ['notebook'], children)
}
renderCell (cell: Cell, notebook: Notebook): TElement {
switch (cell.cell_type) {
case CellType.Code: return this.renderCodeCell(cell, notebook)
case CellType.Markdown: return this.renderMarkdownCell(cell, notebook)
case CellType.Raw: return this.renderRawCell(cell, notebook)
default: return this.el('div', [], '<!-- Unsupported cell type -->')
}
}
renderMarkdownCell (cell: MarkdownCell, _notebook: Notebook): TElement {
return this.el('section', ['cell', 'markdown-cell'], this.renderMarkdown(joinText(cell.source)))
}
renderRawCell (cell: RawCell, _notebook: Notebook): TElement {
return this.el('section', ['cell', 'raw-cell'], joinText(cell.source))
}
renderCodeCell (cell: CodeCell, notebook: Notebook): TElement {
const source = cell.source.length > 0
? this.renderSource(cell, notebook)
: this.el('div')
const outputs = coalesceStreams(cell.outputs ?? [])
.map(output => this.renderOutput(output, cell))
return this.el('section', ['cell', 'code-cell'], [source, ...outputs])
}
renderSource (cell: CodeCell, notebook: Notebook): TElement {
const lang = notebookLanguage(notebook)
const html = this.highlightCode(joinText(cell.source), lang)
const codeEl = this.el('code', { 'class': `lang-${lang}`, 'data-language': lang }, html)
const preEl = this.el('pre', [], [codeEl])
// Class "input" is for backward compatibility with notebook.js.
const attrs = { ...executionCountAttrs(cell), class: 'source input' }
return this.el('div', attrs, [preEl])
}
renderOutput (output: Output, cell: CodeCell): TElement {
const innerEl = (() => {
switch (output.output_type) {
case OutputType.DisplayData: return this.renderDisplayData(output)
case OutputType.ExecuteResult: return this.renderExecuteResult(output)
case OutputType.Stream: return this.renderStream(output)
case OutputType.Error: return this.renderError(output)
default: return this.el('div', [], '<!-- Unsupported output type -->')
} }
}, })()
const attrs = { ...executionCountAttrs(cell), class: 'output' }
MarkdownCell: (cell: MarkdownCell, _notebook: Notebook): TElement => { return this.el('div', attrs, [innerEl])
return el('div', ['cell', 'markdown-cell'], renderMarkdown(joinText(cell.source))) }
},
RawCell: (cell: RawCell, _notebook: Notebook): TElement => { renderDisplayData (output: DisplayData): TElement {
return el('div', ['cell', 'raw-cell'], joinText(cell.source)) const type = this.resolveDataType(output)
}, if (type) {
return this.renderData(type, joinText(output.data[type]))
}
return this.el('div', ['empty-output'])
}
CodeCell: (cell: CodeCell, notebook: Notebook): TElement => { renderExecuteResult (output: ExecuteResult): TElement {
const source = cell.source.length > 0 const type = this.resolveDataType(output)
? r.Source(cell, notebook) if (type) {
: el('div') return this.renderData(type, joinText(output.data[type]))
}
return this.el('div', ['empty-output'])
}
const outputs = coalesceStreams(cell.outputs || []) renderError (error: ErrorOutput): TElement {
.map(output => r.Output(output, cell)) const html = this.renderAnsiCodes(error.traceback.join('\n'))
// Class "pyerr" is for backward compatibility with notebook.js.
return this.el('pre', ['error', 'pyerr'], html)
}
return el('div', ['cell', 'code-cell'], [source, ...outputs]) renderStream (stream: StreamOutput): TElement {
}, const html = this.renderAnsiCodes(joinText(stream.text))
return this.el('pre', [stream.name], html)
}
Source: (cell: CodeCell, notebook: Notebook): TElement => { renderData (mimeType: string, data: string): TElement {
const lang = notebookLanguage(notebook) const render = this.dataRenderers[mimeType]
const html = highlightCode(joinText(cell.source), lang) if (!render) {
throw RangeError(`missing renderer for MIME type: ${mimeType}`)
}
return render.call(this, data)
}
const codeEl = el('code', { 'class': `lang-${lang}`, 'data-language': lang }, html) resolveDataType (output: DisplayData | ExecuteResult): string | undefined {
const preEl = el('pre', [], [codeEl]) return this.dataTypesPriority.find(type => output.data[type])
}
// Class "input" is for backward compatibility with notebook.js.
const attrs = { ...executionCountAttrs(cell), class: 'source input' }
return el('div', attrs, [preEl])
},
Output: (output: Output, cell: CodeCell): TElement => {
const innerEl = (() => {
switch (output.output_type) {
case OutputType.DisplayData: return r.DisplayData(output)
case OutputType.ExecuteResult: return r.ExecuteResult(output)
case OutputType.Stream: return r.Stream(output)
case OutputType.Error: return r.Error(output)
default: return el('div', [], '<!-- Unsupported output type -->')
}
})()
const attrs = { ...executionCountAttrs(cell), class: 'output' }
return el('div', attrs, [innerEl])
},
DisplayData: (output: DisplayData): TElement => {
const type = resolveDataType(output)
if (type) {
return dataRenderers[type](joinText(output.data[type]))
}
return el('div', ['empty-output'])
},
ExecuteResult: (output: ExecuteResult): TElement => {
const type = resolveDataType(output)
if (type) {
return dataRenderers[type](joinText(output.data[type]))
}
return el('div', ['empty-output'])
},
Error: (error: ErrorOutput): TElement => {
const html = renderAnsiCodes(error.traceback.join('\n'))
// Class "pyerr" is for backward compatibility with notebook.js.
return el('pre', ['error', 'pyerr'], html)
},
Stream: (stream: StreamOutput): TElement => {
const html = renderAnsiCodes(joinText(stream.text))
return el('pre', [stream.name], html)
},
})
return r
} }
export default buildRenderer export default NbRenderer
// XXX: An ugly hack to infer return type of generic function that returns
// generalized object.
abstract class DummyClass<T> {
renderer = buildRenderer<T>(this.elementCreator())
abstract elementCreator (): ElementCreator<T>
}
export type NbRenderer<TElement> = DummyClass<TElement>['renderer']

View file

@ -1,5 +1,6 @@
import { $INLINE_JSON } from 'ts-transformer-inline-file' import { $INLINE_JSON } from 'ts-transformer-inline-file'
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { version } = $INLINE_JSON('../package.json') const { version } = $INLINE_JSON('../package.json')
export default version as string export default version as string

View file

@ -1,31 +1,64 @@
import { callableObject, escapeHTML, identity } from '@/internal/utils' import { CallableInstance, escapeHTML, identity } from '@/internal/utils'
describe('.callableObject', () => { describe('CallableInstance', () => {
describe('returned value', () => { class FixtureCallable extends CallableInstance<FixtureCallable> {
const template = { readonly salutation: string
str: 'allons-y!',
func1: jest.fn().mockReturnValue(1), constructor (salutation: string) {
func2: jest.fn().mockReturnValue(2), super()
this.salutation = salutation
} }
const subject = callableObject('func1', template)
it('is a function that calls the specified template function', () => { __call__ (name: string) {
expect( subject ).toBeInstanceOf(Function) return this.salute(name)
expect( subject('a', 'b') ).toBe(1) }
expect( template.func1 ).toBeCalledWith('a', 'b')
salute (name: string) {
return `${this.salutation}, ${name}!`
}
}
describe('subclass', () => {
it('can be instantiated using new', () => {
expect(() => new FixtureCallable('Hello') ).not.toThrow()
})
})
describe('subclass instance', () => {
let instance: FixtureCallable
beforeEach(() => {
instance = new FixtureCallable('Hello')
}) })
it('is not the same function as the specified template function', () => { it('is an instance of its class', () => {
expect( subject ).not.toBe(template.func1) expect( instance ).toBeInstanceOf(FixtureCallable)
expect( instance.salutation ).toBe('Hello')
expect( instance.salute('world') ).toBe('Hello, world!')
}) })
it('has all enumerable properties of the given template', () => { it('is an instance of Function', () => {
expect( subject ) expect( instance ).toBeInstanceOf(Function)
.toHaveProperty('str', template.str) })
.toHaveProperty('func1', template.func1)
.toHaveProperty('func2', template.func2) it('is a typeof function', () => {
expect( typeof instance ).toBe('function')
})
it('has function property "name" that equals the class name', () => {
expect( instance.name ).toBe('FixtureCallable')
})
it('has function property "length" that equals number of arguments of the __call__ method', () => {
expect( instance.length ).toBe(1)
})
it('can be called, redirects to the method __call__', () => {
expect( instance('world') ).toBe('Hello, world!')
expect( instance.apply(null, ['world']) ).toBe('Hello, world!') // eslint-disable-line no-useless-call
expect( instance.call(null, 'world') ).toBe('Hello, world!') // eslint-disable-line no-useless-call
}) })
}) })
}) })

View file

@ -20,7 +20,7 @@ describe('.extractMath', () => {
it('extracts and substitutes math expression in the given text', () => { it('extracts and substitutes math expression in the given text', () => {
expect( expect(
extractMath(`Let's define ${raw}.`) extractMath(`Let's define ${raw}.`),
).toEqual(["Let's define @@1@@.", [{ displayMode, raw, value }]]) ).toEqual(["Let's define @@1@@.", [{ displayMode, raw, value }]])
}) })
}) })
@ -31,7 +31,7 @@ describe('.extractMath', () => {
it('extracts and substitutes math expression in the given text', () => { it('extracts and substitutes math expression in the given text', () => {
expect( expect(
extractMath(`Let's define ${raw}.`) extractMath(`Let's define ${raw}.`),
).toEqual(["Let's define @@1@@.", [{ displayMode, raw, value }]]) ).toEqual(["Let's define @@1@@.", [{ displayMode, raw, value }]])
}) })
}) })
@ -43,7 +43,7 @@ describe('.extractMath', () => {
it('extracts and substitutes math expression in the given text', () => { it('extracts and substitutes math expression in the given text', () => {
expect( expect(
extractMath(`Let's define ${raw}.`) extractMath(`Let's define ${raw}.`),
).toEqual(["Let's define @@1@@.", [{ displayMode: true, raw, value: raw }]]) ).toEqual(["Let's define @@1@@.", [{ displayMode: true, raw, value: raw }]])
}) })
}) })
@ -52,14 +52,14 @@ describe('.extractMath', () => {
it('escapes @@[0-9]+@@ as @@0[0-9]+@@', () => { it('escapes @@[0-9]+@@ as @@0[0-9]+@@', () => {
expect( expect(
extractMath('This @@02@@ is not our marker') extractMath('This @@02@@ is not our marker'),
).toEqual(['This @@002@@ is not our marker', []]) ).toEqual(['This @@002@@ is not our marker', []])
}) })
}) })
it('ignores math delimiters inside `inline code`', () => { it('ignores math delimiters inside `inline code`', () => {
expect( expect(
extractMath('`$x$` and ``$`x`$`` is a code, $x$ is not') extractMath('`$x$` and ``$`x`$`` is a code, $x$ is not'),
).toEqual([ ).toEqual([
'`$x$` and ``$`x`$`` is a code, @@1@@ is not', '`$x$` and ``$`x`$`` is a code, @@1@@ is not',
[{ displayMode: false, raw: '$x$', value: 'x' }], [{ displayMode: false, raw: '$x$', value: 'x' }],
@ -68,7 +68,7 @@ describe('.extractMath', () => {
it('ignores math delimiters inside `inline code` with line breaks', () => { it('ignores math delimiters inside `inline code` with line breaks', () => {
expect( expect(
extractMath('`$x\n$` and ``\n$`x`$\n`` is a code, `$x$\n\nis` not') extractMath('`$x\n$` and ``\n$`x`$\n`` is a code, `$x$\n\nis` not'),
).toEqual([ ).toEqual([
'`$x\n$` and ``\n$`x`$\n`` is a code, `@@1@@\n\nis` not', '`$x\n$` and ``\n$`x`$\n`` is a code, `@@1@@\n\nis` not',
[{ displayMode: false, raw: '$x$', value: 'x' }], [{ displayMode: false, raw: '$x$', value: 'x' }],
@ -135,13 +135,13 @@ describe('.restoreMath', () => {
repl[21] = 'second' repl[21] = 'second'
expect( expect(
restoreMath("Let's define @@1@@ and @@22@@.", repl) restoreMath("Let's define @@1@@ and @@22@@.", repl),
).toEqual("Let's define first and second.") ).toEqual("Let's define first and second.")
}) })
it('unescapes marker-like sequences', () => { it('unescapes marker-like sequences', () => {
expect( expect(
restoreMath('This @@001@@ is not our marker, nor @@01@@, but @@1@@ is.', ['this one']) restoreMath('This @@001@@ is not our marker, nor @@01@@, but @@1@@ is.', ['this one']),
).toEqual('This @@01@@ is not our marker, nor @@1@@, but this one is.') ).toEqual('This @@01@@ is not our marker, nor @@1@@, but this one is.')
}) })
}) })

View file

@ -1,19 +1,20 @@
/* eslint-disable @typescript-eslint/unbound-method */
import '~/test/setup' // setupFilesAfterEnv doesn't work here import '~/test/setup' // setupFilesAfterEnv doesn't work here
import arrify from 'arrify' import arrify from 'arrify'
import { Document, HTMLElement } from 'nodom' import { Document, HTMLElement } from 'nodom'
import buildElementCreator from '@/elementCreator' import buildElementCreator from '@/elementCreator'
import buildRenderer, { NbRenderer, Options as RendererOpts } from '@/renderer' import NbRenderer, { NbRendererOpts } from '@/renderer'
import { DisplayData, MimeBundle, MultilineString, Notebook } from '@/nbformat' import { DisplayData, MimeBundle, MultilineString, Notebook } from '@/nbformat'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Anything } from '~/test/support/matchers/toMatchElement' import { Anything } from '~/test/support/matchers/toMatchElement'
import { mockLastResult, mockResults } from '~/test/support/helpers' import { mockLastResult, mockResults } from '~/test/support/helpers'
import * as _fixtures from './support/fixtures/notebook' import * as fixtures from './support/fixtures/notebook'
const document = new Document() const document = new Document()
const fixtures = _fixtures // workaround to allow indexed access
describe('built renderer', () => { describe('built renderer', () => {
@ -27,7 +28,7 @@ describe('built renderer', () => {
const dataRenderers = { const dataRenderers = {
'text/custom': rendererMock('DisplayData'), 'text/custom': rendererMock('DisplayData'),
} }
const rendererOpts: RendererOpts<HTMLElement> = { const rendererOpts: NbRendererOpts<HTMLElement> = {
ansiCodesRenderer, ansiCodesRenderer,
codeHighlighter, codeHighlighter,
markdownRenderer, markdownRenderer,
@ -37,45 +38,46 @@ describe('built renderer', () => {
beforeEach(() => { beforeEach(() => {
renderer = buildRenderer(elementCreator, rendererOpts) renderer = new NbRenderer(elementCreator, rendererOpts)
}) })
describe('.Notebook', () => { describe('.render', () => {
beforeEach(() => { beforeEach(() => {
renderer.Cell = rendererMock('Cell') renderer.renderCell = rendererMock('Cell')
}) })
it('returns div.notebook.worksheet', () => { it('returns div.notebook', () => {
expect( renderer.Notebook({ ...notebook, cells: [] }) ).toHtmlEqual( expect( renderer.render({ ...notebook, cells: [] }) ).toHtmlEqual(
<div class="notebook worksheet"></div> <div class="notebook"></div>
) )
}) })
it('returns element with $cells converted using .Cell() as the children', () => { it('returns element with $cells converted using .renderCell() as the children', () => {
const result = renderer.Notebook(notebook) const result = renderer.render(notebook)
notebook.cells.forEach((cell, idx) => { notebook.cells.forEach((cell, idx) => {
expect( renderer.Cell ).toHaveBeenNthCalledWith(idx + 1, cell, notebook) expect( renderer.renderCell ).toHaveBeenNthCalledWith(idx + 1, cell, notebook)
}) })
expect( result.children ).toHtmlEqual(mockResults(renderer.Cell)) expect( result.children ).toHtmlEqual(mockResults(renderer.renderCell))
}) })
}) })
describe('.Cell', () => { describe('.renderCell', () => {
describe.each([ describe.each([
'CodeCell', 'MarkdownCell', 'RawCell', 'renderCodeCell', 'renderMarkdownCell', 'renderRawCell',
] as const)('with %s', (type) => { ] as const)('with %s', (funcName) => {
const cell = fixtures[type] const type = funcName.replace('render', '')
const cell = (fixtures as any)[type]
it(`returns result of calling .${type}() with the given cell`, () => { it(`returns result of calling .${funcName}() with the given cell`, () => {
const expected = stubElement(type) const expected = stubElement(type)
renderer[type] = jest.fn(() => expected) const rendererFunc = (renderer as any)[funcName] = jest.fn(() => expected)
expect( renderer.Cell(cell, notebook) ).toBe(expected) expect( renderer.renderCell(cell, notebook) ).toBe(expected)
expect( renderer[type] ).toBeCalledWith(cell, notebook) expect( rendererFunc ).toBeCalledWith(cell, notebook)
}) })
}) })
@ -86,7 +88,7 @@ describe('built renderer', () => {
} as any } as any
it('returns div with comment "Unsupported cell type"', () => { it('returns div with comment "Unsupported cell type"', () => {
expect( renderer.Cell(cell, notebook) ).toHtmlEqual( expect( renderer.renderCell(cell, notebook) ).toHtmlEqual(
<div> <div>
{{__html: '<!-- Unsupported cell type -->' }} {{__html: '<!-- Unsupported cell type -->' }}
</div> </div>
@ -96,15 +98,15 @@ describe('built renderer', () => {
}) })
describe('.MarkdownCell', () => { describe('.renderMarkdownCell', () => {
eachMultilineVariant(fixtures.MarkdownCell, 'source', (cell) => { eachMultilineVariant(fixtures.MarkdownCell, 'source', (cell) => {
const source = join(cell.source) const source = join(cell.source)
it('returns div.cell.markdown-cell with the $source converted using markdownRenderer() as content', () => { it('returns section.cell.markdown-cell with the $source converted using markdownRenderer() as content', () => {
expect( renderer.MarkdownCell(cell, notebook) ).toHtmlEqual( expect( renderer.renderMarkdownCell(cell, notebook) ).toHtmlEqual(
<div class="cell markdown-cell"> <section class="cell markdown-cell">
{{__html: mockLastResult(markdownRenderer) }} {{__html: mockLastResult(markdownRenderer) }}
</div> </section>
) )
expect( markdownRenderer ).toBeCalledWith(source) expect( markdownRenderer ).toBeCalledWith(source)
}) })
@ -112,48 +114,48 @@ describe('built renderer', () => {
}) })
describe('.RawCell', () => { describe('.renderRawCell', () => {
eachMultilineVariant(fixtures.RawCell, 'source', (cell) => { eachMultilineVariant(fixtures.RawCell, 'source', (cell) => {
it('returns div.cell.raw-cell with the $source as content', () => { it('returns section.cell.raw-cell with the $source as content', () => {
expect( renderer.RawCell(cell, notebook) ).toHtmlEqual( expect( renderer.renderRawCell(cell, notebook) ).toHtmlEqual(
<div class="cell raw-cell"> <section class="cell raw-cell">
{{__html: join(cell.source) }} {{__html: join(cell.source) }}
</div> </section>
) )
}) })
}) })
}) })
describe('.CodeCell', () => { describe('.renderCodeCell', () => {
const cell = fixtures.CodeCell const cell = fixtures.CodeCell
let result: HTMLElement let result: HTMLElement
beforeEach(() => { beforeEach(() => {
renderer.Source = rendererMock('Source') renderer.renderSource = rendererMock('Source')
renderer.Output = rendererMock('Output') renderer.renderOutput = rendererMock('Output')
result = renderer.CodeCell(cell, notebook) result = renderer.renderCodeCell(cell, notebook)
}) })
it('returns div.cell.code-cell', () => { it('returns section.cell.code-cell', () => {
expect( result ).toMatchElement( expect( result ).toMatchElement(
<div class="cell code-cell"><Anything /></div> <section class="cell code-cell"><Anything /></section>
) )
}) })
describe('with non-empty $source', () => { describe('with non-empty $source', () => {
it('returns element with $source rendered using .Source() as children[0]', () => { it('returns element with $source rendered using .renderSource() as children[0]', () => {
expect( renderer.Source ).toBeCalledWith(cell, notebook) expect( renderer.renderSource ).toBeCalledWith(cell, notebook)
expect( result.children[0] ).toHtmlEqual(mockLastResult(renderer.Source)) expect( result.children[0] ).toHtmlEqual(mockLastResult(renderer.renderSource)!)
}) })
}) })
describe('with empty $source', () => { describe('with empty $source', () => {
beforeEach(() => { beforeEach(() => {
result = renderer.CodeCell({ ...cell, source: [] }, notebook) result = renderer.renderCodeCell({ ...cell, source: [] }, notebook)
}) })
it('returns element with empty div as children[0]', () => { it('returns element with empty div as children[0]', () => {
@ -163,22 +165,22 @@ describe('built renderer', () => {
}) })
}) })
it('returns element with $outputs rendered using .Output() as children[1+]', () => { it('returns element with $outputs rendered using .renderOutput() as children[1+]', () => {
cell.outputs.forEach((output, idx) => { cell.outputs.forEach((output, idx) => {
expect( renderer.Output ).toHaveBeenNthCalledWith(idx + 1, output, cell) expect( renderer.renderOutput ).toHaveBeenNthCalledWith(idx + 1, output, cell)
}) })
expect( result.children.slice(1) ).toHtmlEqual(mockResults(renderer.Output)) expect( result.children.slice(1) ).toHtmlEqual(mockResults(renderer.renderOutput))
}) })
}) })
describe('.Source', () => { describe('.renderSource', () => {
const cell = fixtures.CodeCell const cell = fixtures.CodeCell
const notebookLang = notebook.metadata.language_info!.name const notebookLang = notebook.metadata.language_info!.name
let result: HTMLElement let result: HTMLElement
beforeEach(() => { beforeEach(() => {
result = renderer.Source(cell, notebook) result = renderer.renderSource(cell, notebook)
}) })
it('returns div > pre > code', () => { it('returns div > pre > code', () => {
@ -206,7 +208,7 @@ describe('built renderer', () => {
const myCell = { ...cell, execution_count: 2 } const myCell = { ...cell, execution_count: 2 }
it('has data-execution-count and data-prompt-number attributes', () => { it('has data-execution-count and data-prompt-number attributes', () => {
const result = renderer.Source(myCell, notebook) const result = renderer.renderSource(myCell, notebook)
expect( result.attributes ).toMatchObject({ expect( result.attributes ).toMatchObject({
'data-execution-count': String(myCell.execution_count), 'data-execution-count': String(myCell.execution_count),
@ -219,7 +221,7 @@ describe('built renderer', () => {
const myCell = { ...cell, execution_count: null } const myCell = { ...cell, execution_count: null }
it('has data-execution-count and data-prompt-number attributes', () => { it('has data-execution-count and data-prompt-number attributes', () => {
const result = renderer.Source(myCell, notebook) const result = renderer.renderSource(myCell, notebook)
expect( result.attributes ) expect( result.attributes )
.not.toHaveProperty('data-execution-count') .not.toHaveProperty('data-execution-count')
@ -232,7 +234,7 @@ describe('built renderer', () => {
let codeEl: HTMLElement let codeEl: HTMLElement
beforeEach(() => { beforeEach(() => {
codeEl = renderer.Source(cell, notebook).firstChild!.firstChild as HTMLElement codeEl = renderer.renderSource(cell, notebook).firstChild!.firstChild as HTMLElement
}) })
it("has class lang-<lang> where lang is the notebook's language", () => { it("has class lang-<lang> where lang is the notebook's language", () => {
@ -254,7 +256,7 @@ describe('built renderer', () => {
const notebookLang = 'python' const notebookLang = 'python'
it('uses the default language: python', () => { it('uses the default language: python', () => {
const result = renderer.Source(cell, myNotebook) const result = renderer.renderSource(cell, myNotebook)
const codeEl = result.firstChild!.firstChild as HTMLElement const codeEl = result.firstChild!.firstChild as HTMLElement
expect( codeEl.getAttribute('data-language') ).toBe(notebookLang) expect( codeEl.getAttribute('data-language') ).toBe(notebookLang)
@ -265,19 +267,20 @@ describe('built renderer', () => {
}) })
describe('.Output', () => { describe('.renderOutput', () => {
const cell = { ...fixtures.CodeCell, execution_count: null } const cell = { ...fixtures.CodeCell, execution_count: null }
describe.each([ describe.each([
'DisplayData', 'ExecuteResult', 'Stream', 'Error', 'renderDisplayData', 'renderExecuteResult', 'renderStream', 'renderError',
] as const)('with %s output', (type) => { ] as const)('with %s output', (funcName) => {
const output = fixtures[type] const type = funcName.replace('render', '')
const output = (fixtures as any)[type]
let result: HTMLElement let result: HTMLElement
beforeEach(() => { beforeEach(() => {
renderer[type] = rendererMock(type) renderer[funcName] = rendererMock(type)
result = renderer.Output(output, cell) result = renderer.renderOutput(output, cell)
}) })
it('returns div.output', () => { it('returns div.output', () => {
@ -286,16 +289,16 @@ describe('built renderer', () => {
) )
}) })
it(`returns element with the output rendered using .${type}() as the only child`, () => { it(`returns element with the output rendered using .${funcName}() as the only child`, () => {
expect( renderer[type] ).toBeCalledWith(output) expect( renderer[funcName] ).toBeCalledWith(output)
expect( result.children ).toHtmlEqual([mockLastResult(renderer[type])]) expect( result.children ).toHtmlEqual([mockLastResult(renderer[funcName])!])
}) })
describe('when the cell has non-null execution_count', () => { describe('when the cell has non-null execution_count', () => {
const cell = { ...fixtures.CodeCell, execution_count: 2 } const cell = { ...fixtures.CodeCell, execution_count: 2 }
it('returns element with attributes data-execution-count and data-prompt-number', () => { it('returns element with attributes data-execution-count and data-prompt-number', () => {
const result = renderer.Output(output, cell) const result = renderer.renderOutput(output, cell)
expect( result.attributes ).toMatchObject({ expect( result.attributes ).toMatchObject({
'data-execution-count': String(cell.execution_count), 'data-execution-count': String(cell.execution_count),
@ -317,7 +320,7 @@ describe('built renderer', () => {
} }
it('returns div with comment "Unsupported output type"', () => { it('returns div with comment "Unsupported output type"', () => {
expect( renderer.Output(output, cell) ).toHtmlEqual( expect( renderer.renderOutput(output, cell) ).toHtmlEqual(
<div class="output"> <div class="output">
<div> <div>
{{__html: '<!-- Unsupported output type -->' }} {{__html: '<!-- Unsupported output type -->' }}
@ -329,7 +332,7 @@ describe('built renderer', () => {
}) })
describe('.DisplayData', () => { describe('.renderDisplayData', () => {
function displayDataWith (data: MimeBundle): DisplayData { function displayDataWith (data: MimeBundle): DisplayData {
return { ...fixtures.DisplayData, data } return { ...fixtures.DisplayData, data }
@ -358,7 +361,7 @@ describe('built renderer', () => {
const displayData = displayDataWith({ 'text/non-sense': 'whaat' }) const displayData = displayDataWith({ 'text/non-sense': 'whaat' })
it('returns div.empty-output', () => { it('returns div.empty-output', () => {
expect( renderer.DisplayData(displayData) ).toHtmlEqual( expect( renderer.renderDisplayData(displayData) ).toHtmlEqual(
<div class="empty-output"></div> <div class="empty-output"></div>
) )
}) })
@ -370,7 +373,7 @@ describe('built renderer', () => {
withMimeData(mimeType, ['aW1hZ2Ug\n', 'ZGF0YQ=='], (output) => { withMimeData(mimeType, ['aW1hZ2Ug\n', 'ZGF0YQ=='], (output) => {
it('returns img.image-output with the data in the src attribute', () => { it('returns img.image-output with the data in the src attribute', () => {
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
<img class="image-output" src={`data:${mimeType};base64,aW1hZ2UgZGF0YQ==`}></img> <img class="image-output" src={`data:${mimeType};base64,aW1hZ2UgZGF0YQ==`}></img>
) )
}) })
@ -388,7 +391,7 @@ describe('built renderer', () => {
withMimeData(mimeType, '<stub>data</stub>', (output, data) => { withMimeData(mimeType, '<stub>data</stub>', (output, data) => {
it(`returns div${classes.map(x => `.${x}`)} with the data as content`, () => { it(`returns div${classes.map(x => `.${x}`)} with the data as content`, () => {
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
<div class={ classes.join(' ') }> <div class={ classes.join(' ') }>
{{__html: join(data) }} {{__html: join(data) }}
</div> </div>
@ -400,7 +403,7 @@ describe('built renderer', () => {
withMimeData('text/markdown', ['Lorem\n', 'ipsum'], (output, data) => { withMimeData('text/markdown', ['Lorem\n', 'ipsum'], (output, data) => {
it('returns div.html-output with the data converted using markdownRenderer() as content', () => { it('returns div.html-output with the data converted using markdownRenderer() as content', () => {
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
<div class="html-output"> <div class="html-output">
{{__html: mockLastResult(markdownRenderer) }} {{__html: mockLastResult(markdownRenderer) }}
</div> </div>
@ -412,7 +415,7 @@ describe('built renderer', () => {
withMimeData('text/plain', '>_<', (output) => { withMimeData('text/plain', '>_<', (output) => {
it('returns pre.text-output with html-escaped data', () => { it('returns pre.text-output with html-escaped data', () => {
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
<pre class="text-output">{ '>_<' }</pre> <pre class="text-output">{ '>_<' }</pre>
) )
}) })
@ -421,7 +424,7 @@ describe('built renderer', () => {
withMimeData('application/javascript', 'alert("Hello &!")', (output, data) => { withMimeData('application/javascript', 'alert("Hello &!")', (output, data) => {
it('returns script with the data', () => { it('returns script with the data', () => {
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
<script>{{__html: join(data) }}</script> <script>{{__html: join(data) }}</script>
) )
}) })
@ -433,8 +436,8 @@ describe('built renderer', () => {
withMimeData('text/custom', 'Lorem ipsum', (output, data) => { withMimeData('text/custom', 'Lorem ipsum', (output, data) => {
it('renders the data using the associated external renderer', () => { it('renders the data using the associated external renderer', () => {
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
mockLastResult(dataRenderers['text/custom']) mockLastResult(dataRenderers['text/custom'])!
) )
expect( dataRenderers['text/custom'] ).toBeCalledWith(join(data)) expect( dataRenderers['text/custom'] ).toBeCalledWith(join(data))
}) })
@ -450,7 +453,7 @@ describe('built renderer', () => {
const output = displayDataWith(mimeBundle) const output = displayDataWith(mimeBundle)
it('renders the data of the MIME type with a higher priority', () => { it('renders the data of the MIME type with a higher priority', () => {
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
<div class="html-output"> <div class="html-output">
{{__html: mimeBundle['text/html'] }} {{__html: mimeBundle['text/html'] }}
</div> </div>
@ -464,8 +467,8 @@ describe('built renderer', () => {
} }
const output = displayDataWith(mimeBundle) const output = displayDataWith(mimeBundle)
expect( renderer.DisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
mockLastResult(dataRenderers['text/custom']) mockLastResult(dataRenderers['text/custom'])!
) )
}) })
}) })
@ -474,7 +477,7 @@ describe('built renderer', () => {
const dataRenderer = rendererMock('DisplayData') const dataRenderer = rendererMock('DisplayData')
beforeEach(() => { beforeEach(() => {
renderer = buildRenderer(elementCreator, { renderer = new NbRenderer(elementCreator, {
...rendererOpts, ...rendererOpts,
dataRenderers: { 'text/plain': dataRenderer }, dataRenderers: { 'text/plain': dataRenderer },
}) })
@ -484,19 +487,19 @@ describe('built renderer', () => {
const data = 'allons-y!' const data = 'allons-y!'
const output = displayDataWith({ 'text/plain': [data] }) const output = displayDataWith({ 'text/plain': [data] })
expect( renderer.DisplayData(output) ).toBe(mockLastResult(dataRenderer)) expect( renderer.renderDisplayData(output) ).toBe(mockLastResult(dataRenderer))
expect( dataRenderer ).toBeCalledWith(data) expect( dataRenderer ).toBeCalledWith(data)
}) })
}) })
}) })
describe('.Error', () => { describe('.renderError', () => {
const error = fixtures.Error const error = fixtures.Error
const traceback = error.traceback.join('\n') const traceback = error.traceback.join('\n')
it('returns pre.error.pyerr with inner $traceback converted using ansiCodesRenderer', () => { it('returns pre.error.pyerr with inner $traceback converted using ansiCodesRenderer', () => {
expect( renderer.Error(error) ).toHtmlEqual( expect( renderer.renderError(error) ).toHtmlEqual(
<pre class="error pyerr"> <pre class="error pyerr">
{{__html: mockLastResult(ansiCodesRenderer) }} {{__html: mockLastResult(ansiCodesRenderer) }}
</pre> </pre>
@ -506,12 +509,12 @@ describe('built renderer', () => {
}) })
describe('.Stream', () => { describe('.renderStream', () => {
eachMultilineVariant(fixtures.Stream, 'text', (stream) => { eachMultilineVariant(fixtures.Stream, 'text', (stream) => {
const text = join(stream.text) const text = join(stream.text)
it('returns pre.$name with inner $text converted using ansiCodesRenderer', () => { it('returns pre.$name with inner $text converted using ansiCodesRenderer', () => {
expect( renderer.Stream(stream) ).toHtmlEqual( expect( renderer.renderStream(stream) ).toHtmlEqual(
<pre class={ stream.name }> <pre class={ stream.name }>
{{__html: mockLastResult(ansiCodesRenderer) }} {{__html: mockLastResult(ansiCodesRenderer) }}
</pre> </pre>

View file

@ -1,5 +1,9 @@
// This file is needed only for VSCode, see https://github.com/palmerhq/tsdx/issues/84.
{ {
"extends": "../tsconfig.json", "extends": "../../../tsconfig.test.json",
"include": ["."], "compilerOptions": {
"baseUrl": ".",
},
"references": [
{ "path": "../" },
],
} }

View file

@ -5,11 +5,6 @@
"outDir": "./lib", "outDir": "./lib",
"tsBuildInfoFile": "./.tsbuildinfo", "tsBuildInfoFile": "./.tsbuildinfo",
"baseUrl": ".", "baseUrl": ".",
"paths": {
"*": ["../../types/*"],
"@/*": ["./src/*"],
"~/*": ["../../*"],
},
}, },
"include": [ "include": [
"./src", "./src",

View file

@ -1,6 +1,6 @@
{ {
"name": "ipynb2html", "name": "ipynb2html",
"version": "0.1.0-beta.8", "version": "0.3.0",
"description": "Convert Jupyter Notebook to static HTML", "description": "Convert Jupyter Notebook to static HTML",
"author": "Jakub Jirutka <jakub@jirutka.cz>", "author": "Jakub Jirutka <jakub@jirutka.cz>",
"license": "MIT", "license": "MIT",
@ -23,15 +23,18 @@
"files": [ "files": [
"dist/ipynb2html.min.js*", "dist/ipynb2html.min.js*",
"dist/ipynb2html-full.min.js*", "dist/ipynb2html-full.min.js*",
"dist/notebook.min.css*",
"lib", "lib",
"src" "src",
"styles"
], ],
"scripts": { "scripts": {
"build": "ttsc --build", "build": "ttsc --build",
"bundle": "rollup -c", "bundle": "rollup -c",
"clean": "rimraf coverage/ dist/ lib/ .tsbuildinfo", "clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
"lint": "eslint --ext .ts,.tsx,.js .", "lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
"prepublishOnly": "run-p bundle readme2md", "minify-css": "csso styles/notebook.css -o dist/notebook.min.css -s dist/notebook.min.css.map",
"prepublishOnly": "run-p bundle minify-css readme2md",
"test": "jest --detectOpenHandles --coverage --verbose", "test": "jest --detectOpenHandles --coverage --verbose",
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md", "readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
"watch-ts": "ttsc --build --watch" "watch-ts": "ttsc --build --watch"
@ -41,10 +44,15 @@
}, },
"dependencies": { "dependencies": {
"anser": "^1.4.9", "anser": "^1.4.9",
"highlightjs": "^9.12.0", "highlight.js": "^10.7.3",
"ipynb2html-core": "0.1.0-beta.8", "ipynb2html-core": "0.3.0",
"katex": "^0.11.0", "katex": "^0.13.11",
"marked": "^0.7.0" "marked": "^2.0.7"
},
"devDependencies": {
"@types/katex": "^0.11.0",
"@types/marked": "^2.0.3",
"ansi_up": "^5.0.1"
}, },
"peerDependencies": { "peerDependencies": {
"nodom": "^2.3.0" "nodom": "^2.3.0"

View file

@ -1,8 +1,8 @@
import addGitMsg from 'rollup-plugin-add-git-msg' import addGitMsg from 'rollup-plugin-add-git-msg'
import babel from 'rollup-plugin-babel' import babel from '@rollup/plugin-babel'
import commonjs from 'rollup-plugin-commonjs' import commonjs from '@rollup/plugin-commonjs'
import license from 'rollup-plugin-node-license' import license from 'rollup-plugin-node-license'
import resolve from 'rollup-plugin-node-resolve' import resolve from '@rollup/plugin-node-resolve'
import { terser } from 'rollup-plugin-terser' import { terser } from 'rollup-plugin-terser'
import ttypescript from 'ttypescript' import ttypescript from 'ttypescript'
import typescript from 'rollup-plugin-typescript2' import typescript from 'rollup-plugin-typescript2'
@ -13,10 +13,10 @@ import pkg from './package.json'
const extensions = ['.mjs', '.js', '.ts'] const extensions = ['.mjs', '.js', '.ts']
const globals = { const globals = {
anser: 'anser', 'anser': 'anser',
highlightjs: 'hljs', 'highlight.js': 'hljs',
katex: 'katex', 'katex': 'katex',
marked: 'marked', 'marked': 'marked',
} }
const plugins = [ const plugins = [
@ -33,9 +33,6 @@ const plugins = [
incremental: true, incremental: true,
}, },
}, },
// This is needed for node-license plugin. :(
// https://github.com/ezolenko/rollup-plugin-typescript2#plugins-using-asyncawait
objectHashIgnoreUnknownHack: true,
clean: true, clean: true,
}), }),
// Resolve node modules. // Resolve node modules.
@ -48,6 +45,7 @@ const plugins = [
// Transpile all sources for older browsers and inject needed polyfills. // Transpile all sources for older browsers and inject needed polyfills.
babel({ babel({
babelrc: false, babelrc: false,
babelHelpers: 'bundled',
// To avoid Babel injecting core-js polyfills into core-js. // To avoid Babel injecting core-js polyfills into core-js.
exclude: [/node_modules\/core-js\//], exclude: [/node_modules\/core-js\//],
extensions, extensions,
@ -72,22 +70,23 @@ const plugins = [
}), }),
// Generate table of the bundled packages at top of the file. // Generate table of the bundled packages at top of the file.
license({ format: 'table' }), license({ format: 'table' }),
// Minify JS.
terser({
ecma: 5,
include: [/^.+\.min\.js$/],
output: {
// Preserve comment injected by addGitMsg and license.
comments: RegExp(`(?:\\$\\{${pkg.name}\\}|Bundled npm packages)`),
},
}),
] ]
const output = (filename, extra = {}) => ['.js', '.min.js'].map(ext => ({ const output = (filename, extra = {}) => [false, true].map(minify => ({
name: pkg.name, name: pkg.name,
file: `${filename}${ext}`, file: `${filename}${minify ? '.min.js' : '.js'}`,
format: 'umd', format: 'umd',
sourcemap: true, sourcemap: true,
plugins: [
// Minify JS when building .min.js file.
minify && terser({
ecma: 5,
output: {
// Preserve comment injected by addGitMsg and license.
comments: RegExp(`(?:\\$\\{${pkg.name}\\}|Bundled npm packages)`),
},
}),
].filter(Boolean),
...extra, ...extra,
})) }))

View file

@ -7,7 +7,7 @@ function unescapeHTML (input: string): string {
return new DOMParser() return new DOMParser()
.parseFromString(input, 'text/html') .parseFromString(input, 'text/html')
.documentElement .documentElement
.textContent || '' .textContent ?? ''
} }
/** /**
@ -39,6 +39,7 @@ export function autoRender (opts: NbRendererOpts = {}): void {
document.querySelectorAll(selector).forEach(script => { document.querySelectorAll(selector).forEach(script => {
if (script.textContent && script.parentElement) { if (script.textContent && script.parentElement) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const notebook = JSON.parse(unescapeHTML(script.textContent)) const notebook = JSON.parse(unescapeHTML(script.textContent))
const nbElement = render(notebook) const nbElement = render(notebook)
script.parentElement.replaceChild(nbElement, script) script.parentElement.replaceChild(nbElement, script)

View file

@ -2,6 +2,5 @@
// via global variable. It's an alternative to Anser that doesn't provide // via global variable. It's an alternative to Anser that doesn't provide
// bundle for browsers. // bundle for browsers.
declare class AnsiUp { declare class AnsiUp {
// eslint-disable-next-line @typescript-eslint/camelcase
ansi_to_html (input: string): string ansi_to_html (input: string): string
} }

View file

@ -1,19 +1,18 @@
import anser from 'anser' import anser from 'anser'
import hljs from 'highlightjs' import hljs from 'highlight.js'
import katex, { KatexOptions } from 'katex' import katex, { KatexOptions } from 'katex'
import marked, { MarkedOptions } from 'marked' import marked from 'marked'
import { import {
createElementCreator, createElementCreator,
createHtmlRenderer, createHtmlRenderer,
createNbRenderer,
MinimalElement, MinimalElement,
NbRenderer, NbRenderer,
NbRendererOpts as BaseOptions, NbRendererOpts as BaseOptions,
Notebook, Notebook,
} from 'ipynb2html-core' } from 'ipynb2html-core'
import buildMarkdownRenderer from './markdownRenderer' import buildMarkdownRenderer, { MarkedOptions } from './markdownRenderer'
export { default as version } from './version' export { default as version } from './version'
@ -43,7 +42,7 @@ export type NbRendererOpts<TElement = HTMLElement> = BaseOptions<TElement> & {
* for this module's function. * for this module's function.
*/ */
type MinimalDocument<TElement extends MinimalElement> = { type MinimalDocument<TElement extends MinimalElement> = {
createElement (tag: string): TElement, createElement: (tag: string) => TElement,
} }
const defaultKatexOpts: KatexOptions = { const defaultKatexOpts: KatexOptions = {
@ -51,9 +50,14 @@ const defaultKatexOpts: KatexOptions = {
throwOnError: false, throwOnError: false,
} }
const defaultMarkedOpts: MarkedOptions = {
headerAnchors: true,
headerIdsStripAccents: true,
}
function hljsCodeHighlighter (code: string, lang: string): string { function hljsCodeHighlighter (code: string, lang: string): string {
return hljs.getLanguage(lang) return hljs.getLanguage(lang)
? hljs.highlight(lang, code).value ? hljs.highlight(code, { language: lang }).value
: code : code
} }
@ -116,18 +120,15 @@ export function createRenderer <TElement extends MinimalElement> (
codeHighlighter = hljsCodeHighlighter codeHighlighter = hljsCodeHighlighter
} }
if (!markdownRenderer && marked) { if (!markdownRenderer && marked) {
if (katex) { const markedOpts = { ...defaultMarkedOpts, ...opts.markedOpts }
markdownRenderer = buildMarkdownRenderer(opts.markedOpts, katexOpts) markdownRenderer = buildMarkdownRenderer(markedOpts, katexOpts)
} else {
markdownRenderer = (text) => marked.parse(text, opts.markedOpts)
}
} }
if (!dataRenderers['text/html'] && katex) { if (!dataRenderers['text/html'] && katex) {
const mathRenderer = (tex: string) => katex.renderToString(tex, katexOpts) const mathRenderer = (tex: string) => katex.renderToString(tex, katexOpts)
dataRenderers['text/html'] = createHtmlRenderer({ elementCreator, mathRenderer }) dataRenderers['text/html'] = createHtmlRenderer({ elementCreator, mathRenderer })
} }
return createNbRenderer(elementCreator, { return new NbRenderer(elementCreator, {
ansiCodesRenderer, ansiCodesRenderer,
codeHighlighter, codeHighlighter,
dataRenderers, dataRenderers,

View file

@ -1,38 +1,86 @@
import hljs from 'highlightjs' import hljs from 'highlight.js'
import katex, { KatexOptions } from 'katex' import katex, { KatexOptions } from 'katex'
import marked, { MarkedOptions } from 'marked' import marked, { Slugger } from 'marked'
import { mathExtractor } from 'ipynb2html-core' import { mathExtractor } from 'ipynb2html-core'
export type MarkdownRenderer = (markdown: string) => string
export interface MarkedOptions extends marked.MarkedOptions {
/** Generate heading anchors (this implies headingIds). */
headerAnchors?: boolean
headerIdsStripAccents?: boolean
}
// Removes accents from the given string.
const stripAccents = (text: string) => text.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
// Removes math markers from the given string.
const stripMath = (text: string) => mathExtractor.restoreMath(text, []).trim()
class Renderer extends marked.Renderer {
heading (text: string, level: 1 | 2 | 3 | 4 | 5 | 6, raw: string, slugger: Slugger): string {
const opts = this.options as MarkedOptions
if (!opts.headerIds && !opts.headerAnchors) {
return super.heading(text, level, raw, slugger)
}
let id = (opts.headerPrefix ?? '') + slugger.slug(stripMath(raw))
if (opts.headerIdsStripAccents) {
id = stripAccents(id)
}
if (opts.headerAnchors) {
text = `<a class="anchor" href="#${id}" aria-hidden="true"></a>${text}`
}
return `<h${level} id="${id}">${text}</h${level}>`
}
link (href: string | null, title: string | null, text: string): string {
return super.link(href && stripMath(href), title && stripMath(title), text)
}
image (href: string | null, title: string | null, text: string): string {
return super.image(href && stripMath(href), title && stripMath(title), stripMath(text))
}
}
function highlight (code: string, lang: string): string { function highlight (code: string, lang: string): string {
return hljs.getLanguage(lang) return hljs.getLanguage(lang)
? hljs.highlight(lang, code, true).value ? hljs.highlight(code, { language: lang, ignoreIllegals: true }).value
: code : code
} }
const renderer = (markedOpts: MarkedOptions) => (markdown: string) => marked.parse(markdown, markedOpts)
const rendererWithMath = (markedOpts: MarkedOptions, katexOpts: KatexOptions) => (markdown: string) => {
const [text, math] = mathExtractor.extractMath(markdown)
const html = marked.parse(text, markedOpts)
const mathHtml = math.map(({ value, displayMode }) => {
return katex.renderToString(value, { ...katexOpts, displayMode })
})
return mathExtractor.restoreMath(html, mathHtml)
}
/** /**
* Returns a pre-configured marked parser with math support (using KaTeX) * Returns a pre-configured marked parser with (optional) math support (using
* and code highlighter (highlight.js). * KaTeX) and code highlighter (highlight.js).
* *
* @param {MarkedOptions} markedOpts Options for the marked Markdown renderer. * @param {MarkedOptions} markedOpts Options for the marked Markdown renderer.
* @param {KatexOptions} katexOpts Options for the KaTeX math renderer. * @param {KatexOptions} katexOpts Options for the KaTeX math renderer.
*/ */
export default (markedOpts: MarkedOptions = {}, katexOpts: KatexOptions = {}) => { export default (markedOpts: MarkedOptions = {}, katexOpts: KatexOptions = {}): MarkdownRenderer => {
if (hljs) { // highlightjs may be an optional dependency (in browser bundle)
markedOpts = { renderer: new Renderer(markedOpts), ...markedOpts }
if (hljs) { // highlight.js may be an optional dependency (in browser bundle)
markedOpts = { highlight, ...markedOpts } markedOpts = { highlight, ...markedOpts }
} }
/** return katex // katex may be an optional dependency (in browser bundle)
* Converts the given *markdown* into HTML. ? rendererWithMath(markedOpts, katexOpts)
*/ : renderer(markedOpts)
return (markdown: string): string => {
const [text, math] = mathExtractor.extractMath(markdown)
const html = marked.parse(text, markedOpts)
const mathHtml = math.map(({ value, displayMode }) => {
return katex.renderToString(value, { ...katexOpts, displayMode })
})
return mathExtractor.restoreMath(html, mathHtml)
}
} }

View file

@ -3,18 +3,18 @@ import marked from 'marked'
import { Notebook } from 'ipynb2html-core' import { Notebook } from 'ipynb2html-core'
class EmptyRenderer extends marked.Renderer { class EmptyRenderer extends marked.Renderer {}
constructor (options?: marked.MarkedOptions) { // Override all the EmptyRenderer's methods inherited from marked.Renderer to
super(options) // always return an empty string.
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
for (const prop in this) { const RendererProto = marked.Renderer.prototype
if (this[prop] instanceof Function) { for (const prop of Object.getOwnPropertyNames(RendererProto)) {
(this as any)[prop] = () => '' if (prop !== 'constructor' && typeof (RendererProto as any)[prop] === 'function') {
} (EmptyRenderer.prototype as any)[prop] = () => ''
}
} }
} }
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
class MainTitleRenderer extends EmptyRenderer { class MainTitleRenderer extends EmptyRenderer {
_titleFound = false _titleFound = false

View file

@ -1,5 +1,6 @@
import { $INLINE_JSON } from 'ts-transformer-inline-file' import { $INLINE_JSON } from 'ts-transformer-inline-file'
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { version } = $INLINE_JSON('../package.json') const { version } = $INLINE_JSON('../package.json')
export default version as string export default version as string

View file

@ -0,0 +1,86 @@
/* Reference styles for ipynb2html */
.nb-notebook {
line-height: 1.5;
}
.nb-cell + .nb-cell {
margin-top: 1.4rem;
}
.nb-raw-cell {
font-family: monospace;
white-space: pre-wrap;
}
.nb-code-cell {
position: relative;
}
.nb-source::before,
.nb-output::before {
position: absolute;
font-family: monospace;
color: #999;
left: -7.5em;
width: 7em;
text-align: right;
}
.nb-source::before {
content: "In [" attr(data-execution-count) "]:";
}
.nb-output::before {
content: "Out[" attr(data-execution-count) "]:";
}
.nb-source + .nb-output,
.nb-output + .nb-output {
margin-top: 1.4rem;
}
.nb-source > pre {
background-color: #f7f7f7;
border: 1px solid #cfcfcf;
border-radius: 2px;
padding: 0.5em;
margin: 0;
overflow-x: auto;
}
.nb-output {
min-height: 1em;
width: 100%;
}
.nb-output > pre {
padding: 0.5em;
margin: 0;
overflow-x: auto;
}
.nb-output > img {
max-width: 100%;
}
.nb-stdout,
.nb-stderr {
white-space: pre-wrap;
}
.nb-error,
.nb-stderr {
background-color: #fdd;
border-radius: 2px;
}
@media (max-width: 768px) {
.nb-source::before,
.nb-output::before {
display: block;
position: relative;
left: 0;
padding-bottom: 0.5em;
text-align: left;
}
}

1
packages/ipynb2html/test/global.d.ts vendored Symbolic link
View file

@ -0,0 +1 @@
../src/global.d.ts

View file

@ -0,0 +1,152 @@
import { KatexOptions } from 'katex'
import parseHtml from 'node-html-parser'
import markdownRenderer, { MarkedOptions } from '@/markdownRenderer'
import '~/test/setup' // setupFilesAfterEnv doesn't work here
import { Anything } from '~/test/support/matchers/toMatchElement'
let katexOpts: KatexOptions
let markedOpts: MarkedOptions
beforeEach(() => {
katexOpts = {
displayMode: true,
throwOnError: false,
}
markedOpts = {}
})
describe('headings', () => {
it('renders h tag without anchor', () => {
expect( render('## Some Title') ).toHtmlEqual(
<h2 id="some-title">Some Title</h2>
)
})
it('renders math in text, but strips it in id', () => {
expect( render('## Integers $\\mathbb{Z}$') ).toMatchElement(
<h2 id="integers">
Integers <span class="katex"><Anything /></span>
</h2>
)
})
describe('when headerAnchors is true', () => {
beforeEach(() => {
markedOpts = { headerAnchors: true }
})
it('renders h tag with anchor', () => {
expect( render('## Some Title') ).toHtmlEqual(
<h2 id="some-title">
<a class="anchor" href="#some-title" aria-hidden="true"></a>Some Title
</h2>
)
})
it('renders math in text, but strips it in href and id', () => {
expect( render('## Integers $\\mathbb{Z}$') ).toMatchElement(
<h2 id="integers">
<a class="anchor" href="#integers" aria-hidden="true"></a>
Integers <span class="katex"><Anything /></span>
</h2>
)
})
})
describe('when headerIds is false', () => {
beforeEach(() => {
markedOpts = { headerIds: false }
})
it('renders h tag without id and anchor', () => {
expect( render('## Some Title') ).toHtmlEqual(
<h2>Some Title</h2>
)
})
})
describe('when headerIdsStripAccents is true', () => {
beforeEach(() => {
markedOpts = { headerIdsStripAccents: true }
})
it('strips accents in generated id', () => {
expect( render('## Příliš žluťoučký kůň') ).toHtmlEqual(
<h2 id="prilis-zlutoucky-kun">
Příliš žluťoučký kůň
</h2>
)
})
})
})
describe('link', () => {
it('strips math in title', () => {
expect( render('[link](https://example.org "This is $\\TeX$!")') ).toHtmlEqual(
<p><a href="https://example.org" title="This is !">link</a></p>
)
})
it('strips math in href', () => {
expect( render('[link](https://example.org/$\\TeX$)') ).toHtmlEqual(
<p><a href="https://example.org/">link</a></p>
)
})
it('renders math in text', () => {
expect( render('[This is $\\TeX$!](https://example.org/)') ).toMatchElement(
<p><a href="https://example.org/">This is <span class="katex"><Anything /></span>!</a></p>
)
})
})
describe('image', () => {
it('strips math in title', () => {
expect( render('![x](https://example.org/img.png "This is $\\TeX$!")') ).toHtmlEqual(
<p><img src="https://example.org/img.png" alt="x" title="This is !" /></p>
)
})
it('strips math in href', () => {
expect( render('![image](https://example.org/$\\TeX$.png)') ).toHtmlEqual(
<p><img src="https://example.org/.png" alt="image" /></p>
)
})
it('strips math in alt text', () => {
expect( render('![This is $\\TeX$!](https://example.org/img.png)') ).toHtmlEqual(
<p><img src="https://example.org/img.png" alt="This is !" /></p>
)
})
})
describe('code', () => {
it('highlights fenced code block', () => {
// Note: We use `backticks` in this example to avoid problem that JSX
// implicitly decodes HTML entities, but node-html-parser does not.
expect( render('```js\nconsole.log(`Hello, world!`)\n```') ).toHtmlEqual(
<pre>
<code class="language-js">
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Hello, world!`</span>)
</code>
</pre>
)
})
})
function render (text: string) {
const html = markdownRenderer(markedOpts, katexOpts)(text).replace(/\n/, '')
return parseHtml(html, { pre: true }).childNodes[0]
}

View file

@ -1,7 +1,7 @@
import readNotebookTitle from '@/readNotebookTitle'
import { Notebook, CellType, MarkdownCell } from 'ipynb2html-core' import { Notebook, CellType, MarkdownCell } from 'ipynb2html-core'
import readNotebookTitle from '@/readNotebookTitle'
const markdownCell = (source: string | string[]): MarkdownCell => ({ const markdownCell = (source: string | string[]): MarkdownCell => ({
cell_type: CellType.Markdown, cell_type: CellType.Markdown,

View file

@ -1,5 +1,9 @@
// This file is needed only for VSCode, see https://github.com/palmerhq/tsdx/issues/84.
{ {
"extends": "../tsconfig.json", "extends": "../../../tsconfig.test.json",
"include": ["."], "compilerOptions": {
"baseUrl": ".",
},
"references": [
{ "path": "../" },
],
} }

View file

@ -5,11 +5,6 @@
"outDir": "./lib", "outDir": "./lib",
"tsBuildInfoFile": "./.tsbuildinfo", "tsBuildInfoFile": "./.tsbuildinfo",
"baseUrl": ".", "baseUrl": ".",
"paths": {
"*": ["../../types/*"],
"@/*": ["./src/*"],
"~/*": ["../../*"],
},
}, },
"include": [ "include": [
"./src", "./src",

View file

@ -1,30 +0,0 @@
TODO: Remove after https://github.com/oplinjie/rollup-plugin-add-git-msg/pull/1 is merged.
diff --git a/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.es.js b/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.es.js
index 184a00e..fb3fde4 100644
--- a/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.es.js
+++ b/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.es.js
@@ -73,7 +73,8 @@ Banner.prototype.getHeaderArray = function getHeaderArray (opt) {
if (opt.showTag) {
var tag = runGitCommand(TAG_COMMAND);
if (tag) {
- gitMsg = gitMsg.concat(("v" + tag + " - "));
+ var prefix = tag.indexOf("v") === 0 ? "" : "v"
+ gitMsg = gitMsg.concat((prefix + tag + " - "));
}
}
diff --git a/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.js b/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.js
index 7d16072..f288638 100644
--- a/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.js
+++ b/node_modules/rollup-plugin-add-git-msg/dist/rollup-plugin-add-git-msg.js
@@ -77,7 +77,8 @@ Banner.prototype.getHeaderArray = function getHeaderArray (opt) {
if (opt.showTag) {
var tag = runGitCommand(TAG_COMMAND);
if (tag) {
- gitMsg = gitMsg.concat(("v" + tag + " - "));
+ var prefix = tag.indexOf("v") === 0 ? "" : "v"
+ gitMsg = gitMsg.concat((prefix + tag + " - "));
}
}

View file

@ -0,0 +1,15 @@
TODO: Remove after https://github.com/chrisdarroch/yarn-bump/pull/1 is merged and released.
diff --git a/node_modules/yarn-version-bump/src/workspace.js b/node_modules/yarn-version-bump/src/workspace.js
index 28b1dd1..0cf2ecb 100644
--- a/node_modules/yarn-version-bump/src/workspace.js
+++ b/node_modules/yarn-version-bump/src/workspace.js
@@ -9,7 +9,7 @@ class Workspace {
get workspaceSnapshot() {
return runCommand('yarn',
- ['workspaces', 'info', '--silent'],
+ ['--silent', 'workspaces', 'info'],
{ cwd: this.root }
)
.then(data => JSON.parse(data))

View file

@ -1,4 +1,4 @@
#!/bin/sh -e #!/bin/sh -e
( set -o pipefail 2>/dev/null ) && set -o pipefail ( set -o pipefail 2>/dev/null ) && set -o pipefail
asciidoctor -o - -b docbook "$@" | pandoc -f docbook -t markdown_github -o - asciidoctor -o - -b docbook "$@" | pandoc -f docbook -t markdown_github --base-header-level 2 -o -

View file

@ -1,5 +1,5 @@
type Callable = (...args: any[]) => any type Callable = (...args: any[]) => unknown
export type Mock<F extends Callable> = jest.Mock<ReturnType<F>, Parameters<F>> export type Mock<F extends Callable> = jest.Mock<ReturnType<F>, Parameters<F>>
@ -13,9 +13,9 @@ export function asMock <F extends Callable> (fn: F): Mock<F> {
export function mockResults <F extends Callable> (fn: F): Array<ReturnType<F>> { export function mockResults <F extends Callable> (fn: F): Array<ReturnType<F>> {
return asMock(fn).mock.results return asMock(fn).mock.results
.filter(x => x.type === 'return') .filter(x => x.type === 'return')
.map(x => x.value) .map(x => x.value as ReturnType<F>)
} }
export function mockLastResult <F extends Callable> (fn: F): ReturnType<F> { export function mockLastResult <F extends Callable> (fn: F): ReturnType<F> | undefined {
return mockResults(fn).pop() as ReturnType<F> return mockResults(fn).pop()
} }

View file

@ -60,7 +60,8 @@ export function createElement (
} else if (child instanceof NNode) { } else if (child instanceof NNode) {
el.appendChild(child) el.appendChild(child)
} else if (typeof child === 'object' && '__html' in child) { } else if (typeof child === 'object' && '__html' in child) {
el.innerHTML = child.__html // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
el.innerHTML = child.__html as string
} else { } else {
el.appendChild(document.createTextNode(String(child))) el.appendChild(document.createTextNode(String(child)))
} }
@ -68,6 +69,7 @@ export function createElement (
return el return el
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(global as any).JSX = { (global as any).JSX = {
createElement, createElement,
} }

View file

@ -6,8 +6,8 @@ type MatcherResult = jest.CustomMatcherResult
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest { namespace jest {
interface Matchers<R> { interface Matchers<R, T> {
toHtmlEqual (expected: HTMLElement | string | Array<HTMLElement | string>): R, toHtmlEqual: (expected: HTMLElement | string | Array<HTMLElement | string>) => R
} }
} }
} }

View file

@ -10,33 +10,41 @@ type Options = {
declare global { declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest { namespace jest {
interface Matchers<R> { interface Matchers<R, T> {
toMatchElement (expected: HTMLElement, opts?: Options): R, toMatchElement: (expected: HTMLElement, opts?: Options) => R
} }
} }
} }
export const AnythingNode = new class extends Node { export const AnythingNode = new class extends Node {
render () { return '<!--Anything-->' } render () { return '<!--Anything-->' }
toString () { return this.render() }
}() }()
export const Anything = () => AnythingNode export const Anything = (): Node => AnythingNode
function isWritable (obj: any, prop: string): boolean {
const desc = Object.getOwnPropertyDescriptor(obj, prop)
// eslint-disable-next-line @typescript-eslint/unbound-method
return !!desc?.writable || !!desc?.set
}
function filterWildcardChildren (rec: Node, exp: Node): void { function filterWildcardChildren (rec: Node, exp: Node): void {
if (exp.firstChild === AnythingNode if (exp.firstChild === AnythingNode
&& exp.children.length === 1 && exp.childNodes.length === 1
&& rec instanceof HTMLElement && (rec as HTMLElement).innerHTML
&& rec.innerHTML
) { ) {
rec.innerHTML = '' if (isWritable(rec, 'innerHTML')) {
rec.children.splice(0, rec.children.length, AnythingNode) (rec as HTMLElement).innerHTML = ''
}
rec.childNodes.splice(0, rec.childNodes.length, AnythingNode)
return return
} }
for (let i = 0; i < exp.children.length && i < rec.children.length; i++) { for (let i = 0; i < exp.childNodes.length && i < rec.childNodes.length; i++) {
if (exp.children[i] === AnythingNode) { if (exp.childNodes[i] === AnythingNode) {
rec.children[i] = AnythingNode rec.childNodes[i] = AnythingNode
} else { } else {
filterWildcardChildren(exp.children[i], rec.children[i]) filterWildcardChildren(rec.childNodes[i], exp.childNodes[i])
} }
} }
} }
@ -46,13 +54,15 @@ function clearAttributes (node: Node): void {
node.attributes = {} node.attributes = {}
node.className = '' node.className = ''
} }
node.children.forEach(clearAttributes) node.childNodes.forEach(clearAttributes)
} }
export function toMatchElement (received: HTMLElement, expected: HTMLElement, opts?: Options): MatcherResult { export function toMatchElement (received: HTMLElement, expected: HTMLElement, opts?: Options): MatcherResult {
received = received.cloneNode(true) as HTMLElement if (received.cloneNode) {
received = received.cloneNode(true) as HTMLElement
}
if (opts && opts.ignoreAttrs) { if (opts?.ignoreAttrs) {
clearAttributes(received) clearAttributes(received)
} }
filterWildcardChildren(received, expected) filterWildcardChildren(received, expected)

6
test/tsconfig.json Normal file
View file

@ -0,0 +1,6 @@
{
"extends": "../tsconfig.test.json",
"compilerOptions": {
"baseUrl": ".",
},
}

View file

@ -42,7 +42,9 @@
/* Module Resolution Options */ /* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ "paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"*": ["../../types/*"], /* This path is relative from baseUrl which is defined in tsconfig.json that includes this file inside each project. */
},
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": [ /* List of folders to include type definitions from. */ "typeRoots": [ /* List of folders to include type definitions from. */
"./types", "./types",

16
tsconfig.test.json Normal file
View file

@ -0,0 +1,16 @@
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"declaration": false,
"declarationMap": false,
"sourceMap": false,
"composite": false,
"noEmit": true,
"paths": {
// These paths are relative from baseUrl which is defined in
// tsconfig.json that includes this file inside each project.
"@/*": ["../src/*"],
"~/*": ["../../../*"],
},
},
}

5662
yarn.lock

File diff suppressed because it is too large Load diff