Compare commits
169 commits
v0.1.0-bet
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
35c1cd6141 | ||
|
6a4308cb11 | ||
|
a865a53327 | ||
|
e5bcf84b3c | ||
|
3d93c7fdf5 | ||
|
050e4515ca | ||
|
dd783a0daf | ||
|
6c26d381f0 | ||
|
e1ba5039e4 | ||
|
1defdcd4ca | ||
|
ccf16ab7c6 | ||
|
3eee5ebd60 | ||
|
8bd46774dd | ||
|
b7216ab089 | ||
|
a182883320 | ||
|
fd04dc508d | ||
|
953ca11aa8 | ||
|
9d528485ab | ||
|
32c109f1d0 | ||
|
122471a52f | ||
|
fbecff633a | ||
|
5ec93ba9e4 | ||
|
9f43fc595c | ||
|
b363818364 | ||
|
f5a283ba72 | ||
|
ec76650a93 | ||
|
6ca8858d64 | ||
|
6944ff1b82 | ||
|
7fbd5b5d8b | ||
|
a31ddbe203 | ||
|
2f3d7ae4c6 | ||
|
49d16ec079 | ||
|
d29f006090 | ||
|
0b098983b5 | ||
|
5f7c2dfb4b | ||
|
06ab09f278 | ||
|
110c8ca82a | ||
|
d9bea2feb2 | ||
|
76c9a1e67f | ||
|
9685b5d8b8 | ||
|
1d9ce38eb4 | ||
|
e4549711f1 | ||
|
3a312385f6 | ||
|
19b41d9c22 | ||
|
6461e84fe7 | ||
|
cc2da7398e | ||
|
294e9c1f83 | ||
|
20008c9834 | ||
|
381caf644d | ||
|
a901a0ad68 | ||
|
fcdfb50698 | ||
|
ca6e88159e | ||
|
af91c38a02 | ||
|
40011569ab | ||
|
03f89b2196 | ||
|
1f5e0fdab8 | ||
|
e1b817b13b | ||
|
e7402a80ea | ||
|
498112834c | ||
|
e6b9ecf96b | ||
|
ad741eaf48 | ||
|
6eebe39b4d | ||
|
c13cb544cb | ||
|
bbbcd2398e | ||
|
7e35810c7f | ||
|
4c47e54acf | ||
|
dbd6bc8821 | ||
|
51e625dd55 | ||
|
ea852d43f7 | ||
|
47dc53b1a7 | ||
|
a17cbefa51 | ||
|
001219b173 | ||
|
56608c2704 | ||
|
ddef7d0da5 | ||
|
1e0400a160 | ||
|
38048f84de | ||
|
ed62ee01e3 | ||
|
9c452d370c | ||
|
0bf96274f9 | ||
|
28735b24c2 | ||
|
f7e794f0fe | ||
|
063847f0c7 | ||
|
fd5d79319c | ||
|
b7d33396fb | ||
|
7e06fdc664 | ||
|
bc4d2e1d9a | ||
|
d863e8cbaf | ||
|
35b26db42b | ||
|
1b6d3b2524 | ||
|
9e8c3752b0 | ||
|
049703e14c | ||
|
1ccabd4d07 | ||
|
fb539853da | ||
|
712c0dbb0c | ||
|
6c2fa15393 | ||
|
31173df623 | ||
|
93a559493f | ||
|
875e57639e | ||
|
842f652ca5 | ||
|
d658d9b5b7 | ||
|
8427698e21 | ||
|
638666c723 | ||
|
eeee0decc6 | ||
|
74a27b318f | ||
|
b7cbf2230b | ||
|
37ce69b9d1 | ||
|
1a8142f641 | ||
|
68e382d05b | ||
|
0da10ec702 | ||
|
e68d189879 | ||
|
7aadb665b6 | ||
|
1e8620a8e0 | ||
|
0afc6f755b | ||
|
c687f528dc | ||
|
e4f8f436f1 | ||
|
8d6614bccc | ||
|
8c924913cf | ||
|
aab57a8f48 | ||
|
01865d5971 | ||
|
731bd2c619 | ||
|
1a092a2736 | ||
|
4d3777725b | ||
|
3154d20f2b | ||
|
7129670f63 | ||
|
4705564489 | ||
|
36faf95829 | ||
|
d8c18bd2c6 | ||
|
dd4bee7118 | ||
|
58aff83cc5 | ||
|
3c78b609f4 | ||
|
a4aeca48b8 | ||
|
518c327d78 | ||
|
af2ed64899 | ||
|
91e8aafa10 | ||
|
e2edb19bc0 | ||
|
1ae226918c | ||
|
495f17fd87 | ||
|
9e27f0a8ed | ||
|
04a37430d1 | ||
|
9b80667f99 | ||
|
99092cd975 | ||
|
b848a9028a | ||
|
c06d13821c | ||
|
a8c48cc4f2 | ||
|
6e64ab79c6 | ||
|
f84ba06666 | ||
|
cde05b7272 | ||
|
9cabcadd4c | ||
|
0d94c8a2ac | ||
|
3919b16d7d | ||
|
00ea724910 | ||
|
35eb180fe9 | ||
|
b54d86c3a4 | ||
|
ffaec46e59 | ||
|
2e3236ccde | ||
|
393e961221 | ||
|
4e4f16a1ba | ||
|
610a6c4cbe | ||
|
0394738a35 | ||
|
a89fad58cf | ||
|
7b90320c49 | ||
|
48f089c6d7 | ||
|
909d01e732 | ||
|
fd653f08de | ||
|
db98cb4f74 | ||
|
0dd5a9ee53 | ||
|
ec70c7713e | ||
|
782fc305e7 | ||
|
34ab834a6d |
91 changed files with 7278 additions and 2816 deletions
|
@ -1,4 +1,13 @@
|
||||||
|
node_modules/
|
||||||
|
/.*cache/
|
||||||
/.tmp/
|
/.tmp/
|
||||||
|
/coverage/
|
||||||
/dist/
|
/dist/
|
||||||
/lib/
|
/packages/*/bin/
|
||||||
|
/packages/*/coverage/
|
||||||
|
/packages/*/dist/
|
||||||
|
/packages/*/lib/
|
||||||
|
/packages/*/*.js
|
||||||
|
/scripts/*
|
||||||
/types/
|
/types/
|
||||||
|
/*.js
|
||||||
|
|
129
.eslintrc.js
129
.eslintrc.js
|
@ -1,7 +1,18 @@
|
||||||
|
const tsconfigs = [
|
||||||
|
'tsconfig.json',
|
||||||
|
'*/tsconfig.json',
|
||||||
|
'packages/*/tsconfig.json',
|
||||||
|
'packages/*/test/tsconfig.json',
|
||||||
|
]
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
root: true,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: './tsconfig.json',
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
project: tsconfigs,
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
es6: true,
|
es6: true,
|
||||||
|
@ -10,60 +21,78 @@ module.exports = {
|
||||||
settings: {
|
settings: {
|
||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
// Use eslint-import-resolver-typescript to obey "paths" in tsconfig.json.
|
// Use eslint-import-resolver-typescript to obey "paths" in tsconfig.json.
|
||||||
typescript: {},
|
typescript: {
|
||||||
|
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',
|
||||||
'standard-with-typescript',
|
'standard-with-typescript',
|
||||||
'plugin:import/recommended',
|
'plugin:import/recommended',
|
||||||
'plugin:import/typescript',
|
'plugin:import/typescript',
|
||||||
],
|
],
|
||||||
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/newline-after-import': 'warn',
|
'import/newline-after-import': 'warn',
|
||||||
'import/order': ['warn', {
|
// This rule disallows using both wildcard and selective imports from the same module.
|
||||||
'groups': [['builtin', 'external']],
|
'import/no-duplicates': 'off',
|
||||||
'newlines-between': 'always-and-inside-groups',
|
// Some packages have it wrong in type declarations (e.g. katex, marked).
|
||||||
}],
|
'import/no-named-as-default-member': 'off',
|
||||||
|
|
||||||
// TypeScript
|
// TypeScript
|
||||||
'@typescript-eslint/await-thenable': 'error',
|
|
||||||
|
// 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,
|
||||||
|
@ -82,48 +111,84 @@ 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',
|
||||||
'@typescript-eslint/no-floating-promises': 'error',
|
// 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',
|
||||||
'@typescript-eslint/no-object-literal-type-assertion': 'off',
|
// Changed from error to warn.
|
||||||
'@typescript-eslint/no-parameter-properties': 'off',
|
'@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',
|
||||||
'@typescript-eslint/prefer-interface': 'off',
|
// Changed from error to 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/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,
|
||||||
}],
|
}],
|
||||||
'@typescript-eslint/semi': ['error', 'never'],
|
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['*.test.ts'],
|
files: ['*.test.ts?(x)'],
|
||||||
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',
|
||||||
'no-multi-spaces': 'off',
|
'no-multi-spaces': 'off',
|
||||||
'standard/array-bracket-even-spacing': 'off',
|
'standard/array-bracket-even-spacing': 'off',
|
||||||
// Allow spaces inside expect( foo ).
|
// Allow spaces inside expect( foo ).
|
||||||
'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
3
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: jirutka
|
68
.github/workflows/ci.yml
vendored
Normal file
68
.github/workflows/ci.yml
vendored
Normal 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
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,7 +1,13 @@
|
||||||
/coverage/
|
/coverage/
|
||||||
/dist/
|
/dist/
|
||||||
/lib/
|
/packages/*/coverage/
|
||||||
|
/packages/*/dist/
|
||||||
|
/packages/*/lib/
|
||||||
|
/packages/*/README.md
|
||||||
/.*cache/
|
/.*cache/
|
||||||
/.tmp/
|
/.tmp/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.eslintcache
|
||||||
|
.tsbuildinfo
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
*.log
|
*.log
|
||||||
|
|
26
.travis.yml
26
.travis.yml
|
@ -1,26 +0,0 @@
|
||||||
dist: bionic
|
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- '10'
|
|
||||||
- '12'
|
|
||||||
- node
|
|
||||||
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
install:
|
|
||||||
- yarn install
|
|
||||||
|
|
||||||
script:
|
|
||||||
- yarn run build
|
|
||||||
- yarn run test
|
|
||||||
- yarn run lint
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
provider: npm
|
|
||||||
email: jakub@jirutka.cz
|
|
||||||
api_key:
|
|
||||||
secure: "0D1IFNeu0BuCg8SCuNUNXARDARNQbmPCH1qivlIRURaoe5Lns/oTunavJ7eIrvPA/1wMOozWGo6TlVb0ucnWNJ+ux31XTX1BGFoTAUw/dZk3C3s0HT7WuTKLrO6itIQtoBIWMfKAAe5doF00fBDQgFgSwU64CgWsZ2GUNKxXOAfkagInYHU5izGGsS+HQ5FyR5QqQzkHNUiLYs9IA1V3TF2s93ITmcqQZlah/n03BChBEA/LRkMqlEF+1YKaO+4q0PglHYJ1vlN/8n/TQTmAgsm4S67x+ADcBA/3M5UawXPGf+ZyYr1sAgjnE/Rt5BrhApnzrb2m1z1vig1uRPL/bsKADZfSDJVyr+dU4zl73g8U9QtpOMdlOphLWdgWix+JmbUVgtu/iC83zjBBHEfP97NFNJLVkBhnSxoX8xu9+za0jYKlSln5Khoegh1lw9YGx1A/3Z34FxY+G5Fh2cUidrMBrI4F9BJWoiDAnEvvB3yFhZtqbDta1p2iROqwW8/Z5M1CjtyMnemvPi/9oGvlQVaqn7mr45srHIJHjqxdS9HMLDhy7fKr407NvSOl6N7VNxOTW2KfawAkCWqJccEvfRnoaouRhUhdMcDTqiIMl8hYO4jEML6ioJisaLksWXeT9Sv6QoNbPPaWoGjDc9/c0eedfn49U4YMnjCqfIpZAdo=" # 9919......6b7c
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
node_js: '10'
|
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
|
@ -8,6 +8,7 @@
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"gamunu.vscode-yarn",
|
"gamunu.vscode-yarn",
|
||||||
"Orta.vscode-jest",
|
"Orta.vscode-jest",
|
||||||
|
"ryanluker.vscode-coverage-gutters",
|
||||||
"shtian.jest-snippets-standard"
|
"shtian.jest-snippets-standard"
|
||||||
],
|
],
|
||||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
|
|
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
|
@ -10,13 +10,27 @@
|
||||||
|
|
||||||
"files.encoding": "utf8",
|
"files.encoding": "utf8",
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.tsbuildinfo": true,
|
||||||
|
},
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
|
|
||||||
"flow.enabled": false,
|
"flow.enabled": false,
|
||||||
|
|
||||||
|
"javascript.format.insertSpaceAfterConstructor": true,
|
||||||
|
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
|
||||||
|
"javascript.format.semicolons": "remove",
|
||||||
|
"javascript.preferences.quoteStyle": "single",
|
||||||
|
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/node_modules": true,
|
"**/node_modules": true,
|
||||||
"**/dist": true
|
"**/dist": true
|
||||||
}
|
},
|
||||||
|
|
||||||
|
"typescript.format.insertSpaceAfterConstructor": true,
|
||||||
|
"typescript.format.insertSpaceBeforeFunctionParenthesis": true,
|
||||||
|
"typescript.format.semicolons": "remove",
|
||||||
|
"typescript.preferences.quoteStyle": "single",
|
||||||
|
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||||
}
|
}
|
||||||
|
|
2
.yarnrc
Normal file
2
.yarnrc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
version-git-message "Release version %s"
|
||||||
|
version-sign-git-tag true
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||||
|
|
209
README.adoc
209
README.adoc
|
@ -2,37 +2,227 @@
|
||||||
:npm-name: ipynb2html
|
:npm-name: ipynb2html
|
||||||
:gh-name: jirutka/{npm-name}
|
:gh-name: jirutka/{npm-name}
|
||||||
:gh-branch: master
|
:gh-branch: master
|
||||||
|
:version: 0.3.0
|
||||||
|
:ansiup-version: 5.0.1
|
||||||
|
:hljs-version: 10.7.3
|
||||||
|
:katex-version: 0.13.11
|
||||||
|
: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]
|
||||||
image:https://img.shields.io/npm/v/{npm-name}.svg[npm Version, link="https://www.npmjs.org/package/{npm-name}"]
|
|
||||||
endif::env-github[]
|
endif::env-github[]
|
||||||
|
|
||||||
TODO
|
{npm-name} is a converter (renderer) of the https://nbformat.readthedocs.io/en/stable/[Jupyter Notebook Format] 4.0+ to static HTML.
|
||||||
|
It works both in Node.js and browser environment.
|
||||||
|
|
||||||
|
|
||||||
|
== Packages
|
||||||
|
|
||||||
|
This repository contains the following packages, all published on https://www.npmjs.com/[npm].
|
||||||
|
|
||||||
|
|
||||||
|
=== {npm-name}-core
|
||||||
|
|
||||||
|
ifdef::env-github[]
|
||||||
|
image:https://img.shields.io/npm/v/{npm-name}-core.svg[Version on npm, link="https://www.npmjs.org/package/{npm-name}-core"]
|
||||||
|
image:https://img.shields.io/bundlephobia/min/{npm-name}-core.svg[Minified bundle size, link="https://bundlephobia.com/result?p={npm-name}-core"]
|
||||||
|
endif::env-github[]
|
||||||
|
|
||||||
|
This package provides the converter itself and some utilities with *no dependencies*.
|
||||||
|
You have to provide your own syntax highlighter and Markdown, math and ANSI sequences renderer; or not, if you don’t need them.
|
||||||
|
|
||||||
|
|
||||||
|
=== {npm-name}
|
||||||
|
|
||||||
|
ifdef::env-github[]
|
||||||
|
image:https://img.shields.io/npm/v/{npm-name}.svg[Version on npm, link="https://www.npmjs.org/package/{npm-name}"]
|
||||||
|
image:https://img.shields.io/bundlephobia/min/{npm-name}.svg[Minified bundle size, link="https://bundlephobia.com/result?p={npm-name}"]
|
||||||
|
endif::env-github[]
|
||||||
|
|
||||||
|
This package builds on the {npm-name}-core and provides a complete, ready-to-go renderer configured with:
|
||||||
|
|
||||||
|
* https://github.com/markedjs/marked[marked] as Markdown renderer,
|
||||||
|
* https://github.com/KaTeX/KaTeX[KaTeX] as math renderer,
|
||||||
|
* https://github.com/IonicaBizau/anser[anser] as ANSI sequences renderer,
|
||||||
|
* 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
|
||||||
|
|
||||||
|
ifdef::env-github[]
|
||||||
|
image:https://img.shields.io/npm/v/{npm-name}-cli.svg[Version on npm, link="https://www.npmjs.org/package/{npm-name}-cli"]
|
||||||
|
image:https://img.shields.io/bundlephobia/min/{npm-name}-cli.svg[Minified bundle size, link="https://bundlephobia.com/result?p={npm-name}-cli"]
|
||||||
|
endif::env-github[]
|
||||||
|
|
||||||
|
This package provides a CLI interface for {npm-name}.
|
||||||
|
|
||||||
|
|
||||||
|
ifndef::npm-readme[]
|
||||||
|
|
||||||
|
== Installation
|
||||||
|
|
||||||
|
All the <<Packages, packages>> can be installed using `npm` or `yarn` from https://www.npmjs.com/[npmjs.com].
|
||||||
|
|
||||||
|
|
||||||
|
=== Standalone CLI Tool
|
||||||
|
|
||||||
|
{npm-name}-cli is also provided as a single minified JavaScript with all the external dependencies bundled in.
|
||||||
|
It requires only Node.js (version 10 or newer) to be installed on the system.
|
||||||
|
|
||||||
|
* https://github.com/{gh-name}/releases/download/v{version}/{npm-name}-cli-v{version}.tar.gz[{npm-name}-cli-v{version}.tar.gz]
|
||||||
|
* https://github.com/{gh-name}/releases/download/v{version}/{npm-name}-cli-v{version}.zip[{npm-name}-cli-v{version}.zip]
|
||||||
|
|
||||||
|
The archive also contains source maps (useful for debugging).
|
||||||
|
|
||||||
|
endif::[]
|
||||||
|
|
||||||
|
|
||||||
|
== Usage
|
||||||
|
|
||||||
|
=== CLI
|
||||||
|
|
||||||
|
[source, subs="+attributes"]
|
||||||
|
{npm-name} notebook.ipynb notebook.html
|
||||||
|
|
||||||
|
Run `{npm-name} --help` for more information.
|
||||||
|
|
||||||
|
|
||||||
|
=== Node.js (server-side)
|
||||||
|
|
||||||
|
To render HTML in Node.js (server-side rendering), you need some (fake) DOM implementation.
|
||||||
|
The recommended one is https://github.com/redom/nodom/[nodom] -- it’s lightweight, https://bundlephobia.com/result?p=nodom[small], doesn’t have any external dependencies and {npm-name} is tested against it.
|
||||||
|
However, you can choose any other if you like.
|
||||||
|
|
||||||
|
[source, subs="+attributes"]
|
||||||
|
npm install {npm-name} nodom
|
||||||
|
|
||||||
|
[source, js, subs="+attributes"]
|
||||||
|
----
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as ipynb from '{npm-name}'
|
||||||
|
import { Document } from 'nodom'
|
||||||
|
|
||||||
|
const renderNotebook = ipynb.createRenderer(new Document())
|
||||||
|
|
||||||
|
const notebook = JSON.parse(fs.readFileSync('./example.ipynb', 'utf8'))
|
||||||
|
|
||||||
|
console.log(renderNotebook(notebook).outerHTML)
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
=== Browser (client-side)
|
||||||
|
|
||||||
|
You have basically two options how to use {npm-name} in the browser: use the browser bundles provided in the {npm-name} package, or build your own bundle (using e.g. https://rollupjs.org[Rollup] or https://webpack.js.org/[webpack]).
|
||||||
|
|
||||||
|
The provided bundles are in UMD format (AMD, CommonJS and IIFE in one file), so they should work in all environments (old and modern browsers, Node.js).
|
||||||
|
They are transpiled and have injected https://github.com/zloirock/core-js/[core-js] polyfills to be compatible with browsers that have https://browserl.ist/?q=%3E0.5%25%2C+Firefox+ESR%2C+not+dead[>0.5% global coverage, Firefox ESR, and not dead browsers].
|
||||||
|
|
||||||
|
|
||||||
|
==== Full Bundle
|
||||||
|
|
||||||
|
`{npm-name}-full.min.js` is a self-contained bundle with all the external dependencies included (marked, KaTeX, Anser and Highlight.js).
|
||||||
|
|
||||||
|
You can link it from https://www.jsdelivr.com/[jsDelivr CDN], for example:
|
||||||
|
|
||||||
|
[source, html, subs="+attributes"]
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<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/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>
|
||||||
|
</head>
|
||||||
|
...
|
||||||
|
</html>
|
||||||
|
|
||||||
|
The bundle exposes global variable `{npm-name}`:
|
||||||
|
|
||||||
|
[source, js, subs="+attributes"]
|
||||||
|
const element = {npm-name}.render(notebook)
|
||||||
|
document.body.appendChild(element)
|
||||||
|
|
||||||
|
{npm-name} also provides function `autoRender` that renders each notebook on the page embedded (as JSON) inside `<script type="application/x-ipynb+json">\...</script>`.footnote:[Don’t forget to escape HTML special characters: `<`, `>`, and `&`.]
|
||||||
|
|
||||||
|
[source, html, subs="+attributes"]
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<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/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"
|
||||||
|
onload="{npm-name}.autoRender();"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<script type="application/x-ipynb+json">
|
||||||
|
{
|
||||||
|
"cells": [ ... ],
|
||||||
|
"metadata": { ... },
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 3
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
|
||||||
|
==== Slim Bundle
|
||||||
|
|
||||||
|
`{npm-name}.min.js` contains only {npm-name} and {npm-name}-core code (plus polyfills).
|
||||||
|
If you load marked, KaTeX, AnsiUp, and Highlight.js in the page, you will get the same functionality as with `{npm-name}-full.min.js`:
|
||||||
|
|
||||||
|
[source, html, subs="+attributes"]
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<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">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked@{marked-version}/marked.min.js" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/ansi_up@{ansiup-version}/ansi_up.js" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@{hljs-version}/build/highlight.min.js" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/katex@{katex-version}/dist/katex.min.js" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/{npm-name}@{version}/dist/{npm-name}.min.js" crossorigin="anonymous"></script>
|
||||||
|
</head>
|
||||||
|
...
|
||||||
|
</html>
|
||||||
|
|
||||||
|
Or you may use any other implementations and provide them to the `{npm-name}.createRenderer` function.
|
||||||
|
All of them are optional, but you usually need at least a Markdown renderer.
|
||||||
|
|
||||||
|
ifndef::npm-readme[]
|
||||||
|
|
||||||
|
|
||||||
== Development
|
== Development
|
||||||
|
|
||||||
== System Requirements
|
=== System Requirements
|
||||||
|
|
||||||
* https://nodejs.org[NodeJS] 10.13+
|
* https://nodejs.org[NodeJS] 10.13+
|
||||||
* https://yarnpkg.com[yarn] 1.6+ (can be installed using npm)
|
* https://pandoc.org[Pandoc] and https://asciidoctor.org[Asciidoctor] (used only for converting README.adoc to Markdown for npmjs)
|
||||||
|
|
||||||
|
|
||||||
=== Used Tools
|
=== Used Tools
|
||||||
|
|
||||||
* https://www.typescriptlang.org[TypeScript] the language
|
* https://www.typescriptlang.org[TypeScript] the language
|
||||||
|
* https://github.com/cevek/ttypescript[ttypescript] wrapper for `tsc` allowing to use custom AST transformers
|
||||||
* https://yarnpkg.com[yarn] for dependencies management and building
|
* https://yarnpkg.com[yarn] for dependencies management and building
|
||||||
* https://eslint.org[ESLint] for linting JS/TypeScript code
|
* https://eslint.org[ESLint] for linting JS/TypeScript code
|
||||||
* https://jestjs.io[Jest] for testing
|
* https://jestjs.io[Jest] for testing
|
||||||
|
* https://rollupjs.org[Rollup] for building single-file bundles
|
||||||
|
|
||||||
|
|
||||||
=== How to Start
|
=== How to Start
|
||||||
|
|
||||||
. Clone this repository:
|
. Clone this repository:
|
||||||
[source, subs="+attributes"]
|
[source, subs="+attributes"]
|
||||||
git clone git@github.com:{gh-name}.git
|
git clone https://github.com/{gh-name}.git
|
||||||
|
|
||||||
. Install Yarn (if you don’t have it already):
|
. Install Yarn (if you don’t have it already):
|
||||||
[source]
|
[source]
|
||||||
|
@ -46,7 +236,7 @@ yarn install
|
||||||
[source]
|
[source]
|
||||||
yarn build
|
yarn build
|
||||||
|
|
||||||
. Run tests:
|
. Run tests and generate code coverage:
|
||||||
[source]
|
[source]
|
||||||
yarn test
|
yarn test
|
||||||
|
|
||||||
|
@ -54,18 +244,21 @@ yarn test
|
||||||
[source]
|
[source]
|
||||||
yarn lint
|
yarn lint
|
||||||
|
|
||||||
IMPORTANT: Keep in mind that JS sources are located in the directory `src`; directory `lib` contains transpiled code (created after running `yarn build`)!
|
IMPORTANT: Keep in mind that JS sources are located in the `src` directories; `lib` directories contains transpiled code (created after running `yarn build`)!
|
||||||
|
|
||||||
|
|
||||||
=== Visual Studio Code
|
=== Visual Studio Code
|
||||||
|
|
||||||
If you use Visual Studio Code, you should install the following extensions:
|
If you use Visual Studio Code, you should install the following extensions:
|
||||||
|
|
||||||
|
* link:{vs-marketplace-uri}ryanluker.vscode-coverage-gutters[Coverage Gutters]
|
||||||
* link:{vs-marketplace-uri}EditorConfig.EditorConfig[EditorConfig for VS Code]
|
* link:{vs-marketplace-uri}EditorConfig.EditorConfig[EditorConfig for VS Code]
|
||||||
* link:{vs-marketplace-uri}dbaeumer.vscode-eslint[ESLint]
|
* link:{vs-marketplace-uri}dbaeumer.vscode-eslint[ESLint]
|
||||||
* link:{vs-marketplace-uri}Orta.vscode-jest[Jest] (and link:{vs-marketplace-uri}shtian.jest-snippets-standard[Jest Snippets Standard Style])
|
* link:{vs-marketplace-uri}Orta.vscode-jest[Jest] (and link:{vs-marketplace-uri}shtian.jest-snippets-standard[Jest Snippets Standard Style])
|
||||||
* link:{vs-marketplace-uri}gamunu.vscode-yarn[yarn]
|
* link:{vs-marketplace-uri}gamunu.vscode-yarn[yarn]
|
||||||
|
|
||||||
|
endif::[]
|
||||||
|
|
||||||
|
|
||||||
== Credits
|
== Credits
|
||||||
|
|
||||||
|
|
323
examples/bundle-full.html
Normal file
323
examples/bundle-full.html
Normal file
File diff suppressed because one or more lines are too long
327
examples/bundle.html
Normal file
327
examples/bundle.html
Normal file
File diff suppressed because one or more lines are too long
196
jest.config.base.js
Normal file
196
jest.config.base.js
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
// For a detailed explanation regarding each configuration property, visit:
|
||||||
|
// https://jestjs.io/docs/en/configuration.html
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// All imported modules in your tests should be mocked automatically
|
||||||
|
// automock: false,
|
||||||
|
|
||||||
|
// Stop running tests after `n` failures
|
||||||
|
// bail: 0,
|
||||||
|
|
||||||
|
// Respect "browser" field in package.json when resolving modules
|
||||||
|
// browser: false,
|
||||||
|
|
||||||
|
// The directory where Jest should store its cached dependency information
|
||||||
|
// cacheDirectory: '/tmp/jest_rt',
|
||||||
|
|
||||||
|
// Automatically clear mock calls and instances between every test
|
||||||
|
clearMocks: true,
|
||||||
|
|
||||||
|
// Indicates whether the coverage information should be collected while executing the test
|
||||||
|
// collectCoverage: false,
|
||||||
|
|
||||||
|
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'<rootDir>/src/**/*.ts',
|
||||||
|
],
|
||||||
|
|
||||||
|
// The directory where Jest should output its coverage files
|
||||||
|
coverageDirectory: '<rootDir>/coverage',
|
||||||
|
|
||||||
|
// An array of regexp pattern strings used to skip coverage collection
|
||||||
|
// coveragePathIgnorePatterns: [
|
||||||
|
// '/node_modules/'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// A list of reporter names that Jest uses when writing coverage reports
|
||||||
|
// coverageReporters: [
|
||||||
|
// 'json',
|
||||||
|
// 'text',
|
||||||
|
// 'lcov',
|
||||||
|
// 'clover'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
|
// coverageThreshold: null,
|
||||||
|
|
||||||
|
// A path to a custom dependency extractor
|
||||||
|
// dependencyExtractor: null,
|
||||||
|
|
||||||
|
// Make calling deprecated APIs throw helpful error messages
|
||||||
|
// errorOnDeprecated: false,
|
||||||
|
|
||||||
|
// Force coverage collection from ignored files using an array of glob patterns
|
||||||
|
// forceCoverageMatch: [],
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once before all test suites
|
||||||
|
// globalSetup: null,
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once after all test suites
|
||||||
|
// globalTeardown: null,
|
||||||
|
|
||||||
|
// A set of global variables that need to be available in all test environments
|
||||||
|
globals: {
|
||||||
|
'ts-jest': {
|
||||||
|
compiler: 'ttypescript',
|
||||||
|
tsConfig: '<rootDir>/test/tsconfig.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// An array of directory names to be searched recursively up from the requiring module's location
|
||||||
|
// moduleDirectories: [
|
||||||
|
// 'node_modules'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of file extensions your modules use
|
||||||
|
// moduleFileExtensions: [
|
||||||
|
// 'js',
|
||||||
|
// 'json',
|
||||||
|
// 'jsx',
|
||||||
|
// 'ts',
|
||||||
|
// 'tsx',
|
||||||
|
// 'node'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// A map from regular expressions to module names that allow to stub out resources with a single module
|
||||||
|
moduleNameMapper: {
|
||||||
|
'@/(.*)': '<rootDir>/src/$1',
|
||||||
|
'~/(.*)': `${__dirname}/$1`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
|
// modulePathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Activates notifications for test results
|
||||||
|
// notify: false,
|
||||||
|
|
||||||
|
// An enum that specifies notification mode. Requires { notify: true }
|
||||||
|
// notifyMode: "failure-change",
|
||||||
|
|
||||||
|
// A preset that is used as a base for Jest's configuration
|
||||||
|
preset: 'ts-jest',
|
||||||
|
|
||||||
|
// Run tests from one or more projects
|
||||||
|
// projects: null,
|
||||||
|
|
||||||
|
// Use this configuration option to add custom reporters to Jest
|
||||||
|
// reporters: undefined,
|
||||||
|
|
||||||
|
// Automatically reset mock state between every test
|
||||||
|
// resetMocks: false,
|
||||||
|
|
||||||
|
// Reset the module registry before running each individual test
|
||||||
|
// resetModules: false,
|
||||||
|
|
||||||
|
// A path to a custom resolver
|
||||||
|
// resolver: null,
|
||||||
|
|
||||||
|
// Automatically restore mock state between every test
|
||||||
|
// restoreMocks: false,
|
||||||
|
|
||||||
|
// The root directory that Jest should scan for tests and modules within
|
||||||
|
// rootDir: null,
|
||||||
|
|
||||||
|
// A list of paths to directories that Jest should use to search for files in
|
||||||
|
// roots: [
|
||||||
|
// '<rootDir>'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Allows you to use a custom runner instead of Jest's default test runner
|
||||||
|
// runner: 'jest-runner',
|
||||||
|
|
||||||
|
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||||
|
// setupFiles: [],
|
||||||
|
|
||||||
|
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||||
|
setupFilesAfterEnv: [
|
||||||
|
`${__dirname}/test/setup.ts`,
|
||||||
|
],
|
||||||
|
|
||||||
|
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||||
|
// snapshotSerializers: [],
|
||||||
|
|
||||||
|
// The test environment that will be used for testing
|
||||||
|
testEnvironment: 'node',
|
||||||
|
|
||||||
|
// Options that will be passed to the testEnvironment
|
||||||
|
// testEnvironmentOptions: {},
|
||||||
|
|
||||||
|
// Adds a location field to test results
|
||||||
|
// testLocationInResults: false,
|
||||||
|
|
||||||
|
// The glob patterns Jest uses to detect test files
|
||||||
|
testMatch: [
|
||||||
|
'<rootDir>/test/**/*.test.[jt]s?(x)',
|
||||||
|
],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||||
|
// testPathIgnorePatterns: [
|
||||||
|
// '/node_modules/'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||||
|
// testRegex: [],
|
||||||
|
|
||||||
|
// This option allows the use of a custom results processor
|
||||||
|
// testResultsProcessor: null,
|
||||||
|
|
||||||
|
// This option allows use of a custom test runner
|
||||||
|
// testRunner: 'jasmine2',
|
||||||
|
|
||||||
|
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||||
|
// testURL: 'http://localhost',
|
||||||
|
|
||||||
|
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||||
|
// timers: 'real',
|
||||||
|
|
||||||
|
// A map from regular expressions to paths to transformers
|
||||||
|
// transform: null,
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||||
|
// transformIgnorePatterns: [
|
||||||
|
// '/node_modules/'
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||||
|
// unmockedModulePathPatterns: undefined,
|
||||||
|
|
||||||
|
// Indicates whether each individual test should be reported during the run
|
||||||
|
// verbose: null,
|
||||||
|
|
||||||
|
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||||
|
// watchPathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Whether to use watchman for file crawling
|
||||||
|
// watchman: true,
|
||||||
|
}
|
186
jest.config.js
186
jest.config.js
|
@ -2,189 +2,11 @@
|
||||||
// https://jestjs.io/docs/en/configuration.html
|
// https://jestjs.io/docs/en/configuration.html
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// All imported modules in your tests should be mocked automatically
|
...require('./jest.config.base'),
|
||||||
// automock: false,
|
|
||||||
|
|
||||||
// Stop running tests after `n` failures
|
|
||||||
// bail: 0,
|
|
||||||
|
|
||||||
// Respect "browser" field in package.json when resolving modules
|
|
||||||
// browser: false,
|
|
||||||
|
|
||||||
// The directory where Jest should store its cached dependency information
|
|
||||||
// cacheDirectory: '/tmp/jest_rt',
|
|
||||||
|
|
||||||
// Automatically clear mock calls and instances between every test
|
|
||||||
clearMocks: true,
|
|
||||||
|
|
||||||
// Indicates whether the coverage information should be collected while executing the test
|
|
||||||
// collectCoverage: false,
|
|
||||||
|
|
||||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
|
||||||
collectCoverageFrom: [
|
|
||||||
'<rootDir>/src/**/*.ts',
|
|
||||||
],
|
|
||||||
|
|
||||||
// The directory where Jest should output its coverage files
|
|
||||||
coverageDirectory: 'coverage',
|
|
||||||
|
|
||||||
// An array of regexp pattern strings used to skip coverage collection
|
|
||||||
// coveragePathIgnorePatterns: [
|
|
||||||
// '/node_modules/'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// A list of reporter names that Jest uses when writing coverage reports
|
|
||||||
// coverageReporters: [
|
|
||||||
// 'json',
|
|
||||||
// 'text',
|
|
||||||
// 'lcov',
|
|
||||||
// 'clover'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An object that configures minimum threshold enforcement for coverage results
|
|
||||||
// coverageThreshold: null,
|
|
||||||
|
|
||||||
// A path to a custom dependency extractor
|
|
||||||
// dependencyExtractor: null,
|
|
||||||
|
|
||||||
// Make calling deprecated APIs throw helpful error messages
|
|
||||||
// errorOnDeprecated: false,
|
|
||||||
|
|
||||||
// Force coverage collection from ignored files using an array of glob patterns
|
|
||||||
// forceCoverageMatch: [],
|
|
||||||
|
|
||||||
// A path to a module which exports an async function that is triggered once before all test suites
|
|
||||||
// globalSetup: null,
|
|
||||||
|
|
||||||
// A path to a module which exports an async function that is triggered once after all test suites
|
|
||||||
// globalTeardown: null,
|
|
||||||
|
|
||||||
// A set of global variables that need to be available in all test environments
|
|
||||||
// globals: {},
|
|
||||||
|
|
||||||
// An array of directory names to be searched recursively up from the requiring module's location
|
|
||||||
// moduleDirectories: [
|
|
||||||
// 'node_modules'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An array of file extensions your modules use
|
|
||||||
// moduleFileExtensions: [
|
|
||||||
// 'js',
|
|
||||||
// 'json',
|
|
||||||
// 'jsx',
|
|
||||||
// 'ts',
|
|
||||||
// 'tsx',
|
|
||||||
// 'node'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// A map from regular expressions to module names that allow to stub out resources with a single module
|
|
||||||
moduleNameMapper: {
|
|
||||||
'@/(.*)': '<rootDir>/src/$1',
|
|
||||||
},
|
|
||||||
|
|
||||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
|
||||||
// modulePathIgnorePatterns: [],
|
|
||||||
|
|
||||||
// Activates notifications for test results
|
|
||||||
// notify: false,
|
|
||||||
|
|
||||||
// An enum that specifies notification mode. Requires { notify: true }
|
|
||||||
// notifyMode: "failure-change",
|
|
||||||
|
|
||||||
// A preset that is used as a base for Jest's configuration
|
|
||||||
preset: 'ts-jest',
|
|
||||||
|
|
||||||
// Run tests from one or more projects
|
// Run tests from one or more projects
|
||||||
// projects: null,
|
projects: [
|
||||||
|
'<rootDir>',
|
||||||
// Use this configuration option to add custom reporters to Jest
|
'<rootDir>/packages/*/',
|
||||||
// reporters: undefined,
|
|
||||||
|
|
||||||
// Automatically reset mock state between every test
|
|
||||||
// resetMocks: false,
|
|
||||||
|
|
||||||
// Reset the module registry before running each individual test
|
|
||||||
// resetModules: false,
|
|
||||||
|
|
||||||
// A path to a custom resolver
|
|
||||||
// resolver: null,
|
|
||||||
|
|
||||||
// Automatically restore mock state between every test
|
|
||||||
// restoreMocks: false,
|
|
||||||
|
|
||||||
// The root directory that Jest should scan for tests and modules within
|
|
||||||
// rootDir: null,
|
|
||||||
|
|
||||||
// A list of paths to directories that Jest should use to search for files in
|
|
||||||
// roots: [
|
|
||||||
// '<rootDir>'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// Allows you to use a custom runner instead of Jest's default test runner
|
|
||||||
// runner: 'jest-runner',
|
|
||||||
|
|
||||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
|
||||||
// setupFiles: [],
|
|
||||||
|
|
||||||
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
|
||||||
setupFilesAfterEnv: [
|
|
||||||
'<rootDir>/test/setup.ts',
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
|
||||||
// snapshotSerializers: [],
|
|
||||||
|
|
||||||
// The test environment that will be used for testing
|
|
||||||
testEnvironment: 'node',
|
|
||||||
|
|
||||||
// Options that will be passed to the testEnvironment
|
|
||||||
// testEnvironmentOptions: {},
|
|
||||||
|
|
||||||
// Adds a location field to test results
|
|
||||||
// testLocationInResults: false,
|
|
||||||
|
|
||||||
// The glob patterns Jest uses to detect test files
|
|
||||||
testMatch: [
|
|
||||||
'<rootDir>/test/**/*.test.[jt]s?(x)',
|
|
||||||
],
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
|
||||||
// testPathIgnorePatterns: [
|
|
||||||
// '/node_modules/'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// The regexp pattern or array of patterns that Jest uses to detect test files
|
|
||||||
// testRegex: [],
|
|
||||||
|
|
||||||
// This option allows the use of a custom results processor
|
|
||||||
// testResultsProcessor: null,
|
|
||||||
|
|
||||||
// This option allows use of a custom test runner
|
|
||||||
// testRunner: 'jasmine2',
|
|
||||||
|
|
||||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
|
||||||
// testURL: 'http://localhost',
|
|
||||||
|
|
||||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
|
||||||
// timers: 'real',
|
|
||||||
|
|
||||||
// A map from regular expressions to paths to transformers
|
|
||||||
// transform: null,
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
|
||||||
// transformIgnorePatterns: [
|
|
||||||
// '/node_modules/'
|
|
||||||
// ],
|
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
|
||||||
// unmockedModulePathPatterns: undefined,
|
|
||||||
|
|
||||||
// Indicates whether each individual test should be reported during the run
|
|
||||||
// verbose: null,
|
|
||||||
|
|
||||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
|
||||||
// watchPathIgnorePatterns: [],
|
|
||||||
|
|
||||||
// Whether to use watchman for file crawling
|
|
||||||
// watchman: true,
|
|
||||||
}
|
}
|
||||||
|
|
108
package.json
108
package.json
|
@ -1,66 +1,72 @@
|
||||||
{
|
{
|
||||||
"name": "ipynb2html",
|
"name": "ipynb2html-parent",
|
||||||
"version": "0.1.0-beta.4",
|
"version": "0.3.0",
|
||||||
"description": "Convert Jupyter Notebook to static HTML",
|
"private": true,
|
||||||
"author": "Jakub Jirutka <jakub@jirutka.cz>",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": "https://github.com/jirutka/ipynb2html",
|
|
||||||
"bugs": "https://github.com/jirutka/ipynb2html/issues",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/jirutka/ipynb2html.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"converter",
|
|
||||||
"html",
|
|
||||||
"ipython",
|
|
||||||
"jupyter",
|
|
||||||
"notebook"
|
|
||||||
],
|
|
||||||
"main": "lib/index.js",
|
|
||||||
"files": [
|
|
||||||
"lib",
|
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "ttsc --build",
|
||||||
"lint": "eslint 'src/**/*.[jt]s'",
|
"bundle": "wsrun --exclude-missing bundle",
|
||||||
|
"clean": "rimraf coverage/ dist/ lib/ .eslintcache *.log && wsrun clean",
|
||||||
|
"lint": "eslint --cache --ext .ts,.tsx,.js .",
|
||||||
|
"postinstall": "patch-package && run-s build",
|
||||||
|
"publish-all": "wsrun --serial publish",
|
||||||
"test": "jest --detectOpenHandles --coverage --verbose",
|
"test": "jest --detectOpenHandles --coverage --verbose",
|
||||||
"watch-ts": "tsc -w"
|
"version": "./scripts/bump-version && git add README.adoc **/package.json",
|
||||||
|
"watch-ts": "ttsc --build --watch"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"anser": "^1.4.8",
|
|
||||||
"highlightjs": "^9.12.0",
|
|
||||||
"katex": "^0.11.0",
|
|
||||||
"marked": "^0.7.0",
|
|
||||||
"nodom": "^2.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.10.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": "^1.13.0",
|
|
||||||
"@typescript-eslint/parser": "^1.13.0",
|
|
||||||
"arrify": "^2.0.1",
|
"arrify": "^2.0.1",
|
||||||
|
"common-path-prefix": "^3.0.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"csso-cli": "^3.0.0",
|
||||||
"dedent": "^0.7.0",
|
"dedent": "^0.7.0",
|
||||||
"eslint": "^6.2.2",
|
"eslint": "^7.3.0",
|
||||||
"eslint-config-standard-with-typescript": "^8.0.0",
|
"eslint-config-standard-with-typescript": "^18.0.2",
|
||||||
"eslint-import-resolver-typescript": "^1.1.1",
|
"eslint-import-resolver-typescript": "^2.0.0",
|
||||||
"eslint-plugin-import": "^2.18.0",
|
"eslint-plugin-import": "^2.21.2",
|
||||||
"eslint-plugin-node": "^9.1.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",
|
||||||
"jest": "^24.9.0",
|
"fs-extra": "^8.1.0",
|
||||||
"jest-chain": "^1.1.2",
|
"jest": "^25.5.4",
|
||||||
|
"jest-chain": "^1.1.5",
|
||||||
|
"node-html-parser": "^1.2.19",
|
||||||
|
"nodom": "^2.3.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"ts-jest": "^24.1.0",
|
"patch-package": "^6.2.2",
|
||||||
"ts-node": "^8.3.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"typescript": "~3.5.2"
|
"rimraf": "^3.0.2",
|
||||||
}
|
"rollup": "^2.17.1",
|
||||||
|
"rollup-plugin-add-git-msg": "^1.1.0",
|
||||||
|
"rollup-plugin-executable": "^1.6.0",
|
||||||
|
"rollup-plugin-node-externals": "^2.2.0",
|
||||||
|
"rollup-plugin-node-license": "^0.2.0",
|
||||||
|
"rollup-plugin-terser": "^6.1.0",
|
||||||
|
"rollup-plugin-typescript2": "^0.27.1",
|
||||||
|
"tar": "^5.0.10",
|
||||||
|
"ts-jest": "^25.5.1",
|
||||||
|
"ts-node": "^8.10.2",
|
||||||
|
"ts-transformer-export-default-name": "^0.1.0",
|
||||||
|
"ts-transformer-inline-file": "^0.1.1",
|
||||||
|
"ttypescript": "^1.5.10",
|
||||||
|
"typescript": "~3.9.5",
|
||||||
|
"wsrun": "^5.2.1",
|
||||||
|
"yarn-version-bump": "^0.0.3",
|
||||||
|
"yazl": "^2.5.1"
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
2
packages/ipynb2html-cli/bin/ipynb2html
Executable file
2
packages/ipynb2html-cli/bin/ipynb2html
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
require('../lib/index').default(process.argv.slice(2))
|
54
packages/ipynb2html-cli/package.json
Normal file
54
packages/ipynb2html-cli/package.json
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"name": "ipynb2html-cli",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "CLI tool for converting Jupyter Notebooks to static HTML",
|
||||||
|
"author": "Jakub Jirutka <jakub@jirutka.cz>",
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/jirutka/ipynb2html",
|
||||||
|
"bugs": "https://github.com/jirutka/ipynb2html/issues",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/jirutka/ipynb2html.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"cli",
|
||||||
|
"converter",
|
||||||
|
"html",
|
||||||
|
"ipython",
|
||||||
|
"jupyter",
|
||||||
|
"notebook"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"ipynb2html": "bin/ipynb2html"
|
||||||
|
},
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"lib",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "ttsc --build",
|
||||||
|
"bundle": "rollup -c && ./scripts/pack-bundle",
|
||||||
|
"clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
|
||||||
|
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
||||||
|
"prepublishOnly": "run-s readme2md",
|
||||||
|
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
||||||
|
"watch-ts": "ttsc --build --watch"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ipynb2html": "0.3.0",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"minimist-options": "^4.0.2",
|
||||||
|
"nodom": "^2.3.0",
|
||||||
|
"source-map-support": "^0.5.16"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/minimist": "^1.2.0",
|
||||||
|
"@types/source-map-support": "^0.5.1"
|
||||||
|
}
|
||||||
|
}
|
67
packages/ipynb2html-cli/rollup.config.js
Normal file
67
packages/ipynb2html-cli/rollup.config.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import addGitMsg from 'rollup-plugin-add-git-msg'
|
||||||
|
import commonjs from '@rollup/plugin-commonjs'
|
||||||
|
import executable from 'rollup-plugin-executable'
|
||||||
|
import externals from 'rollup-plugin-node-externals'
|
||||||
|
import license from 'rollup-plugin-node-license'
|
||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import { terser } from 'rollup-plugin-terser'
|
||||||
|
import typescript from 'rollup-plugin-typescript2'
|
||||||
|
import ttypescript from 'ttypescript'
|
||||||
|
|
||||||
|
import pkg from './package.json'
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.ts',
|
||||||
|
plugins: [
|
||||||
|
// Transpile TypeScript sources to JS.
|
||||||
|
typescript({
|
||||||
|
typescript: ttypescript,
|
||||||
|
tsconfigOverride: {
|
||||||
|
compilerOptions: {
|
||||||
|
module: 'ESNext',
|
||||||
|
declaration: false,
|
||||||
|
declarationMap: false,
|
||||||
|
composite: false,
|
||||||
|
incremental: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clean: true,
|
||||||
|
}),
|
||||||
|
// Make node builtins external.
|
||||||
|
externals(),
|
||||||
|
// Resolve node modules.
|
||||||
|
resolve({
|
||||||
|
extensions: ['.mjs', '.js', '.ts'],
|
||||||
|
mainFields: ['jsnext:main', 'module', 'main'],
|
||||||
|
}),
|
||||||
|
// Convert CommonJS modules to ES6 modules.
|
||||||
|
commonjs(),
|
||||||
|
// Add git tag, commit SHA and build date at top of the file.
|
||||||
|
addGitMsg({
|
||||||
|
copyright: [
|
||||||
|
pkg.author,
|
||||||
|
'* This project is licensed under the terms of the MIT license.'
|
||||||
|
].join('\n'),
|
||||||
|
}),
|
||||||
|
// Generate table of the bundled packages at top of the file.
|
||||||
|
license({ format: 'table' }),
|
||||||
|
// Minify JS.
|
||||||
|
terser({
|
||||||
|
ecma: 2018,
|
||||||
|
output: {
|
||||||
|
// Preserve comment injected by addGitMsg and license.
|
||||||
|
comments: RegExp(`(?:\\$\\{${pkg.name}\\}|Bundled npm packages)`),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// Make the output file executable.
|
||||||
|
executable(),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
file: 'dist/ipynb2html',
|
||||||
|
format: 'cjs',
|
||||||
|
banner: '#!/usr/bin/env node',
|
||||||
|
exports: 'named',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
}
|
8
packages/ipynb2html-cli/scripts/pack-bundle
Executable file
8
packages/ipynb2html-cli/scripts/pack-bundle
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/../dist"
|
||||||
|
export PATH="$(pwd)/../../../scripts:$PATH"
|
||||||
|
|
||||||
|
assemble-license ipynb2html > LICENSE
|
||||||
|
create-archives ipynb2html-cli ipynb2html ipynb2html.map LICENSE
|
134
packages/ipynb2html-cli/src/cli.ts
Normal file
134
packages/ipynb2html-cli/src/cli.ts
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import minimist from 'minimist'
|
||||||
|
import minimistOptions from 'minimist-options'
|
||||||
|
import { Document } from 'nodom'
|
||||||
|
import { exit } from 'process'
|
||||||
|
import { $INLINE_FILE, $INLINE_JSON } from 'ts-transformer-inline-file'
|
||||||
|
|
||||||
|
import * as ipynb2html from 'ipynb2html'
|
||||||
|
|
||||||
|
import renderPage from './page'
|
||||||
|
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
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 helpMsg = `\
|
||||||
|
Usage: ${progName} [options] <input> [<output>]
|
||||||
|
|
||||||
|
Convert Jupyter Notebook 4.0+ to a static HTML page.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
<input> Path of the Jupyter notebook to read, or "-" for STDIN.
|
||||||
|
<output> Path of the file to write the output HTML into. If not
|
||||||
|
provided, the output will be written to STDOUT.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-d --debug Print debug messages.
|
||||||
|
|
||||||
|
-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:
|
||||||
|
1 Generic error code.
|
||||||
|
2 Missing required arguments or invalid option.
|
||||||
|
|
||||||
|
Please report bugs at <${bugsUrl}>.
|
||||||
|
`
|
||||||
|
|
||||||
|
function logErr (msg: string): void {
|
||||||
|
console.error(`${progName}: ${msg}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrify <T> (obj: T | T[]): T[] {
|
||||||
|
return Array.isArray(obj) ? obj : [obj]
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCliArgs (argv: string[]) {
|
||||||
|
const opts = minimist(argv, minimistOptions({
|
||||||
|
debug: { alias: 'd', type: 'boolean' },
|
||||||
|
style: { alias: 's', type: 'string', default: '@default' },
|
||||||
|
version: { alias: 'V', type: 'boolean' },
|
||||||
|
help: { alias: 'h', type: 'boolean' },
|
||||||
|
arguments: 'string',
|
||||||
|
stopEarly: true,
|
||||||
|
unknown: (arg: string) => {
|
||||||
|
if (arg.startsWith('-')) {
|
||||||
|
logErr(`Unknown option: ${arg}`)
|
||||||
|
return exit(2)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (opts.help) {
|
||||||
|
console.log(helpMsg)
|
||||||
|
return exit(0)
|
||||||
|
}
|
||||||
|
if (opts.version) {
|
||||||
|
console.log(`${progName} ${version}`)
|
||||||
|
return exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts._.length < 1 || opts._.length > 2) {
|
||||||
|
logErr('Invalid number of arguments\n')
|
||||||
|
console.log(helpMsg)
|
||||||
|
return exit(2)
|
||||||
|
}
|
||||||
|
const [input, output] = opts._
|
||||||
|
|
||||||
|
return {
|
||||||
|
styles: arrify(opts.style).join(',').split(/,\s*/),
|
||||||
|
debug: opts.debug as boolean,
|
||||||
|
input: input === '-' ? 0 : input, // 0 = stdin
|
||||||
|
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 => {
|
||||||
|
const opts = parseCliArgs(argv)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
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 renderNotebook = ipynb2html.createRenderer(new Document())
|
||||||
|
const contents = renderNotebook(notebook).outerHTML
|
||||||
|
|
||||||
|
const html = renderPage({ contents, title, style })
|
||||||
|
|
||||||
|
if (opts.output) {
|
||||||
|
fs.writeFileSync(opts.output, html)
|
||||||
|
} else {
|
||||||
|
console.log(html)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (opts.debug) {
|
||||||
|
console.debug(err)
|
||||||
|
} else {
|
||||||
|
logErr((err as Error).message)
|
||||||
|
}
|
||||||
|
return exit(1)
|
||||||
|
}
|
||||||
|
}
|
15
packages/ipynb2html-cli/src/index.ts
Normal file
15
packages/ipynb2html-cli/src/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import sourceMapSupport from 'source-map-support'
|
||||||
|
|
||||||
|
import cli from './cli'
|
||||||
|
|
||||||
|
// Allow to disable sourcemap when running from pkg bundle.
|
||||||
|
if (!/^(0|disable|false|no|off)$/i.test(process.env.NODE_SOURCEMAP ?? '')) {
|
||||||
|
sourceMapSupport.install({ environment: 'node' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file is run directly (not required as a module), call CLI.
|
||||||
|
if (require.main === module) {
|
||||||
|
cli(process.argv.slice(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default cli
|
95
packages/ipynb2html-cli/src/page.css
Normal file
95
packages/ipynb2html-cli/src/page.css
Normal 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%;
|
||||||
|
}
|
||||||
|
}
|
34
packages/ipynb2html-cli/src/page.ts
Normal file
34
packages/ipynb2html-cli/src/page.ts
Normal 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>
|
||||||
|
`
|
16
packages/ipynb2html-cli/tsconfig.json
Normal file
16
packages/ipynb2html-cli/tsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./lib",
|
||||||
|
"tsBuildInfoFile": "./.tsbuildinfo",
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src",
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../ipynb2html-core" },
|
||||||
|
{ "path": "../ipynb2html" },
|
||||||
|
],
|
||||||
|
}
|
7
packages/ipynb2html-core/jest.config.js
Normal file
7
packages/ipynb2html-core/jest.config.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const pkg = require('./package.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...require('../../jest.config.base'),
|
||||||
|
name: pkg.name,
|
||||||
|
displayName: pkg.name,
|
||||||
|
}
|
38
packages/ipynb2html-core/package.json
Normal file
38
packages/ipynb2html-core/package.json
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "ipynb2html-core",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "Convert Jupyter Notebook to static HTML",
|
||||||
|
"author": "Jakub Jirutka <jakub@jirutka.cz>",
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/jirutka/ipynb2html",
|
||||||
|
"bugs": "https://github.com/jirutka/ipynb2html/issues",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/jirutka/ipynb2html.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"converter",
|
||||||
|
"html",
|
||||||
|
"ipython",
|
||||||
|
"jupyter",
|
||||||
|
"notebook"
|
||||||
|
],
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "ttsc --build",
|
||||||
|
"clean": "rimraf coverage/ lib/ .eslintcache .tsbuildinfo",
|
||||||
|
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
||||||
|
"prepublishOnly": "run-s readme2md",
|
||||||
|
"test": "jest --detectOpenHandles --coverage --verbose",
|
||||||
|
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
||||||
|
"watch-ts": "ttsc --build --watch"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
}
|
53
packages/ipynb2html-core/src/elementCreator.ts
Normal file
53
packages/ipynb2html-core/src/elementCreator.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
type Attributes = { [k: string]: string }
|
||||||
|
|
||||||
|
// Definition of the smallest possible subset of the HTMLElement type required
|
||||||
|
// for this module's function.
|
||||||
|
export type MinimalElement = {
|
||||||
|
innerHTML: string,
|
||||||
|
setAttribute: (name: string, value: string) => void,
|
||||||
|
appendChild: (child: any) => unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ElementCreator<TElement = HTMLElement> =
|
||||||
|
& ((tag: string, classes?: string[], children?: TElement[] | string) => TElement)
|
||||||
|
& ((tag: string, attrs?: Attributes, children?: TElement[] | string) => TElement)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a function for building `HTMLElement`s.
|
||||||
|
*
|
||||||
|
* @param {function} createElement A function that creates a new `HTMLElement`
|
||||||
|
* (e.g. `document.createElement.bind(document)`).
|
||||||
|
* @param {string} classPrefix The prefix to be used for all CSS class names
|
||||||
|
* except `lang-*`. Default is `nb-`.
|
||||||
|
* @template TElement Type of the element object that *createElement* produces.
|
||||||
|
*/
|
||||||
|
export default <TElement extends MinimalElement> (
|
||||||
|
createElement: (tag: string) => TElement,
|
||||||
|
classPrefix = 'nb-',
|
||||||
|
): ElementCreator<TElement> => {
|
||||||
|
|
||||||
|
const prefixClassName = (name: string) => name.startsWith('lang-') ? name : classPrefix + name
|
||||||
|
|
||||||
|
return (tag: string, classesOrAttrs?: string[] | Attributes, childrenOrHTML?: TElement[] | string): TElement => {
|
||||||
|
const el = createElement(tag)
|
||||||
|
|
||||||
|
if (Array.isArray(classesOrAttrs)) {
|
||||||
|
el.setAttribute('class', classesOrAttrs.map(prefixClassName).join(' '))
|
||||||
|
|
||||||
|
} else if (classesOrAttrs) {
|
||||||
|
for (let [key, val] of Object.entries(classesOrAttrs)) {
|
||||||
|
if (key === 'class') {
|
||||||
|
val = val.split(' ').map(prefixClassName).join(' ')
|
||||||
|
}
|
||||||
|
el.setAttribute(key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(childrenOrHTML)) {
|
||||||
|
childrenOrHTML.forEach(e => el.appendChild(e))
|
||||||
|
|
||||||
|
} else if (childrenOrHTML) {
|
||||||
|
el.innerHTML = childrenOrHTML
|
||||||
|
}
|
||||||
|
return el
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,22 @@
|
||||||
import { ElementCreator, HTMLElement } from './elementCreator'
|
import { ElementCreator } from './elementCreator'
|
||||||
import { DataRenderer } from './renderer'
|
import { DataRenderer } from './renderer'
|
||||||
|
|
||||||
|
|
||||||
const extractMathRx = /^\s*<html>\s*<script\s*type="math\/tex(?:;[^"]*)">([\s\S]*)<\/script><\/html>\s*$/
|
const extractMathRx = /^\s*<html>\s*<script\s*type="math\/tex(?:;[^"]*)">([\s\S]*)<\/script><\/html>\s*$/
|
||||||
|
|
||||||
export type Options = {
|
export type Options<TElement = HTMLElement> = {
|
||||||
elementCreator: ElementCreator,
|
elementCreator: ElementCreator<TElement>,
|
||||||
mathRenderer: (math: string) => string,
|
mathRenderer: (math: string) => string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a text/html data renderer with workaround for SageMath (La)TeX output.
|
* Returns a text/html data renderer with workaround for SageMath (La)TeX output.
|
||||||
*/
|
*/
|
||||||
export default ({ elementCreator: el, mathRenderer: renderMath }: Options): DataRenderer => {
|
export default <TElement> (opts: Options<TElement>): DataRenderer<TElement> => {
|
||||||
|
const { elementCreator: el, mathRenderer: renderMath } = opts
|
||||||
|
|
||||||
return (data: string): HTMLElement => {
|
return (data: string): TElement => {
|
||||||
const math = (data.match(extractMathRx) || [])[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)
|
8
packages/ipynb2html-core/src/index.ts
Normal file
8
packages/ipynb2html-core/src/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import * as mathExtractor from './mathExtractor'
|
||||||
|
|
||||||
|
export { default as createElementCreator, ElementCreator, MinimalElement } from './elementCreator'
|
||||||
|
export { default as createHtmlRenderer } from './htmlRenderer'
|
||||||
|
export * from './nbformat'
|
||||||
|
export { default as NbRenderer, DataRenderer, NbRendererOpts } from './renderer'
|
||||||
|
export { mathExtractor }
|
||||||
|
export { default as version } from './version'
|
56
packages/ipynb2html-core/src/internal/utils.ts
Normal file
56
packages/ipynb2html-core/src/internal/utils.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
|
||||||
|
const htmlEntities: Record<string, string> = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
}
|
||||||
|
|
||||||
|
type Callable = (...args: any[]) => any
|
||||||
|
|
||||||
|
type CallableConstructor = new <T> () => T extends { __call__: Callable }
|
||||||
|
? 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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
* HTML entities.
|
||||||
|
*/
|
||||||
|
export function escapeHTML (str: string): string {
|
||||||
|
return str.replace(/[&<>]/g, c => htmlEntities[c])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that does nothing but return the parameter supplied to it.
|
||||||
|
*/
|
||||||
|
export const identity = <T>(x: T, ..._rest: any[]): T => x
|
|
@ -157,7 +157,7 @@ export function extractMath (text: string): [string, MathExpression[]] {
|
||||||
case '}':
|
case '}':
|
||||||
if (bracesLevel) { bracesLevel-- }
|
if (bracesLevel) { bracesLevel-- }
|
||||||
break
|
break
|
||||||
default: if (block.match(/\n.*\n/)) {
|
default: if (/\n.*\n/.exec(block)) {
|
||||||
if (lastIdx) {
|
if (lastIdx) {
|
||||||
i = lastIdx
|
i = lastIdx
|
||||||
processMath(unescape, math, blocks, startIdx, i)
|
processMath(unescape, math, blocks, startIdx, i)
|
||||||
|
@ -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}@@`)
|
||||||
}
|
}
|
|
@ -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,44 +139,44 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------------- Output types ------------------------- //
|
// ------------------------- Output types ------------------------- //
|
||||||
|
|
||||||
export type Output = ExecuteResult | DisplayData | Stream | Error
|
export type Output = ExecuteResult | DisplayData | StreamOutput | ErrorOutput
|
||||||
|
|
||||||
export enum OutputType {
|
export enum OutputType {
|
||||||
ExecuteResult = 'execute_result',
|
ExecuteResult = 'execute_result',
|
||||||
|
@ -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 Stream {
|
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 Error {
|
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 Error {
|
||||||
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[]
|
255
packages/ipynb2html-core/src/renderer.ts
Normal file
255
packages/ipynb2html-core/src/renderer.ts
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
// This code is originally based on notebookjs 0.4.2 distributed under the MIT license.
|
||||||
|
import { ElementCreator } from './elementCreator'
|
||||||
|
import { CallableInstance, escapeHTML, identity } from './internal/utils'
|
||||||
|
import {
|
||||||
|
Cell,
|
||||||
|
CellType,
|
||||||
|
CodeCell,
|
||||||
|
DisplayData,
|
||||||
|
ErrorOutput,
|
||||||
|
ExecuteResult,
|
||||||
|
MarkdownCell,
|
||||||
|
Notebook,
|
||||||
|
Output,
|
||||||
|
OutputType,
|
||||||
|
RawCell,
|
||||||
|
StreamOutput,
|
||||||
|
} from './nbformat'
|
||||||
|
|
||||||
|
|
||||||
|
export type NbRendererOpts<TElement = HTMLElement> = {
|
||||||
|
/**
|
||||||
|
* An object with additional data renderers indexed by a media type.
|
||||||
|
*/
|
||||||
|
dataRenderers?: DataRenderers<TElement>,
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* that has the lowest index in this array will be rendered. The default is
|
||||||
|
* `Object.keys({ ...dataRenderers, ...builtinRenderers })`.
|
||||||
|
*/
|
||||||
|
dataTypesPriority?: string[],
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* escape special characters unsafe for HTML (ansi_up does it implicitly)!
|
||||||
|
*/
|
||||||
|
ansiCodesRenderer?: (text: string) => string,
|
||||||
|
/**
|
||||||
|
* A function for highlighting the given source *code*, it should return an
|
||||||
|
* HTML string. It gets the text from the cell as-is, without prior escaping,
|
||||||
|
* so it must escape special characters unsafe for HTML (highlight.js does it
|
||||||
|
* implicitly)!
|
||||||
|
*/
|
||||||
|
codeHighlighter?: (code: string, lang: string) => string,
|
||||||
|
/**
|
||||||
|
* A function for converting the given Markdown source to HTML.
|
||||||
|
*/
|
||||||
|
markdownRenderer?: (markup: string) => string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DataRenderer<TElement = HTMLElement> = (this: NbRenderer<TElement> | void, data: string) => TElement
|
||||||
|
|
||||||
|
type DataRenderers<TElement> = { [mediaType: string]: DataRenderer<TElement> }
|
||||||
|
|
||||||
|
|
||||||
|
function joinText (text: string | string[]): string {
|
||||||
|
return Array.isArray(text) ? text.map(joinText).join('') : text
|
||||||
|
}
|
||||||
|
|
||||||
|
function coalesceStreams (outputs: Output[]): Output[] {
|
||||||
|
if (!outputs.length) { return outputs }
|
||||||
|
|
||||||
|
let last = outputs[0]
|
||||||
|
const newOutputs = [last]
|
||||||
|
|
||||||
|
for (const output of outputs.slice(1)) {
|
||||||
|
if (output.output_type === 'stream' && last.output_type === 'stream' && output.name === last.name) {
|
||||||
|
last.text = last.text.concat(...output.text)
|
||||||
|
} else {
|
||||||
|
newOutputs.push(output)
|
||||||
|
last = output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newOutputs
|
||||||
|
}
|
||||||
|
|
||||||
|
function executionCountAttrs ({ execution_count: count }: CodeCell): { [k: string]: string } {
|
||||||
|
return count ? {
|
||||||
|
'data-execution-count': String(count),
|
||||||
|
// Only for backward compatibility with notebook.js.
|
||||||
|
'data-prompt-number': String(count),
|
||||||
|
} : {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function notebookLanguage ({ metadata: meta }: Notebook): string {
|
||||||
|
return meta.language_info?.name ?? 'python'
|
||||||
|
}
|
||||||
|
|
||||||
|
class NbRenderer <TElement> extends CallableInstance<NbRenderer<TElement>> {
|
||||||
|
|
||||||
|
readonly el: ElementCreator<TElement>
|
||||||
|
readonly renderMarkdown: NonNullable<NbRendererOpts['markdownRenderer']>
|
||||||
|
readonly renderAnsiCodes: NonNullable<NbRendererOpts['ansiCodesRenderer']>
|
||||||
|
readonly highlightCode: NonNullable<NbRendererOpts['codeHighlighter']>
|
||||||
|
readonly dataRenderers: DataRenderers<TElement>
|
||||||
|
readonly dataTypesPriority: string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Notebook renderer with the given options. The constructed object
|
||||||
|
* 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()
|
||||||
|
|
||||||
|
this.el = elementCreator
|
||||||
|
this.renderMarkdown = opts.markdownRenderer ?? identity
|
||||||
|
this.renderAnsiCodes = opts.ansiCodesRenderer ?? escapeHTML
|
||||||
|
this.highlightCode = opts.codeHighlighter ?? escapeHTML
|
||||||
|
|
||||||
|
const el2 = (tag: string, classes: string[]) => (data: string) => this.el(tag, classes, data)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the given Jupyter *notebook*.
|
||||||
|
*/
|
||||||
|
__call__ (notebook: Notebook): TElement {
|
||||||
|
return this.render(notebook)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the given Jupyter *notebook*.
|
||||||
|
*/
|
||||||
|
render (notebook: Notebook): TElement {
|
||||||
|
const children = notebook.cells.map(cell => this.renderCell(cell, notebook))
|
||||||
|
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' }
|
||||||
|
|
||||||
|
return this.el('div', attrs, [innerEl])
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDisplayData (output: DisplayData): TElement {
|
||||||
|
const type = this.resolveDataType(output)
|
||||||
|
if (type) {
|
||||||
|
return this.renderData(type, joinText(output.data[type]))
|
||||||
|
}
|
||||||
|
return this.el('div', ['empty-output'])
|
||||||
|
}
|
||||||
|
|
||||||
|
renderExecuteResult (output: ExecuteResult): TElement {
|
||||||
|
const type = this.resolveDataType(output)
|
||||||
|
if (type) {
|
||||||
|
return this.renderData(type, joinText(output.data[type]))
|
||||||
|
}
|
||||||
|
return this.el('div', ['empty-output'])
|
||||||
|
}
|
||||||
|
|
||||||
|
renderError (error: ErrorOutput): TElement {
|
||||||
|
const html = this.renderAnsiCodes(error.traceback.join('\n'))
|
||||||
|
// Class "pyerr" is for backward compatibility with notebook.js.
|
||||||
|
return this.el('pre', ['error', 'pyerr'], html)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderStream (stream: StreamOutput): TElement {
|
||||||
|
const html = this.renderAnsiCodes(joinText(stream.text))
|
||||||
|
return this.el('pre', [stream.name], html)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderData (mimeType: string, data: string): TElement {
|
||||||
|
const render = this.dataRenderers[mimeType]
|
||||||
|
if (!render) {
|
||||||
|
throw RangeError(`missing renderer for MIME type: ${mimeType}`)
|
||||||
|
}
|
||||||
|
return render.call(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveDataType (output: DisplayData | ExecuteResult): string | undefined {
|
||||||
|
return this.dataTypesPriority.find(type => output.data[type])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NbRenderer
|
6
packages/ipynb2html-core/src/version.ts
Normal file
6
packages/ipynb2html-core/src/version.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { $INLINE_JSON } from 'ts-transformer-inline-file'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const { version } = $INLINE_JSON('../package.json')
|
||||||
|
|
||||||
|
export default version as string
|
84
packages/ipynb2html-core/test/internal/utils.test.ts
Normal file
84
packages/ipynb2html-core/test/internal/utils.test.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { CallableInstance, escapeHTML, identity } from '@/internal/utils'
|
||||||
|
|
||||||
|
|
||||||
|
describe('CallableInstance', () => {
|
||||||
|
|
||||||
|
class FixtureCallable extends CallableInstance<FixtureCallable> {
|
||||||
|
readonly salutation: string
|
||||||
|
|
||||||
|
constructor (salutation: string) {
|
||||||
|
super()
|
||||||
|
this.salutation = salutation
|
||||||
|
}
|
||||||
|
|
||||||
|
__call__ (name: string) {
|
||||||
|
return this.salute(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 an instance of its class', () => {
|
||||||
|
expect( instance ).toBeInstanceOf(FixtureCallable)
|
||||||
|
expect( instance.salutation ).toBe('Hello')
|
||||||
|
expect( instance.salute('world') ).toBe('Hello, world!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is an instance of Function', () => {
|
||||||
|
expect( instance ).toBeInstanceOf(Function)
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe('.escapeHTML', () => {
|
||||||
|
test.each([
|
||||||
|
/* input | expected */
|
||||||
|
['&' , '&' ],
|
||||||
|
['<' , '<' ],
|
||||||
|
['>' , '>' ],
|
||||||
|
['Hey >_<! <<&>>', 'Hey >_<! <<&>>'],
|
||||||
|
])('"%s" -> "%s"', (input, expected) => {
|
||||||
|
expect( escapeHTML(input) ).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe('.identity', () => {
|
||||||
|
it('returns the first given argument', () => {
|
||||||
|
expect( identity('a', 'b') ).toBe('a')
|
||||||
|
})
|
||||||
|
})
|
|
@ -20,8 +20,8 @@ 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,8 +31,8 @@ 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,8 +43,8 @@ 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.')
|
||||||
})
|
})
|
||||||
})
|
})
|
|
@ -1,18 +1,20 @@
|
||||||
import './setup' // setupFilesAfterEnv doesn't work here
|
/* eslint-disable @typescript-eslint/unbound-method */
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
import { Anything } from './support/matchers/toMatchElement'
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
import { mockLastResult, mockResults } from './support/helpers'
|
import { Anything } from '~/test/support/matchers/toMatchElement'
|
||||||
import * as _fixtures from './support/fixtures/notebook'
|
import { mockLastResult, mockResults } from '~/test/support/helpers'
|
||||||
|
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', () => {
|
||||||
|
@ -26,55 +28,56 @@ describe('built renderer', () => {
|
||||||
const dataRenderers = {
|
const dataRenderers = {
|
||||||
'text/custom': rendererMock('DisplayData'),
|
'text/custom': rendererMock('DisplayData'),
|
||||||
}
|
}
|
||||||
const rendererOpts: RendererOpts = {
|
const rendererOpts: NbRendererOpts<HTMLElement> = {
|
||||||
ansiCodesRenderer,
|
ansiCodesRenderer,
|
||||||
codeHighlighter,
|
codeHighlighter,
|
||||||
markdownRenderer,
|
markdownRenderer,
|
||||||
dataRenderers,
|
dataRenderers,
|
||||||
}
|
}
|
||||||
let renderer: NbRenderer
|
let renderer: NbRenderer<HTMLElement>
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -85,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>
|
||||||
|
@ -95,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)
|
||||||
})
|
})
|
||||||
|
@ -111,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]', () => {
|
||||||
|
@ -162,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', () => {
|
||||||
|
@ -205,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),
|
||||||
|
@ -218,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')
|
||||||
|
@ -231,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", () => {
|
||||||
|
@ -253,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)
|
||||||
|
@ -264,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', () => {
|
||||||
|
@ -285,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),
|
||||||
|
@ -316,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 -->' }}
|
||||||
|
@ -328,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 }
|
||||||
|
@ -357,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>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -369,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>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -387,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>
|
||||||
|
@ -399,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>
|
||||||
|
@ -411,8 +415,8 @@ 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> // FIXME: unescape after updating nodom
|
<pre class="text-output">{ '>_<' }</pre>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -420,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>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -432,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))
|
||||||
})
|
})
|
||||||
|
@ -449,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>
|
||||||
|
@ -463,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'])!
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -473,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 },
|
||||||
})
|
})
|
||||||
|
@ -483,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>
|
||||||
|
@ -505,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>
|
|
@ -3,7 +3,7 @@ import * as nb from '@/nbformat'
|
||||||
const { CellType, OutputType } = nb
|
const { CellType, OutputType } = nb
|
||||||
|
|
||||||
|
|
||||||
export const Stream: nb.Stream = {
|
export const Stream: nb.StreamOutput = {
|
||||||
output_type: OutputType.Stream,
|
output_type: OutputType.Stream,
|
||||||
name: 'stdout',
|
name: 'stdout',
|
||||||
text: [
|
text: [
|
||||||
|
@ -12,7 +12,7 @@ export const Stream: nb.Stream = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Error: nb.Error = {
|
export const Error: nb.ErrorOutput = {
|
||||||
output_type: OutputType.Error,
|
output_type: OutputType.Error,
|
||||||
ename: 'Error',
|
ename: 'Error',
|
||||||
evalue: 'whatever',
|
evalue: 'whatever',
|
9
packages/ipynb2html-core/test/tsconfig.json
Normal file
9
packages/ipynb2html-core/test/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.test.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{ "path": "../" },
|
||||||
|
],
|
||||||
|
}
|
12
packages/ipynb2html-core/tsconfig.json
Normal file
12
packages/ipynb2html-core/tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./lib",
|
||||||
|
"tsBuildInfoFile": "./.tsbuildinfo",
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src",
|
||||||
|
],
|
||||||
|
}
|
7
packages/ipynb2html/jest.config.js
Normal file
7
packages/ipynb2html/jest.config.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const pkg = require('./package.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...require('../../jest.config.base'),
|
||||||
|
name: pkg.name,
|
||||||
|
displayName: pkg.name,
|
||||||
|
}
|
65
packages/ipynb2html/package.json
Normal file
65
packages/ipynb2html/package.json
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"name": "ipynb2html",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "Convert Jupyter Notebook to static HTML",
|
||||||
|
"author": "Jakub Jirutka <jakub@jirutka.cz>",
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/jirutka/ipynb2html",
|
||||||
|
"bugs": "https://github.com/jirutka/ipynb2html/issues",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/jirutka/ipynb2html.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"converter",
|
||||||
|
"html",
|
||||||
|
"ipython",
|
||||||
|
"jupyter",
|
||||||
|
"notebook"
|
||||||
|
],
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"browser": "dist/ipynb2html.min.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/ipynb2html.min.js*",
|
||||||
|
"dist/ipynb2html-full.min.js*",
|
||||||
|
"dist/notebook.min.css*",
|
||||||
|
"lib",
|
||||||
|
"src",
|
||||||
|
"styles"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "ttsc --build",
|
||||||
|
"bundle": "rollup -c",
|
||||||
|
"clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
|
||||||
|
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
||||||
|
"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",
|
||||||
|
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
||||||
|
"watch-ts": "ttsc --build --watch"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"anser": "^1.4.9",
|
||||||
|
"highlight.js": "^10.7.3",
|
||||||
|
"ipynb2html-core": "0.3.0",
|
||||||
|
"katex": "^0.13.11",
|
||||||
|
"marked": "^2.0.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/katex": "^0.11.0",
|
||||||
|
"@types/marked": "^2.0.3",
|
||||||
|
"ansi_up": "^5.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"nodom": "^2.3.0"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
">0.5%",
|
||||||
|
"Firefox ESR",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
109
packages/ipynb2html/rollup.config.js
Normal file
109
packages/ipynb2html/rollup.config.js
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import addGitMsg from 'rollup-plugin-add-git-msg'
|
||||||
|
import babel from '@rollup/plugin-babel'
|
||||||
|
import commonjs from '@rollup/plugin-commonjs'
|
||||||
|
import license from 'rollup-plugin-node-license'
|
||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import { terser } from 'rollup-plugin-terser'
|
||||||
|
import ttypescript from 'ttypescript'
|
||||||
|
import typescript from 'rollup-plugin-typescript2'
|
||||||
|
|
||||||
|
import pkg from './package.json'
|
||||||
|
|
||||||
|
|
||||||
|
const extensions = ['.mjs', '.js', '.ts']
|
||||||
|
|
||||||
|
const globals = {
|
||||||
|
'anser': 'anser',
|
||||||
|
'highlight.js': 'hljs',
|
||||||
|
'katex': 'katex',
|
||||||
|
'marked': 'marked',
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
// Transpile TypeScript sources to JS.
|
||||||
|
typescript({
|
||||||
|
typescript: ttypescript,
|
||||||
|
tsconfigOverride: {
|
||||||
|
compilerOptions: {
|
||||||
|
target: 'ES5',
|
||||||
|
module: 'ESNext',
|
||||||
|
declaration: false,
|
||||||
|
declarationMap: false,
|
||||||
|
composite: false,
|
||||||
|
incremental: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clean: true,
|
||||||
|
}),
|
||||||
|
// Resolve node modules.
|
||||||
|
resolve({
|
||||||
|
extensions,
|
||||||
|
mainFields: ['browser', 'module', 'main'],
|
||||||
|
}),
|
||||||
|
// Convert CommonJS modules to ES6 modules.
|
||||||
|
commonjs(),
|
||||||
|
// Transpile all sources for older browsers and inject needed polyfills.
|
||||||
|
babel({
|
||||||
|
babelrc: false,
|
||||||
|
babelHelpers: 'bundled',
|
||||||
|
// To avoid Babel injecting core-js polyfills into core-js.
|
||||||
|
exclude: [/node_modules\/core-js\//],
|
||||||
|
extensions,
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/env', {
|
||||||
|
corejs: 3,
|
||||||
|
debug: false,
|
||||||
|
modules: false,
|
||||||
|
useBuiltIns: 'usage', // inject polyfills
|
||||||
|
// targets: reads from "browserslist" in package.json
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
// Add git tag, commit SHA and build date at top of the file.
|
||||||
|
addGitMsg({
|
||||||
|
copyright: [
|
||||||
|
pkg.author,
|
||||||
|
'* This project is licensed under the terms of the MIT license.'
|
||||||
|
].join('\n'),
|
||||||
|
}),
|
||||||
|
// Generate table of the bundled packages at top of the file.
|
||||||
|
license({ format: 'table' }),
|
||||||
|
]
|
||||||
|
|
||||||
|
const output = (filename, extra = {}) => [false, true].map(minify => ({
|
||||||
|
name: pkg.name,
|
||||||
|
file: `${filename}${minify ? '.min.js' : '.js'}`,
|
||||||
|
format: 'umd',
|
||||||
|
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,
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// Bundle third-party dependencies except core-js polyfills.
|
||||||
|
{
|
||||||
|
input: 'src/browser.ts',
|
||||||
|
external: Object.keys(globals),
|
||||||
|
plugins,
|
||||||
|
output: output(`dist/${pkg.name}`, { globals }),
|
||||||
|
},
|
||||||
|
// Bundle with all dependencies.
|
||||||
|
{
|
||||||
|
input: 'src/browser.ts',
|
||||||
|
plugins,
|
||||||
|
// Tree-shaking breaks KaTeX and has almost zero effect on the bundle size.
|
||||||
|
treeshake: false,
|
||||||
|
output: output(`dist/${pkg.name}-full`),
|
||||||
|
}
|
||||||
|
]
|
48
packages/ipynb2html/src/browser.ts
Normal file
48
packages/ipynb2html/src/browser.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { createRenderer, NbRendererOpts, Notebook } from '.'
|
||||||
|
|
||||||
|
export * from '.'
|
||||||
|
|
||||||
|
|
||||||
|
function unescapeHTML (input: string): string {
|
||||||
|
return new DOMParser()
|
||||||
|
.parseFromString(input, 'text/html')
|
||||||
|
.documentElement
|
||||||
|
.textContent ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the given Jupyter *notebook* to HTML. It's a shorthand for
|
||||||
|
* `createRenderer(document, opts)(notebook)`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* document.body.appendChild(ipynb2html.render(notebook))
|
||||||
|
*
|
||||||
|
* @param notebook Object in Jupyter Notebook Format 4.0+.
|
||||||
|
* @param opts The renderer options.
|
||||||
|
* @return An HTMLElement with the rendered *notebook*.
|
||||||
|
* @see createRenderer
|
||||||
|
*/
|
||||||
|
export function render (notebook: Notebook, opts: NbRendererOpts = {}): HTMLElement {
|
||||||
|
return createRenderer(document, opts)(notebook)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders Jupyter Notebook inside each
|
||||||
|
* `<script type="application/x-ipynb+json">...</script>` found in
|
||||||
|
* the page's body.
|
||||||
|
*
|
||||||
|
* @param opts The renderer options.
|
||||||
|
*/
|
||||||
|
export function autoRender (opts: NbRendererOpts = {}): void {
|
||||||
|
const selector = 'script[type="application/x-ipynb+json"]'
|
||||||
|
const render = createRenderer(document, opts)
|
||||||
|
|
||||||
|
document.querySelectorAll(selector).forEach(script => {
|
||||||
|
if (script.textContent && script.parentElement) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const notebook = JSON.parse(unescapeHTML(script.textContent))
|
||||||
|
const nbElement = render(notebook)
|
||||||
|
script.parentElement.replaceChild(nbElement, script)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
6
packages/ipynb2html/src/global.d.ts
vendored
Normal file
6
packages/ipynb2html/src/global.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// AnsiUp may be provided in the browser environment as an external dependency
|
||||||
|
// via global variable. It's an alternative to Anser that doesn't provide
|
||||||
|
// bundle for browsers.
|
||||||
|
declare class AnsiUp {
|
||||||
|
ansi_to_html (input: string): string
|
||||||
|
}
|
138
packages/ipynb2html/src/index.ts
Normal file
138
packages/ipynb2html/src/index.ts
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import anser from 'anser'
|
||||||
|
import hljs from 'highlight.js'
|
||||||
|
import katex, { KatexOptions } from 'katex'
|
||||||
|
import marked from 'marked'
|
||||||
|
|
||||||
|
import {
|
||||||
|
createElementCreator,
|
||||||
|
createHtmlRenderer,
|
||||||
|
MinimalElement,
|
||||||
|
NbRenderer,
|
||||||
|
NbRendererOpts as BaseOptions,
|
||||||
|
Notebook,
|
||||||
|
} from 'ipynb2html-core'
|
||||||
|
|
||||||
|
import buildMarkdownRenderer, { MarkedOptions } from './markdownRenderer'
|
||||||
|
|
||||||
|
|
||||||
|
export { default as version } from './version'
|
||||||
|
export { default as readNotebookTitle } from './readNotebookTitle'
|
||||||
|
export { NbRenderer, Notebook }
|
||||||
|
|
||||||
|
export type NbRendererOpts<TElement = HTMLElement> = BaseOptions<TElement> & {
|
||||||
|
/**
|
||||||
|
* The prefix to be used for all CSS class names except `lang-*`.
|
||||||
|
* Default is `nb-`.
|
||||||
|
*/
|
||||||
|
classPrefix?: string,
|
||||||
|
/**
|
||||||
|
* Options for the KaTeX math renderer. Default is
|
||||||
|
* `{ displayMode: true, throwOnError: false }`. The provided options will
|
||||||
|
* be merged with the default.
|
||||||
|
*/
|
||||||
|
katexOpts?: KatexOptions,
|
||||||
|
/**
|
||||||
|
* Options for the marked Markdown renderer.
|
||||||
|
*/
|
||||||
|
markedOpts?: MarkedOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition of the smallest possible subset of the Document type required
|
||||||
|
* for this module's function.
|
||||||
|
*/
|
||||||
|
type MinimalDocument<TElement extends MinimalElement> = {
|
||||||
|
createElement: (tag: string) => TElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultKatexOpts: KatexOptions = {
|
||||||
|
displayMode: true,
|
||||||
|
throwOnError: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMarkedOpts: MarkedOptions = {
|
||||||
|
headerAnchors: true,
|
||||||
|
headerIdsStripAccents: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
function hljsCodeHighlighter (code: string, lang: string): string {
|
||||||
|
return hljs.getLanguage(lang)
|
||||||
|
? hljs.highlight(code, { language: lang }).value
|
||||||
|
: code
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a full-fledged Jupyter Notebook renderer.
|
||||||
|
*
|
||||||
|
* It supports rendering of Markdown cells with math (using marked and KaTeX),
|
||||||
|
* code highlighting (using highlight.js), rendering of ANSI escape sequences
|
||||||
|
* (using Anser) and SageMath-style math outputs. All of them may be overridden
|
||||||
|
* via *opts*.
|
||||||
|
*
|
||||||
|
* It returns a "callable object" that exposes one renderer function for each
|
||||||
|
* of the Notebook's AST nodes. You can easily replace any of the functions to
|
||||||
|
* modify behaviour of the renderer.
|
||||||
|
*
|
||||||
|
* @example // Node.js
|
||||||
|
* import * as fs from 'fs'
|
||||||
|
* import * as ipynb from 'ipynb2html'
|
||||||
|
* import { Document } from 'nodom'
|
||||||
|
*
|
||||||
|
* const renderNotebook = ipynb.createRenderer(new Document())
|
||||||
|
* const notebook = JSON.parse(fs.readFileSync('./example.ipynb', 'utf8'))
|
||||||
|
*
|
||||||
|
* console.log(renderNotebook(notebook).outerHTML)
|
||||||
|
*
|
||||||
|
* @example // Browser
|
||||||
|
* const render = ipynb2html.createRenderer(document)
|
||||||
|
* document.body.appendChild(render(notebook))
|
||||||
|
*
|
||||||
|
* @param document The `Document` object from the browser's native DOM or any
|
||||||
|
* fake/virtual DOM library (e.g. nodom). The only required method is
|
||||||
|
* `createElement`.
|
||||||
|
* @param opts The renderer options.
|
||||||
|
* @return A configured instance of the Notebook renderer.
|
||||||
|
*/
|
||||||
|
export function createRenderer <TElement extends MinimalElement> (
|
||||||
|
document: MinimalDocument<TElement>,
|
||||||
|
opts: NbRendererOpts<TElement> = {},
|
||||||
|
): NbRenderer<TElement> {
|
||||||
|
|
||||||
|
let { ansiCodesRenderer, codeHighlighter, dataRenderers = {}, markdownRenderer } = opts
|
||||||
|
const katexOpts = { ...defaultKatexOpts, ...opts.katexOpts }
|
||||||
|
|
||||||
|
const elementCreator = createElementCreator(
|
||||||
|
document.createElement.bind(document),
|
||||||
|
opts.classPrefix,
|
||||||
|
)
|
||||||
|
|
||||||
|
// The following ifs for the imported modules are for the browser bundle
|
||||||
|
// without dependencies.
|
||||||
|
if (!ansiCodesRenderer) {
|
||||||
|
if (anser) {
|
||||||
|
ansiCodesRenderer = (input) => anser.ansiToHtml(anser.escapeForHtml(input))
|
||||||
|
} else if (AnsiUp) {
|
||||||
|
const ansiUp = new AnsiUp()
|
||||||
|
ansiCodesRenderer = ansiUp.ansi_to_html.bind(ansiUp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!codeHighlighter && hljs) {
|
||||||
|
codeHighlighter = hljsCodeHighlighter
|
||||||
|
}
|
||||||
|
if (!markdownRenderer && marked) {
|
||||||
|
const markedOpts = { ...defaultMarkedOpts, ...opts.markedOpts }
|
||||||
|
markdownRenderer = buildMarkdownRenderer(markedOpts, katexOpts)
|
||||||
|
}
|
||||||
|
if (!dataRenderers['text/html'] && katex) {
|
||||||
|
const mathRenderer = (tex: string) => katex.renderToString(tex, katexOpts)
|
||||||
|
dataRenderers['text/html'] = createHtmlRenderer({ elementCreator, mathRenderer })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NbRenderer(elementCreator, {
|
||||||
|
ansiCodesRenderer,
|
||||||
|
codeHighlighter,
|
||||||
|
dataRenderers,
|
||||||
|
markdownRenderer,
|
||||||
|
...opts,
|
||||||
|
})
|
||||||
|
}
|
86
packages/ipynb2html/src/markdownRenderer.ts
Normal file
86
packages/ipynb2html/src/markdownRenderer.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import hljs from 'highlight.js'
|
||||||
|
import katex, { KatexOptions } from 'katex'
|
||||||
|
import marked, { Slugger } from 'marked'
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return hljs.getLanguage(lang)
|
||||||
|
? hljs.highlight(code, { language: lang, ignoreIllegals: true }).value
|
||||||
|
: 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 (optional) math support (using
|
||||||
|
* KaTeX) and code highlighter (highlight.js).
|
||||||
|
*
|
||||||
|
* @param {MarkedOptions} markedOpts Options for the marked Markdown renderer.
|
||||||
|
* @param {KatexOptions} katexOpts Options for the KaTeX math renderer.
|
||||||
|
*/
|
||||||
|
export default (markedOpts: MarkedOptions = {}, katexOpts: KatexOptions = {}): MarkdownRenderer => {
|
||||||
|
|
||||||
|
markedOpts = { renderer: new Renderer(markedOpts), ...markedOpts }
|
||||||
|
if (hljs) { // highlight.js may be an optional dependency (in browser bundle)
|
||||||
|
markedOpts = { highlight, ...markedOpts }
|
||||||
|
}
|
||||||
|
|
||||||
|
return katex // katex may be an optional dependency (in browser bundle)
|
||||||
|
? rendererWithMath(markedOpts, katexOpts)
|
||||||
|
: renderer(markedOpts)
|
||||||
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
import marked from 'marked'
|
import marked from 'marked'
|
||||||
|
|
||||||
import { Notebook, CellType } from './nbformat'
|
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
|
||||||
|
@ -38,7 +38,7 @@ export default (notebook: Notebook): string => {
|
||||||
if (notebook.metadata.title) {
|
if (notebook.metadata.title) {
|
||||||
return notebook.metadata.title
|
return notebook.metadata.title
|
||||||
}
|
}
|
||||||
if (notebook.cells.length > 0 && notebook.cells[0].cell_type === CellType.Markdown) {
|
if (notebook.cells.length > 0 && notebook.cells[0].cell_type === 'markdown') {
|
||||||
const source = notebook.cells[0].source
|
const source = notebook.cells[0].source
|
||||||
const markup = Array.isArray(source) ? source.join('') : source
|
const markup = Array.isArray(source) ? source.join('') : source
|
||||||
|
|
6
packages/ipynb2html/src/version.ts
Normal file
6
packages/ipynb2html/src/version.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { $INLINE_JSON } from 'ts-transformer-inline-file'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const { version } = $INLINE_JSON('../package.json')
|
||||||
|
|
||||||
|
export default version as string
|
86
packages/ipynb2html/styles/notebook.css
Normal file
86
packages/ipynb2html/styles/notebook.css
Normal 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
1
packages/ipynb2html/test/global.d.ts
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../src/global.d.ts
|
152
packages/ipynb2html/test/markdownRenderer.test.tsx
Normal file
152
packages/ipynb2html/test/markdownRenderer.test.tsx
Normal 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('') ).toHtmlEqual(
|
||||||
|
<p><img src="https://example.org/img.png" alt="x" title="This is !" /></p>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('strips math in href', () => {
|
||||||
|
expect( render('') ).toHtmlEqual(
|
||||||
|
<p><img src="https://example.org/.png" alt="image" /></p>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('strips math in alt text', () => {
|
||||||
|
expect( render('') ).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]
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { Notebook, CellType, MarkdownCell } from '@/nbformat'
|
import { Notebook, CellType, MarkdownCell } from 'ipynb2html-core'
|
||||||
|
|
||||||
import readNotebookTitle from '@/readNotebookTitle'
|
import readNotebookTitle from '@/readNotebookTitle'
|
||||||
|
|
||||||
|
|
9
packages/ipynb2html/test/tsconfig.json
Normal file
9
packages/ipynb2html/test/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.test.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{ "path": "../" },
|
||||||
|
],
|
||||||
|
}
|
15
packages/ipynb2html/tsconfig.json
Normal file
15
packages/ipynb2html/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./lib",
|
||||||
|
"tsBuildInfoFile": "./.tsbuildinfo",
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src",
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../ipynb2html-core" },
|
||||||
|
],
|
||||||
|
}
|
0
patches/.gitkeep
Normal file
0
patches/.gitkeep
Normal file
15
patches/yarn-version-bump+0.0.3.patch
Normal file
15
patches/yarn-version-bump+0.0.3.patch
Normal 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))
|
4
scripts/adoc2md
Executable file
4
scripts/adoc2md
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh -e
|
||||||
|
( set -o pipefail 2>/dev/null ) && set -o pipefail
|
||||||
|
|
||||||
|
asciidoctor -o - -b docbook "$@" | pandoc -f docbook -t markdown_github --base-header-level 2 -o -
|
42
scripts/assemble-license
Executable file
42
scripts/assemble-license
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
|
||||||
|
function extractLicenseTable (filename) {
|
||||||
|
const source = fs.readFileSync(filename, 'utf8')
|
||||||
|
|
||||||
|
const start = source.indexOf('/*!\n * Bundled npm packages')
|
||||||
|
const end = start + source.slice(start).indexOf('*/')
|
||||||
|
|
||||||
|
return source
|
||||||
|
.slice(start, end)
|
||||||
|
.split('\n')
|
||||||
|
.filter(s => s.startsWith(' * |'))
|
||||||
|
.map(s => s.slice(3))
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLicense () {
|
||||||
|
return fs.readFileSync(path.join(__dirname, '../LICENSE'), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const argv = process.argv.slice(2)
|
||||||
|
if (argv.length < 1 || ['-h', '--help'].includes(argv[0])) {
|
||||||
|
console.warn('Usage: assemble-license <bundle-file>')
|
||||||
|
process.exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bundlePath = path.resolve(argv[0])
|
||||||
|
|
||||||
|
console.log(`\
|
||||||
|
${readLicense()}
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
This product bundles source code of the following projects:
|
||||||
|
|
||||||
|
${extractLicenseTable(bundlePath)}\
|
||||||
|
`)
|
43
scripts/bump-version
Executable file
43
scripts/bump-version
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
const { bumpVersion } = require('yarn-version-bump/src/bump-version')
|
||||||
|
const { processJsonFile } = require('yarn-version-bump/src/util/json')
|
||||||
|
const Workspace = require('yarn-version-bump/src/workspace')
|
||||||
|
const rootPkg = require('../package.json')
|
||||||
|
|
||||||
|
|
||||||
|
async function workspacePackages () {
|
||||||
|
return Object.keys((await new Workspace('.').workspaceSnapshot).packages)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function bumpAllPackages (newVersion) {
|
||||||
|
processJsonFile('package.json', pkg => {
|
||||||
|
pkg.version = newVersion
|
||||||
|
return pkg
|
||||||
|
})
|
||||||
|
for (const pkgname of await workspacePackages()) {
|
||||||
|
console.log(`bumping ${pkgname} to ${newVersion}`)
|
||||||
|
await bumpVersion(pkgname, newVersion, '.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateReadme (newVersion) {
|
||||||
|
return fs.readFile('README.adoc', 'utf8')
|
||||||
|
.then(str => str.replace(/^:version: \d.*$/m, `:version: ${newVersion}`))
|
||||||
|
.then(str => fs.writeFile('README.adoc', str))
|
||||||
|
}
|
||||||
|
|
||||||
|
const newVersion = process.argv[2] || rootPkg.version
|
||||||
|
|
||||||
|
process.chdir(path.resolve(__dirname, '..'))
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
bumpAllPackages(newVersion),
|
||||||
|
updateReadme(newVersion),
|
||||||
|
]).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
79
scripts/create-archives
Executable file
79
scripts/create-archives
Executable file
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const commonPathPrefix = require('common-path-prefix')
|
||||||
|
const execa = require('execa')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
const tar = require('tar')
|
||||||
|
const { ZipFile } = require('yazl')
|
||||||
|
|
||||||
|
|
||||||
|
async function writeTarGz (basename, fileList, cwd) {
|
||||||
|
console.log(`Creating ${basename}.tar.gz...`)
|
||||||
|
|
||||||
|
return tar.create({
|
||||||
|
file: `${basename}.tar.gz`,
|
||||||
|
prefix: path.basename(basename),
|
||||||
|
gzip: true,
|
||||||
|
portable: true,
|
||||||
|
cwd: cwd || '.',
|
||||||
|
}, fileList)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeZip (basename, fileList, cwd) {
|
||||||
|
const prefix = path.basename(basename)
|
||||||
|
const zipfile = new ZipFile()
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(`Creating ${basename}.zip...`)
|
||||||
|
|
||||||
|
for (let fname of fileList) {
|
||||||
|
zipfile.addFile(path.join(cwd || '.', fname), path.join(prefix, fname))
|
||||||
|
}
|
||||||
|
zipfile.outputStream
|
||||||
|
.pipe(fs.createWriteStream(`${basename}.zip`))
|
||||||
|
.on('close', resolve)
|
||||||
|
.on('error', reject)
|
||||||
|
zipfile.end()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function gitDescribe (prefix) {
|
||||||
|
const output = await execa.stdout('git', [
|
||||||
|
'describe',
|
||||||
|
'--always',
|
||||||
|
`--match=${prefix}*`
|
||||||
|
])
|
||||||
|
return output.startsWith(prefix) ? output : `v0.0.0-${output}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createArchives (destName, srcFiles) {
|
||||||
|
const version = await gitDescribe('v')
|
||||||
|
destName += `-${version}`
|
||||||
|
|
||||||
|
const srcDir = commonPathPrefix(srcFiles)
|
||||||
|
const fileList = srcFiles.map(p => p.slice(srcDir.length))
|
||||||
|
|
||||||
|
fs.mkdirpSync(path.dirname(destName))
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
writeTarGz(destName, fileList, srcDir),
|
||||||
|
writeZip(destName, fileList, srcDir),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const argv = process.argv.slice(2)
|
||||||
|
if (argv.length < 3 || ['-h', '--help'].includes(argv[0])) {
|
||||||
|
console.warn('Usage: create-archives <dest-basename> <src-file>...')
|
||||||
|
process.exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const destName = argv[0]
|
||||||
|
const srcFiles = argv.slice(1)
|
||||||
|
|
||||||
|
createArchives(destName, srcFiles).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
19
scripts/create-src-tarball
Executable file
19
scripts/create-src-tarball
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
'' | -h | --help) echo "Usage: $0 <out-file>" >&2; exit 2;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
out_file="$1"
|
||||||
|
base_dir=$(basename "$(pwd)")
|
||||||
|
base_name=$(basename "${out_file%.tar.gz}")
|
||||||
|
|
||||||
|
mkdir -p dist
|
||||||
|
tar -czf "$out_file" \
|
||||||
|
-C .. --exclude-vcs \
|
||||||
|
--exclude="**/$out_file" \
|
||||||
|
--xform="s|^$base_dir|$base_name|" \
|
||||||
|
"$base_dir"
|
|
@ -1,49 +0,0 @@
|
||||||
// NOTE: nodom.HTMLElement defines a subset of properties and functions of
|
|
||||||
// the browser's HTMLElement, that's why we use it here.
|
|
||||||
import { HTMLElement } from 'nodom'
|
|
||||||
|
|
||||||
|
|
||||||
type Attributes = { [k: string]: string }
|
|
||||||
|
|
||||||
export type ElementCreator =
|
|
||||||
& ((tag: string, classes?: string[], children?: HTMLElement[] | string) => HTMLElement)
|
|
||||||
& ((tag: string, attrs?: Attributes, children?: HTMLElement[] | string) => HTMLElement)
|
|
||||||
|
|
||||||
export { HTMLElement }
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a function for building `HTMLElement`s.
|
|
||||||
*
|
|
||||||
* @param {function} createElement A function that creates a new `HTMLElement`
|
|
||||||
* (e.g. `document.createElement.bind(document)`).
|
|
||||||
* @param {string} classPrefix The prefix to be used for all CSS class names
|
|
||||||
* except `lang-*`. Default is `nb-`.
|
|
||||||
*/
|
|
||||||
export default (createElement: (tag: string) => HTMLElement, classPrefix: string = 'nb-'): ElementCreator => {
|
|
||||||
const prefixClassName = (name: string) => name.startsWith('lang-') ? name : classPrefix + name
|
|
||||||
|
|
||||||
return (tag: string, classesOrAttrs?: string[] | Attributes, childrenOrHTML?: HTMLElement[] | string) => {
|
|
||||||
const el = createElement(tag)
|
|
||||||
|
|
||||||
if (Array.isArray(classesOrAttrs)) {
|
|
||||||
el.className = classesOrAttrs.map(prefixClassName).join(' ')
|
|
||||||
|
|
||||||
} else if (classesOrAttrs) {
|
|
||||||
for (const [key, val] of Object.entries(classesOrAttrs)) {
|
|
||||||
if (key === 'class') {
|
|
||||||
el.className = val.split(' ').map(prefixClassName).join(' ')
|
|
||||||
} else {
|
|
||||||
el.setAttribute(key, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Array.isArray(childrenOrHTML)) {
|
|
||||||
childrenOrHTML.forEach(e => el.appendChild(e))
|
|
||||||
|
|
||||||
} else if (childrenOrHTML) {
|
|
||||||
el.innerHTML = childrenOrHTML
|
|
||||||
}
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
}
|
|
53
src/index.ts
53
src/index.ts
|
@ -1,53 +0,0 @@
|
||||||
import anser from 'anser'
|
|
||||||
import hjs from 'highlightjs'
|
|
||||||
import katex, { KatexOptions } from 'katex'
|
|
||||||
import { MarkedOptions } from 'marked'
|
|
||||||
import { Document } from 'nodom'
|
|
||||||
|
|
||||||
import buildElementCreator from './elementCreator'
|
|
||||||
import htmlRenderer from './htmlRenderer'
|
|
||||||
import buildMarkdownRenderer from './markdownRenderer'
|
|
||||||
import buildRenderer, { Options as RendererOpts, NbRenderer } from './renderer'
|
|
||||||
|
|
||||||
|
|
||||||
export { NbRenderer }
|
|
||||||
|
|
||||||
export type Options = RendererOpts & {
|
|
||||||
classPrefix?: string,
|
|
||||||
katexOpts?: KatexOptions,
|
|
||||||
markedOpts?: MarkedOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function ansiCodesRenderer (input: string): string {
|
|
||||||
return anser.ansiToHtml(anser.escapeForHtml(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
function codeHighlighter (code: string, lang: string): string {
|
|
||||||
return hjs.getLanguage(lang)
|
|
||||||
? hjs.highlight(lang, code).value
|
|
||||||
: code
|
|
||||||
}
|
|
||||||
|
|
||||||
function mathRenderer (tex: string) {
|
|
||||||
return katex.renderToString(tex, { displayMode: true, throwOnError: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (opts: Options = {}): NbRenderer => {
|
|
||||||
const doc = new Document()
|
|
||||||
const elementCreator = buildElementCreator(doc.createElement.bind(doc), opts.classPrefix)
|
|
||||||
const markdownRenderer = buildMarkdownRenderer(opts.markedOpts, opts.katexOpts)
|
|
||||||
|
|
||||||
const dataRenderers = {
|
|
||||||
'text/html': htmlRenderer({ elementCreator, mathRenderer }),
|
|
||||||
...opts.dataRenderers,
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildRenderer(elementCreator, {
|
|
||||||
ansiCodesRenderer,
|
|
||||||
codeHighlighter,
|
|
||||||
dataRenderers,
|
|
||||||
markdownRenderer,
|
|
||||||
...opts,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
|
|
||||||
const htmlEntities = {
|
|
||||||
'&': '&',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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[]) {
|
|
||||||
return (template[funcName] as any)(...args)
|
|
||||||
}
|
|
||||||
return Object.assign(fn, template) as any
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes characters with special meaning in HTML with the corresponding
|
|
||||||
* HTML entities.
|
|
||||||
*/
|
|
||||||
export function escapeHTML (str: string): string {
|
|
||||||
return str.replace(/[&<>]/g, c => (htmlEntities as any)[c])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that does nothing but return the parameter supplied to it.
|
|
||||||
*/
|
|
||||||
export const identity = <T>(x: T, ..._rest: any[]): T => x
|
|
|
@ -1,37 +0,0 @@
|
||||||
import hjs from 'highlightjs'
|
|
||||||
import katex, { KatexOptions } from 'katex'
|
|
||||||
import marked, { MarkedOptions } from 'marked'
|
|
||||||
|
|
||||||
import { extractMath, restoreMath } from './mathExtractor'
|
|
||||||
|
|
||||||
|
|
||||||
function highlight (code: string, lang: string): string {
|
|
||||||
return hjs.getLanguage(lang)
|
|
||||||
? hjs.highlight(lang, code, true).value
|
|
||||||
: code
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a pre-configured marked parser with math support (using KaTeX)
|
|
||||||
* and code highlighter (highlight.js).
|
|
||||||
*
|
|
||||||
* @param {MarkedOptions} markedOpts Options for the marked Markdown renderer.
|
|
||||||
* @param {KatexOptions} katexOpts Options for the KaTeX math renderer.
|
|
||||||
*/
|
|
||||||
export default (markedOpts: MarkedOptions = {}, katexOpts: KatexOptions = {}) => {
|
|
||||||
markedOpts = { highlight, ...markedOpts }
|
|
||||||
katexOpts = { strict: false, throwOnError: false, ...katexOpts }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given *markdown* into HTML.
|
|
||||||
*/
|
|
||||||
return (markdown: string): string => {
|
|
||||||
const [text, math] = extractMath(markdown)
|
|
||||||
const html = marked.parse(text, markedOpts)
|
|
||||||
|
|
||||||
const mathHtml = math.map(({ value, displayMode }) => {
|
|
||||||
return katex.renderToString(value, { ...katexOpts, displayMode })
|
|
||||||
})
|
|
||||||
return restoreMath(html, mathHtml)
|
|
||||||
}
|
|
||||||
}
|
|
229
src/renderer.ts
229
src/renderer.ts
|
@ -1,229 +0,0 @@
|
||||||
// This code is originally based on notebookjs 0.4.2 distributed under the MIT license.
|
|
||||||
import { ElementCreator, HTMLElement } from './elementCreator'
|
|
||||||
import { callableObject, escapeHTML, identity } from './internal/utils'
|
|
||||||
import {
|
|
||||||
Cell,
|
|
||||||
CellType,
|
|
||||||
CodeCell,
|
|
||||||
DisplayData,
|
|
||||||
Error as NbError,
|
|
||||||
ExecuteResult,
|
|
||||||
MarkdownCell,
|
|
||||||
Notebook,
|
|
||||||
Output,
|
|
||||||
OutputType,
|
|
||||||
RawCell,
|
|
||||||
Stream as NbStream,
|
|
||||||
} from './nbformat'
|
|
||||||
|
|
||||||
|
|
||||||
export type Options = {
|
|
||||||
/**
|
|
||||||
* An object with additional data renderers indexed by a media type.
|
|
||||||
*/
|
|
||||||
dataRenderers?: DataRenderers,
|
|
||||||
/**
|
|
||||||
* An array of the supported media types in the priority order. When a cell
|
|
||||||
* 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
|
|
||||||
* `Object.keys({ ...dataRenderers, ...builtinRenderers })`.
|
|
||||||
*/
|
|
||||||
dataRenderersOrder?: string[],
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* escape special characters unsafe for HTML (ansi_up does it implicitly)!
|
|
||||||
*/
|
|
||||||
ansiCodesRenderer?: (text: string) => string,
|
|
||||||
/**
|
|
||||||
* A function for highlighting the given source *code*, it should return an
|
|
||||||
* HTML string. It gets the text from the cell as-is, without prior escaping,
|
|
||||||
* so it must escape special characters unsafe for HTML (highlight.js does it
|
|
||||||
* implicitly)!
|
|
||||||
*/
|
|
||||||
codeHighlighter?: (code: string, lang: string) => string,
|
|
||||||
/**
|
|
||||||
* A function for converting the given Markdown source to HTML.
|
|
||||||
*/
|
|
||||||
markdownRenderer?: (markup: string) => string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DataRenderer = (data: string) => HTMLElement
|
|
||||||
|
|
||||||
type DataRenderers = { [mediaType: string]: DataRenderer }
|
|
||||||
|
|
||||||
|
|
||||||
function joinText (text: string | string[]): string {
|
|
||||||
return Array.isArray(text) ? text.map(joinText).join('') : text
|
|
||||||
}
|
|
||||||
|
|
||||||
function coalesceStreams (outputs: Output[]): Output[] {
|
|
||||||
if (!outputs.length) { return outputs }
|
|
||||||
|
|
||||||
let last = outputs[0]
|
|
||||||
const newOutputs = [last]
|
|
||||||
|
|
||||||
for (const output of outputs.slice(1)) {
|
|
||||||
if (output.output_type === 'stream' && last.output_type === 'stream' && output.name === last.name) {
|
|
||||||
last.text = last.text.concat(...output.text)
|
|
||||||
} else {
|
|
||||||
newOutputs.push(output)
|
|
||||||
last = output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newOutputs
|
|
||||||
}
|
|
||||||
|
|
||||||
function executionCountAttrs ({ execution_count: count }: CodeCell): { [k: string]: string } {
|
|
||||||
return count ? {
|
|
||||||
'data-execution-count': String(count),
|
|
||||||
// Only for backward compatibility with notebook.js.
|
|
||||||
'data-prompt-number': String(count),
|
|
||||||
} : {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function notebookLanguage ({ metadata: meta }: Notebook): string {
|
|
||||||
return (meta.language_info && meta.language_info.name) || 'python'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a Notebook renderer function with the given options. It returns
|
|
||||||
* a "callable object" of renderer functions for each Notebook's AST node.
|
|
||||||
* You can easily replace any of the renderer functions to modify behaviour
|
|
||||||
* of the renderer.
|
|
||||||
*
|
|
||||||
* @param {ElementCreator} elementCreator The function that will be used for
|
|
||||||
* building all HTML elements.
|
|
||||||
* @param {Options} opts
|
|
||||||
*/
|
|
||||||
function buildRenderer (elementCreator: ElementCreator, opts: Options = {}) {
|
|
||||||
const renderMarkdown = opts.markdownRenderer || identity
|
|
||||||
const renderAnsiCodes = opts.ansiCodesRenderer || escapeHTML
|
|
||||||
const highlightCode = opts.codeHighlighter || escapeHTML
|
|
||||||
|
|
||||||
const el = elementCreator
|
|
||||||
const el2 = (tag: string, classes: string[]) => (data: string) => el(tag, classes, data)
|
|
||||||
|
|
||||||
const embeddedImageEl = (format: string) => (data: string) => 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 dataRenderersOrder before the built-in
|
|
||||||
// renderers and at the same time allow to override any built-in renderer.
|
|
||||||
const dataRenderers: DataRenderers = {
|
|
||||||
...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) => {
|
|
||||||
return dataRenderersOrder.find(type => output.data[type] && dataRenderers[type])
|
|
||||||
}
|
|
||||||
|
|
||||||
const r = callableObject('Notebook', {
|
|
||||||
Notebook: (notebook: Notebook): HTMLElement => {
|
|
||||||
const children = notebook.cells.map(cell => r.Cell(cell, notebook))
|
|
||||||
// Class "worksheet" is for backward compatibility with notebook.js.
|
|
||||||
return el('div', ['notebook', 'worksheet'], children)
|
|
||||||
},
|
|
||||||
|
|
||||||
Cell: (cell: Cell, notebook: Notebook): HTMLElement => {
|
|
||||||
switch (cell.cell_type) {
|
|
||||||
case CellType.Code: return r.CodeCell(cell, notebook)
|
|
||||||
case CellType.Markdown: return r.MarkdownCell(cell, notebook)
|
|
||||||
case CellType.Raw: return r.RawCell(cell, notebook)
|
|
||||||
default: return el('div', [], '<!-- Unsupported cell type -->')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
MarkdownCell: (cell: MarkdownCell, _notebook: Notebook): HTMLElement => {
|
|
||||||
return el('div', ['cell', 'markdown-cell'], renderMarkdown(joinText(cell.source)))
|
|
||||||
},
|
|
||||||
|
|
||||||
RawCell: (cell: RawCell, _notebook: Notebook): HTMLElement => {
|
|
||||||
return el('div', ['cell', 'raw-cell'], joinText(cell.source))
|
|
||||||
},
|
|
||||||
|
|
||||||
CodeCell: (cell: CodeCell, notebook: Notebook): HTMLElement => {
|
|
||||||
const source = cell.source.length > 0
|
|
||||||
? r.Source(cell, notebook)
|
|
||||||
: el('div')
|
|
||||||
|
|
||||||
const outputs = coalesceStreams(cell.outputs || [])
|
|
||||||
.map(output => r.Output(output, cell))
|
|
||||||
|
|
||||||
return el('div', ['cell', 'code-cell'], [source, ...outputs])
|
|
||||||
},
|
|
||||||
|
|
||||||
Source: (cell: CodeCell, notebook: Notebook): HTMLElement => {
|
|
||||||
const lang = notebookLanguage(notebook)
|
|
||||||
const html = highlightCode(joinText(cell.source), lang)
|
|
||||||
|
|
||||||
const codeEl = el('code', { 'class': `lang-${lang}`, 'data-language': lang }, html)
|
|
||||||
const preEl = el('pre', [], [codeEl])
|
|
||||||
|
|
||||||
// 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): HTMLElement => {
|
|
||||||
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): HTMLElement => {
|
|
||||||
const type = resolveDataType(output)
|
|
||||||
if (type) {
|
|
||||||
return dataRenderers[type](joinText(output.data[type]))
|
|
||||||
}
|
|
||||||
return el('div', ['empty-output'])
|
|
||||||
},
|
|
||||||
|
|
||||||
ExecuteResult: (output: ExecuteResult): HTMLElement => {
|
|
||||||
const type = resolveDataType(output)
|
|
||||||
if (type) {
|
|
||||||
return dataRenderers[type](joinText(output.data[type]))
|
|
||||||
}
|
|
||||||
return el('div', ['empty-output'])
|
|
||||||
},
|
|
||||||
|
|
||||||
Error: (error: NbError): HTMLElement => {
|
|
||||||
const html = renderAnsiCodes(error.traceback.join('\n'))
|
|
||||||
// Class "pyerr" is for backward compatibility with notebook.js.
|
|
||||||
return el('pre', ['error', 'pyerr'], html)
|
|
||||||
},
|
|
||||||
|
|
||||||
Stream: (stream: NbStream): HTMLElement => {
|
|
||||||
const html = renderAnsiCodes(joinText(stream.text))
|
|
||||||
return el('pre', [stream.name], html)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NbRenderer = ReturnType<typeof buildRenderer>
|
|
||||||
|
|
||||||
export default buildRenderer
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { callableObject, escapeHTML, identity } from '@/internal/utils'
|
|
||||||
|
|
||||||
|
|
||||||
describe('.callableObject', () => {
|
|
||||||
|
|
||||||
describe('returned value', () => {
|
|
||||||
const template = {
|
|
||||||
str: 'allons-y!',
|
|
||||||
func1: jest.fn().mockReturnValue(1),
|
|
||||||
func2: jest.fn().mockReturnValue(2),
|
|
||||||
}
|
|
||||||
const subject = callableObject('func1', template)
|
|
||||||
|
|
||||||
it('is a function that calls the specified template function', () => {
|
|
||||||
expect( subject ).toBeInstanceOf(Function)
|
|
||||||
expect( subject('a', 'b') ).toBe(1)
|
|
||||||
expect( template.func1 ).toBeCalledWith('a', 'b')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('is not the same function as the specified template function', () => {
|
|
||||||
expect( subject ).not.toBe(template.func1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has all enumerable properties of the given template', () => {
|
|
||||||
expect( subject )
|
|
||||||
.toHaveProperty('str', template.str)
|
|
||||||
.toHaveProperty('func1', template.func1)
|
|
||||||
.toHaveProperty('func2', template.func2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
describe('.escapeHTML', () => {
|
|
||||||
test.each([
|
|
||||||
/* input | expected */
|
|
||||||
['&' , '&' ],
|
|
||||||
['<' , '<' ],
|
|
||||||
['>' , '>' ],
|
|
||||||
['Hey >_<! <<&>>', 'Hey >_<! <<&>>'],
|
|
||||||
])('"%s" -> "%s"', (input, expected) => {
|
|
||||||
expect( escapeHTML(input) ).toEqual(expected)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
describe('.identity', () => {
|
|
||||||
it('returns the first given argument', () => {
|
|
||||||
expect( identity('a', 'b') ).toBe('a')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
if (received.cloneNode) {
|
||||||
received = received.cloneNode(true) as HTMLElement
|
received = received.cloneNode(true) as HTMLElement
|
||||||
|
}
|
||||||
|
|
||||||
if (opts && opts.ignoreAttrs) {
|
if (opts?.ignoreAttrs) {
|
||||||
clearAttributes(received)
|
clearAttributes(received)
|
||||||
}
|
}
|
||||||
filterWildcardChildren(received, expected)
|
filterWildcardChildren(received, expected)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// 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": ".",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
76
tsconfig.base.json
Normal file
76
tsconfig.base.json
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
|
"jsxFactory": "JSX.createElement", /* Specify the JSX factory function to use when targeting react JSX emit, e.g. React.createElement or h. */
|
||||||
|
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
// "outDir": "./lib", /* Redirect output structure to the directory. */
|
||||||
|
// "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
"composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
"strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
"strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "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'. */
|
||||||
|
"*": ["../../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. */
|
||||||
|
"typeRoots": [ /* List of folders to include type definitions from. */
|
||||||
|
"./types",
|
||||||
|
"./node_modules/@types"
|
||||||
|
],
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
"resolveJsonModule": true, /* Include modules imported with .json extension. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
// AST transformers used by ttypescript.
|
||||||
|
"plugins": [
|
||||||
|
{ "transform": "ts-transformer-export-default-name" },
|
||||||
|
{ "transform": "ts-transformer-inline-file/transformer" },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,77 +1,8 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"files": [],
|
||||||
/* Basic Options */
|
"references": [
|
||||||
// "incremental": true, /* Enable incremental compilation */
|
{ "path": "./packages/ipynb2html-core" },
|
||||||
"target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
{ "path": "./packages/ipynb2html" },
|
||||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
{ "path": "./packages/ipynb2html-cli" },
|
||||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
|
||||||
"allowJs": true, /* Allow javascript files to be compiled. */
|
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
|
||||||
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
|
||||||
"jsxFactory": "JSX.createElement", /* Specify the JSX factory function to use when targeting react JSX emit, e.g. React.createElement or h. */
|
|
||||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
|
||||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
|
||||||
"sourceMap": true, /* Generates corresponding '.map' file. */
|
|
||||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
|
||||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
|
||||||
// "rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
|
||||||
// "composite": true, /* Enable project compilation */
|
|
||||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
|
||||||
// "removeComments": true, /* Do not emit comments to output. */
|
|
||||||
// "noEmit": true, /* Do not emit outputs. */
|
|
||||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
|
||||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
|
||||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
|
||||||
|
|
||||||
/* Strict Type-Checking Options */
|
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
|
||||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
|
||||||
"strictNullChecks": true, /* Enable strict null checks. */
|
|
||||||
"strictFunctionTypes": true, /* Enable strict checking of function types. */
|
|
||||||
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
|
||||||
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
|
||||||
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
|
||||||
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
|
||||||
|
|
||||||
/* Additional Checks */
|
|
||||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
|
||||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
|
||||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
|
||||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
|
||||||
|
|
||||||
/* Module Resolution Options */
|
|
||||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
|
||||||
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
|
|
||||||
"paths": { /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
|
||||||
"*": ["../types/*"],
|
|
||||||
"@/*": ["./*"], /* resolve all modules prefixed with '@/' from 'src' directory */
|
|
||||||
},
|
|
||||||
// "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. */
|
|
||||||
"./types",
|
|
||||||
"./node_modules/@types"
|
|
||||||
],
|
|
||||||
// "types": [], /* Type declaration files to be included in compilation. */
|
|
||||||
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
|
||||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
|
||||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
|
||||||
"resolveJsonModule": true, /* Include modules imported with .json extension. */
|
|
||||||
|
|
||||||
/* Source Map Options */
|
|
||||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
|
||||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
|
||||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
|
||||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
|
||||||
|
|
||||||
/* Experimental Options */
|
|
||||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
|
||||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src",
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
16
tsconfig.test.json
Normal file
16
tsconfig.test.json
Normal 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/*"],
|
||||||
|
"~/*": ["../../../*"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
7
types/nodom/index.d.ts
vendored
7
types/nodom/index.d.ts
vendored
|
@ -1,7 +0,0 @@
|
||||||
// TODO: Remove after https://github.com/DefinitelyTyped/DefinitelyTyped/pull/38248 is released.
|
|
||||||
|
|
||||||
export * from './lib/document';
|
|
||||||
export * from './lib/element';
|
|
||||||
export * from './lib/node';
|
|
||||||
export * from './lib/render';
|
|
||||||
export * from './lib/textnode';
|
|
1
types/nodom/lib/attributes.d.ts
vendored
1
types/nodom/lib/attributes.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
export class Attributes {}
|
|
13
types/nodom/lib/classlist.d.ts
vendored
13
types/nodom/lib/classlist.d.ts
vendored
|
@ -1,13 +0,0 @@
|
||||||
import { HTMLElement } from './element';
|
|
||||||
|
|
||||||
export class ClassList extends Array {
|
|
||||||
constructor(element: HTMLElement);
|
|
||||||
|
|
||||||
add(className: string): void;
|
|
||||||
|
|
||||||
contains(className: string): boolean;
|
|
||||||
|
|
||||||
remove(className: string): void;
|
|
||||||
|
|
||||||
reset(className?: string): void;
|
|
||||||
}
|
|
1
types/nodom/lib/dataset.d.ts
vendored
1
types/nodom/lib/dataset.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
export class Dataset {}
|
|
31
types/nodom/lib/document.d.ts
vendored
31
types/nodom/lib/document.d.ts
vendored
|
@ -1,31 +0,0 @@
|
||||||
import { HTMLElement } from './element';
|
|
||||||
import { TextNode } from './textnode';
|
|
||||||
|
|
||||||
export class Document {
|
|
||||||
readonly nodeType: number;
|
|
||||||
body: HTMLElement;
|
|
||||||
documentElement: HTMLElement;
|
|
||||||
head: HTMLElement;
|
|
||||||
implementation: {
|
|
||||||
createHTMLDocument(textContent: string): Document;
|
|
||||||
hasFeature(feature: string, version?: string): boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
createElement(tagName: string): HTMLElement;
|
|
||||||
|
|
||||||
createElementNS(ns: string | null, tagName: string): HTMLElement;
|
|
||||||
|
|
||||||
createDocumentFragment(): HTMLElement;
|
|
||||||
|
|
||||||
createTextNode(text: string): TextNode;
|
|
||||||
|
|
||||||
getElementsByTagName(tagName: string): HTMLElement[];
|
|
||||||
|
|
||||||
getElementsByClassName(classNames: string): HTMLElement[];
|
|
||||||
|
|
||||||
getElementById(id: string): HTMLElement | null;
|
|
||||||
|
|
||||||
querySelector(selectors: string): HTMLElement | null;
|
|
||||||
|
|
||||||
querySelectorAll(selectors: string): HTMLElement[];
|
|
||||||
}
|
|
56
types/nodom/lib/element.d.ts
vendored
56
types/nodom/lib/element.d.ts
vendored
|
@ -1,56 +0,0 @@
|
||||||
import { Attributes } from './attributes';
|
|
||||||
import { ClassList } from './classlist';
|
|
||||||
import { Dataset } from './dataset';
|
|
||||||
import { Node } from './node';
|
|
||||||
import { CSSStyleDeclaration } from './style';
|
|
||||||
|
|
||||||
export class HTMLElement extends Node {
|
|
||||||
attributes: Attributes;
|
|
||||||
readonly classList: ClassList;
|
|
||||||
className: string;
|
|
||||||
dataset: Dataset;
|
|
||||||
innerHTML: string;
|
|
||||||
readonly isVoidEl: boolean;
|
|
||||||
readonly nextSibling: Node | undefined;
|
|
||||||
nodeType: number;
|
|
||||||
readonly outerHTML: string;
|
|
||||||
style: CSSStyleDeclaration;
|
|
||||||
tagName: string;
|
|
||||||
textContent: string;
|
|
||||||
|
|
||||||
constructor(options: { [key: string]: string });
|
|
||||||
|
|
||||||
appendChild<T extends Node>(child: T): T | undefined;
|
|
||||||
|
|
||||||
insertBefore<T extends Node>(child: T, before: Node | null): T | undefined;
|
|
||||||
|
|
||||||
removeChild(child: Node): void;
|
|
||||||
|
|
||||||
replaceChild(child: Node, replace: Node): void;
|
|
||||||
|
|
||||||
getAttribute(attr: string): string | undefined;
|
|
||||||
|
|
||||||
setAttribute(attr: string, value: string): void;
|
|
||||||
|
|
||||||
getElementsByClassName(classNames: string): HTMLElement[];
|
|
||||||
|
|
||||||
getElementsByTagName(tagName: string): HTMLElement[];
|
|
||||||
|
|
||||||
matches(query: string): boolean;
|
|
||||||
|
|
||||||
querySelector(selectors: string): HTMLElement | null;
|
|
||||||
|
|
||||||
querySelectorAll(selectors: string): HTMLElement[];
|
|
||||||
|
|
||||||
addEventListener(): void;
|
|
||||||
|
|
||||||
removeEventListener(): void;
|
|
||||||
|
|
||||||
blur(): void;
|
|
||||||
|
|
||||||
click(): void;
|
|
||||||
|
|
||||||
focus(): void;
|
|
||||||
|
|
||||||
render(inner?: boolean): string;
|
|
||||||
}
|
|
3
types/nodom/lib/navigator.d.ts
vendored
3
types/nodom/lib/navigator.d.ts
vendored
|
@ -1,3 +0,0 @@
|
||||||
export class Navigator {
|
|
||||||
userAgent: string;
|
|
||||||
}
|
|
10
types/nodom/lib/node.d.ts
vendored
10
types/nodom/lib/node.d.ts
vendored
|
@ -1,10 +0,0 @@
|
||||||
export class Node {
|
|
||||||
childNodes: Node[];
|
|
||||||
readonly children: Node[];
|
|
||||||
readonly firstChild: Node | undefined;
|
|
||||||
readonly lastChild: Node | undefined;
|
|
||||||
readonly nodeName: string;
|
|
||||||
readonly nodeValue: any;
|
|
||||||
|
|
||||||
cloneNode(deep?: boolean): Node;
|
|
||||||
}
|
|
6
types/nodom/lib/render.d.ts
vendored
6
types/nodom/lib/render.d.ts
vendored
|
@ -1,6 +0,0 @@
|
||||||
import { HTMLElement } from './element';
|
|
||||||
|
|
||||||
export function render(
|
|
||||||
view: HTMLElement | { el: HTMLElement },
|
|
||||||
inner: boolean,
|
|
||||||
): string;
|
|
9
types/nodom/lib/style.d.ts
vendored
9
types/nodom/lib/style.d.ts
vendored
|
@ -1,9 +0,0 @@
|
||||||
export class CSSStyleDeclaration {
|
|
||||||
cssText: string;
|
|
||||||
|
|
||||||
setProperty(propertyName: string, value: string, priority?: string): void;
|
|
||||||
|
|
||||||
valueOf(): this;
|
|
||||||
|
|
||||||
setValue(style: string): void;
|
|
||||||
}
|
|
11
types/nodom/lib/textnode.d.ts
vendored
11
types/nodom/lib/textnode.d.ts
vendored
|
@ -1,11 +0,0 @@
|
||||||
import { Node } from './node';
|
|
||||||
|
|
||||||
export class TextNode extends Node {
|
|
||||||
readonly nodeType: number;
|
|
||||||
readonly nodeValue: string;
|
|
||||||
textContent: string;
|
|
||||||
|
|
||||||
constructor(text: string);
|
|
||||||
|
|
||||||
render(): string;
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue