Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

49 changed files with 2135 additions and 2538 deletions

View file

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

3
.github/FUNDING.yml vendored
View file

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

View file

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

1
.gitignore vendored
View file

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

54
.travis.yml Normal file
View file

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

View file

@ -2,15 +2,15 @@
:npm-name: ipynb2html :npm-name: ipynb2html
:gh-name: jirutka/{npm-name} :gh-name: jirutka/{npm-name}
:gh-branch: master :gh-branch: master
:version: 0.3.0 :version: 0.1.0
:ansiup-version: 5.0.1 :ansiup-version: 4.0.4
:hljs-version: 10.7.3 :hljs-version: 9.15.10
:katex-version: 0.13.11 :katex-version: 0.11.1
:marked-version: 2.0.7 :marked-version: 0.7.0
: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://github.com/{gh-name}/workflows/CI/badge.svg[CI Status, link=https://github.com/{gh-name}/actions?query=workflow%3A%22CI%22] image:https://travis-ci.com/{gh-name}.svg?branch={gh-branch}[Build Status, link="https://travis-ci.com/{gh-name}"]
endif::env-github[] endif::env-github[]
{npm-name} is a converter (renderer) of the https://nbformat.readthedocs.io/en/stable/[Jupyter Notebook Format] 4.0+ to static HTML. {npm-name} is a converter (renderer) of the https://nbformat.readthedocs.io/en/stable/[Jupyter Notebook Format] 4.0+ to static HTML.

View file

@ -7,11 +7,11 @@
<link rel="stylesheet" href="../packages/ipynb2html/dist/notebook.min.css"> <link rel="stylesheet" href="../packages/ipynb2html/dist/notebook.min.css">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/styles/default.min.css" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/default.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<script defer src="../packages/ipynb2html/dist/ipynb2html-full.js" onload="ipynb2html.autoRender();"></script> <script defer src="../packages/ipynb2html/dist/ipynb2html-full.js" onload="ipynb2html.autoRender();"></script>
<style> <style>

View file

@ -7,16 +7,16 @@
<link rel="stylesheet" href="../packages/ipynb2html/dist/notebook.min.css"> <link rel="stylesheet" href="../packages/ipynb2html/dist/notebook.min.css">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/styles/default.min.css" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/default.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/marked@2.0.7/marked.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/marked@0.7.0/marked.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/ansi_up@5.0.1/ansi_up.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/ansi_up@4.0.4/ansi_up.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/highlight.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/highlight.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js" crossorigin="anonymous"></script>
<script defer src="../packages/ipynb2html/dist/ipynb2html.js" onload="ipynb2html.autoRender();"></script> <script defer src="../packages/ipynb2html/dist/ipynb2html.js" onload="ipynb2html.autoRender();"></script>
<style> <style>
html, body { html, body {

View file

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

View file

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

View file

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

View file

@ -26,6 +26,9 @@ export default {
incremental: true, incremental: true,
}, },
}, },
// This is needed for node-license plugin. :(
// https://github.com/ezolenko/rollup-plugin-typescript2#plugins-using-asyncawait
objectHashIgnoreUnknownHack: true,
clean: true, clean: true,
}), }),
// Make node builtins external. // Make node builtins external.

View file

@ -3,17 +3,14 @@ import minimist from 'minimist'
import minimistOptions from 'minimist-options' import minimistOptions from 'minimist-options'
import { Document } from 'nodom' import { Document } from 'nodom'
import { exit } from 'process' import { exit } from 'process'
import { $INLINE_FILE, $INLINE_JSON } from 'ts-transformer-inline-file' import { $INLINE_JSON } from 'ts-transformer-inline-file'
import * as ipynb2html from 'ipynb2html' import * as ipynb2html from 'ipynb2html'
import renderPage from './page' import renderPage from './page'
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { version, bugs: bugsUrl } = $INLINE_JSON('../package.json') const { version, bugs: bugsUrl } = $INLINE_JSON('../package.json')
const notebookCss = $INLINE_FILE('../../ipynb2html/styles/notebook.css')
const pageCss = $INLINE_FILE('./page.css')
const progName = 'ipynb2html' const progName = 'ipynb2html'
const helpMsg = `\ const helpMsg = `\
@ -28,14 +25,7 @@ Arguments:
Options: Options:
-d --debug Print debug messages. -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. -h --help Show this message and exit.
-V --version Print version and exit. -V --version Print version and exit.
Exit Codes: Exit Codes:
@ -49,14 +39,9 @@ function logErr (msg: string): void {
console.error(`${progName}: ${msg}`) console.error(`${progName}: ${msg}`)
} }
function arrify <T> (obj: T | T[]): T[] {
return Array.isArray(obj) ? obj : [obj]
}
function parseCliArgs (argv: string[]) { function parseCliArgs (argv: string[]) {
const opts = minimist(argv, minimistOptions({ const opts = minimist(argv, minimistOptions({
debug: { alias: 'd', type: 'boolean' }, debug: { alias: 'd', type: 'boolean' },
style: { alias: 's', type: 'string', default: '@default' },
version: { alias: 'V', type: 'boolean' }, version: { alias: 'V', type: 'boolean' },
help: { alias: 'h', type: 'boolean' }, help: { alias: 'h', type: 'boolean' },
arguments: 'string', arguments: 'string',
@ -88,35 +73,24 @@ function parseCliArgs (argv: string[]) {
const [input, output] = opts._ const [input, output] = opts._
return { return {
styles: arrify(opts.style).join(',').split(/,\s*/),
debug: opts.debug as boolean, debug: opts.debug as boolean,
input: input === '-' ? 0 : input, // 0 = stdin input: input === '-' ? 0 : input, // 0 = stdin
output, output,
} }
} }
function loadStyle (name: string): string {
switch (name) {
case '@base': return notebookCss
case '@default': return pageCss + notebookCss
default: return fs.readFileSync(name, 'utf8')
}
}
export default (argv: string[]): void => { export default (argv: string[]): void => {
const opts = parseCliArgs(argv) const opts = parseCliArgs(argv)
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const notebook = JSON.parse(fs.readFileSync(opts.input, 'utf-8')) const notebook = JSON.parse(fs.readFileSync(opts.input, 'utf-8'))
const style = opts.styles.map(loadStyle).join('\n')
const title = ipynb2html.readNotebookTitle(notebook) || 'Notebook' const title = ipynb2html.readNotebookTitle(notebook) ?? 'Notebook'
const renderNotebook = ipynb2html.createRenderer(new Document()) const renderNotebook = ipynb2html.createRenderer(new Document())
const contents = renderNotebook(notebook).outerHTML const contents = renderNotebook(notebook).outerHTML
const html = renderPage({ contents, title, style }) const html = renderPage(contents, title)
if (opts.output) { if (opts.output) {
fs.writeFileSync(opts.output, html) fs.writeFileSync(opts.output, html)
@ -127,7 +101,7 @@ export default (argv: string[]): void => {
if (opts.debug) { if (opts.debug) {
console.debug(err) console.debug(err)
} else { } else {
logErr((err as Error).message) logErr(err.message)
} }
return exit(1) return exit(1)
} }

View file

@ -13,32 +13,6 @@ pre {
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
margin-top: 1.6em; 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 { table {

View file

@ -1,30 +1,26 @@
import { version } from 'ipynb2html' import { $INLINE_FILE } from 'ts-transformer-inline-file'
const notebookCss = $INLINE_FILE('../../ipynb2html/styles/notebook.css')
const pageCss = $INLINE_FILE('./page.css')
export type Options = { export default (contents: string, title: string): string => `\
contents: string,
title: string,
style: string,
}
export default ({ contents, title, style }: Options): string => `\
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="initial-scale=1"> <meta name="viewport" content="initial-scale=1">
<meta name="generator" content="ipynb2html ${version}">
<title>${title}</title> <title>${title}</title>
<link rel="stylesheet" <link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.3/build/styles/default.min.css" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/default.min.css"
integrity="sha384-s4RLYRjGGbVqKOyMGGwfxUTMOO6D7r2eom7hWZQ6BjK2Df4ZyfzLXEkonSm0KLIQ" integrity="sha384-ut3ELVx81ErZQaaMTknSmGb0CEGAKoBFTamRcY1ddG4guN0aoga4C+B6B7Kv1Ll1"
crossorigin="anonymous"> crossorigin="anonymous">
<link rel="stylesheet" <link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css" href="https://cdn.jsdelivr.net/npm/katex@0.11.0/dist/katex.min.css"
integrity="sha384-Um5gpz1odJg5Z4HAmzPtgZKdTBHZdw8S29IecapCSB31ligYPhHQZMIlWLYQGVoc" integrity="sha384-BdGj8xC2eZkQaxoQ8nSLefg4AV4/AwB3Fj+8SUSo7pnKP6Eoy18liIKTPn9oBYNG"
crossorigin="anonymous"> crossorigin="anonymous">
<style> <style>
${style.replace(/\n\n/g, '\n').replace(/\n$/, '').replace(/^/gm, ' ')} ${(pageCss + notebookCss).slice(0, -1).replace(/\n\n/g, '\n').replace(/^/gm, ' ')}
</style> </style>
</head> </head>
<body> <body>

View file

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

View file

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

View file

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

View file

@ -1,27 +1,22 @@
const htmlEntities: Record<string, string> = { const htmlEntities = {
'&': '&amp;', '&': '&amp;',
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
} }
type Callable = (...args: any[]) => any type CallableConstructor = new <T> () => T extends { __call__: Function }
type CallableConstructor = new <T> () => T extends { __call__: Callable }
? T['__call__'] ? T['__call__']
: 'subclass does not implement method __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 ( export const CallableInstance: CallableConstructor = function Callable (
this: object, this: object,
): Callable { ): Function {
const func = this.constructor.prototype.__call__ as Callable const func = this.constructor.prototype.__call__ as Function
const cls = function (...args: any[]) { const cls = function (...args: any[]) {
return func.apply(cls, args) as unknown return func.apply(cls, args)
} }
Object.setPrototypeOf(cls, this.constructor.prototype) Object.setPrototypeOf(cls, this.constructor.prototype)
@ -38,16 +33,13 @@ export const CallableInstance: CallableConstructor = function Callable (
return cls return cls
} as any } as any
CallableInstance.prototype = Object.create(Function.prototype) CallableInstance.prototype = Object.create(Function.prototype)
/* eslint-enable @typescript-eslint/no-unsafe-assignment,
@typescript-eslint/no-unsafe-member-access,
@typescript-eslint/ban-types */
/** /**
* Escapes characters with special meaning in HTML with the corresponding * Escapes characters with special meaning in HTML with the corresponding
* HTML entities. * HTML entities.
*/ */
export function escapeHTML (str: string): string { export function escapeHTML (str: string): string {
return str.replace(/[&<>]/g, c => htmlEntities[c]) return str.replace(/[&<>]/g, c => (htmlEntities as any)[c])
} }
/** /**

View file

@ -204,6 +204,6 @@ export function extractMath (text: string): [string, MathExpression[]] {
*/ */
export function restoreMath (text: string, math: string[]): string { export function restoreMath (text: string, math: string[]): string {
return text return text
.replace(/@@([1-9][0-9]*)@@/g, (_, n) => math[Number(n) - 1] ?? '') .replace(/@@([1-9][0-9]*)@@/g, (_, n) => math[Number(n) - 1])
.replace(/@@0(\d+)@@/g, (_, n) => `@@${n}@@`) .replace(/@@0(\d+)@@/g, (_, n) => `@@${n}@@`)
} }

View file

@ -1,79 +1,78 @@
// 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,
} }
@ -90,46 +89,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 & {
@ -139,38 +138,38 @@ export interface CodeCell extends BaseCell {
/** Whether the cell's output is scrolled, unscrolled, or autoscrolled. */ /** Whether the cell's output is scrolled, unscrolled, or autoscrolled. */
scrolled?: true | false | 'auto', scrolled?: true | false | 'auto',
} },
/** Execution, display, or stream outputs. */ /** Execution, display, or stream outputs. */
outputs: Output[] outputs: Output[],
/** The code cell's prompt number. Will be null if the cell has not been run. */ /** The code cell's prompt number. Will be null if the cell has not been run. */
execution_count: number | null execution_count: number | null,
} }
export interface CellMetadata { export interface CellMetadata {
/** Official Jupyter Metadata for Raw Cells */ /** Official Jupyter Metadata for Raw Cells */
jupyter?: { [k: string]: any } jupyter?: { [k: string]: any },
/** /**
* The cell's name. If present, must be a non-empty string. Cell names are expected to be unique * The cell's name. If present, must be a non-empty string. Cell names are expected to be unique
* across all the cells in a given notebook. This criterion cannot be checked by the json schema * across all the cells in a given notebook. This criterion cannot be checked by the json schema
* and must be established by an additional check. * and must be established by an additional check.
*/ */
name?: string name?: string,
/** The cell's tags. Tags must be unique, and must not contain commas. */ /** The cell's tags. Tags must be unique, and must not contain commas. */
tags?: string[] tags?: string[],
/** Extra properties. */ /** Extra properties. */
[key: string]: any [key: string]: any,
} }
export interface MediaAttachments { export interface MediaAttachments {
/** The attachment's data stored as a mimebundle. */ /** The attachment's data stored as a mimebundle. */
[filename: string]: MimeBundle [filename: string]: MimeBundle,
} }
@ -189,62 +188,62 @@ export enum OutputType {
export interface ExecuteResult { export interface ExecuteResult {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.ExecuteResult output_type: OutputType.ExecuteResult,
/** A result's prompt number. */ /** A result's prompt number. */
execution_count: number | null execution_count: number | null,
/** A mime-type keyed dictionary of data */ /** A mime-type keyed dictionary of data */
data: MimeBundle data: MimeBundle,
/** Cell output metadata. */ /** Cell output metadata. */
metadata: { metadata: {
[k: string]: any, [k: string]: any,
} },
} }
/** Data displayed as a result of code cell execution. */ /** Data displayed as a result of code cell execution. */
export interface DisplayData { export interface DisplayData {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.DisplayData output_type: OutputType.DisplayData,
/** A mime-type keyed dictionary of data */ /** A mime-type keyed dictionary of data */
data: MimeBundle data: MimeBundle,
/** Cell output metadata. */ /** Cell output metadata. */
metadata: { metadata: {
[k: string]: any, [k: string]: any,
} },
} }
/** Stream output from a code cell. */ /** Stream output from a code cell. */
export interface StreamOutput { export interface StreamOutput {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.Stream output_type: OutputType.Stream,
/** The name of the stream (stdout, stderr). */ /** The name of the stream (stdout, stderr). */
name: string name: string,
/** The stream's text output, represented as an array of strings. */ /** The stream's text output, represented as an array of strings. */
text: MultilineString text: MultilineString,
} }
/** Output of an error that occurred during code cell execution. */ /** Output of an error that occurred during code cell execution. */
export interface ErrorOutput { export interface ErrorOutput {
/** Type of cell output. */ /** Type of cell output. */
output_type: OutputType.Error output_type: OutputType.Error,
/** The name of the error. */ /** The name of the error. */
ename: string ename: string,
/** The value, or message, of the error. */ /** The value, or message, of the error. */
evalue: string evalue: string,
/** The error's traceback, represented as an array of strings. */ /** The error's traceback, represented as an array of strings. */
traceback: string[] traceback: string[],
} }
@ -253,7 +252,7 @@ export interface ErrorOutput {
export interface MimeBundle { export interface MimeBundle {
/** mimetype output (e.g. text/plain), represented as either an array of strings or a string. */ /** mimetype output (e.g. text/plain), represented as either an array of strings or a string. */
[mediaType: string]: MultilineString [mediaType: string]: MultilineString,
} }
export type MultilineString = string | string[] export type MultilineString = string | string[]

View file

@ -147,7 +147,6 @@ class NbRenderer <TElement> extends CallableInstance<NbRenderer<TElement>> {
__call__ (notebook: Notebook): TElement { __call__ (notebook: Notebook): TElement {
return this.render(notebook) return this.render(notebook)
} }
/** /**
* Renders the given Jupyter *notebook*. * Renders the given Jupyter *notebook*.
*/ */
@ -166,11 +165,11 @@ class NbRenderer <TElement> extends CallableInstance<NbRenderer<TElement>> {
} }
renderMarkdownCell (cell: MarkdownCell, _notebook: Notebook): TElement { renderMarkdownCell (cell: MarkdownCell, _notebook: Notebook): TElement {
return this.el('section', ['cell', 'markdown-cell'], this.renderMarkdown(joinText(cell.source))) return this.el('div', ['cell', 'markdown-cell'], this.renderMarkdown(joinText(cell.source)))
} }
renderRawCell (cell: RawCell, _notebook: Notebook): TElement { renderRawCell (cell: RawCell, _notebook: Notebook): TElement {
return this.el('section', ['cell', 'raw-cell'], joinText(cell.source)) return this.el('div', ['cell', 'raw-cell'], joinText(cell.source))
} }
renderCodeCell (cell: CodeCell, notebook: Notebook): TElement { renderCodeCell (cell: CodeCell, notebook: Notebook): TElement {
@ -181,7 +180,7 @@ class NbRenderer <TElement> extends CallableInstance<NbRenderer<TElement>> {
const outputs = coalesceStreams(cell.outputs ?? []) const outputs = coalesceStreams(cell.outputs ?? [])
.map(output => this.renderOutput(output, cell)) .map(output => this.renderOutput(output, cell))
return this.el('section', ['cell', 'code-cell'], [source, ...outputs]) return this.el('div', ['cell', 'code-cell'], [source, ...outputs])
} }
renderSource (cell: CodeCell, notebook: Notebook): TElement { renderSource (cell: CodeCell, notebook: Notebook): TElement {

View file

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

View file

@ -102,11 +102,11 @@ describe('built renderer', () => {
eachMultilineVariant(fixtures.MarkdownCell, 'source', (cell) => { eachMultilineVariant(fixtures.MarkdownCell, 'source', (cell) => {
const source = join(cell.source) const source = join(cell.source)
it('returns section.cell.markdown-cell with the $source converted using markdownRenderer() as content', () => { it('returns div.cell.markdown-cell with the $source converted using markdownRenderer() as content', () => {
expect( renderer.renderMarkdownCell(cell, notebook) ).toHtmlEqual( expect( renderer.renderMarkdownCell(cell, notebook) ).toHtmlEqual(
<section class="cell markdown-cell"> <div class="cell markdown-cell">
{{__html: mockLastResult(markdownRenderer) }} {{__html: mockLastResult(markdownRenderer) }}
</section> </div>
) )
expect( markdownRenderer ).toBeCalledWith(source) expect( markdownRenderer ).toBeCalledWith(source)
}) })
@ -117,11 +117,11 @@ describe('built renderer', () => {
describe('.renderRawCell', () => { describe('.renderRawCell', () => {
eachMultilineVariant(fixtures.RawCell, 'source', (cell) => { eachMultilineVariant(fixtures.RawCell, 'source', (cell) => {
it('returns section.cell.raw-cell with the $source as content', () => { it('returns div.cell.raw-cell with the $source as content', () => {
expect( renderer.renderRawCell(cell, notebook) ).toHtmlEqual( expect( renderer.renderRawCell(cell, notebook) ).toHtmlEqual(
<section class="cell raw-cell"> <div class="cell raw-cell">
{{__html: join(cell.source) }} {{__html: join(cell.source) }}
</section> </div>
) )
}) })
}) })
@ -139,9 +139,9 @@ describe('built renderer', () => {
result = renderer.renderCodeCell(cell, notebook) result = renderer.renderCodeCell(cell, notebook)
}) })
it('returns section.cell.code-cell', () => { it('returns div.cell.code-cell', () => {
expect( result ).toMatchElement( expect( result ).toMatchElement(
<section class="cell code-cell"><Anything /></section> <div class="cell code-cell"><Anything /></div>
) )
}) })
@ -149,7 +149,7 @@ describe('built renderer', () => {
it('returns element with $source rendered using .renderSource() as children[0]', () => { it('returns element with $source rendered using .renderSource() as children[0]', () => {
expect( renderer.renderSource ).toBeCalledWith(cell, notebook) expect( renderer.renderSource ).toBeCalledWith(cell, notebook)
expect( result.children[0] ).toHtmlEqual(mockLastResult(renderer.renderSource)!) expect( result.children[0] ).toHtmlEqual(mockLastResult(renderer.renderSource))
}) })
}) })
@ -291,7 +291,7 @@ describe('built renderer', () => {
it(`returns element with the output rendered using .${funcName}() as the only child`, () => { it(`returns element with the output rendered using .${funcName}() as the only child`, () => {
expect( renderer[funcName] ).toBeCalledWith(output) expect( renderer[funcName] ).toBeCalledWith(output)
expect( result.children ).toHtmlEqual([mockLastResult(renderer[funcName])!]) 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', () => {
@ -437,7 +437,7 @@ describe('built renderer', () => {
it('renders the data using the associated external renderer', () => { it('renders the data using the associated external renderer', () => {
expect( renderer.renderDisplayData(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))
}) })
@ -468,7 +468,7 @@ describe('built renderer', () => {
const output = displayDataWith(mimeBundle) const output = displayDataWith(mimeBundle)
expect( renderer.renderDisplayData(output) ).toHtmlEqual( expect( renderer.renderDisplayData(output) ).toHtmlEqual(
mockLastResult(dataRenderers['text/custom'])! mockLastResult(dataRenderers['text/custom'])
) )
}) })
}) })

View file

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

View file

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

View file

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

View file

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

View file

@ -39,7 +39,6 @@ export function autoRender (opts: NbRendererOpts = {}): void {
document.querySelectorAll(selector).forEach(script => { document.querySelectorAll(selector).forEach(script => {
if (script.textContent && script.parentElement) { if (script.textContent && script.parentElement) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const notebook = JSON.parse(unescapeHTML(script.textContent)) const notebook = JSON.parse(unescapeHTML(script.textContent))
const nbElement = render(notebook) const nbElement = render(notebook)
script.parentElement.replaceChild(nbElement, script) script.parentElement.replaceChild(nbElement, script)

View file

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

View file

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

View file

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

View file

@ -7,14 +7,12 @@ class EmptyRenderer extends marked.Renderer {}
// Override all the EmptyRenderer's methods inherited from marked.Renderer to // Override all the EmptyRenderer's methods inherited from marked.Renderer to
// always return an empty string. // always return an empty string.
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ const RendererProto: any = marked.Renderer.prototype
const RendererProto = marked.Renderer.prototype
for (const prop of Object.getOwnPropertyNames(RendererProto)) { for (const prop of Object.getOwnPropertyNames(RendererProto)) {
if (prop !== 'constructor' && typeof (RendererProto as any)[prop] === 'function') { if (prop !== 'constructor' && typeof RendererProto[prop] === 'function') {
(EmptyRenderer.prototype as any)[prop] = () => '' ;(EmptyRenderer.prototype as any)[prop] = () => ''
} }
} }
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
class MainTitleRenderer extends EmptyRenderer { class MainTitleRenderer extends EmptyRenderer {
_titleFound = false _titleFound = false

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
type Callable = (...args: any[]) => unknown type Callable = (...args: any[]) => any
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 as ReturnType<F>) .map(x => x.value)
} }
export function mockLastResult <F extends Callable> (fn: F): ReturnType<F> | undefined { export function mockLastResult <F extends Callable> (fn: F): ReturnType<F> {
return mockResults(fn).pop() return mockResults(fn).pop() as ReturnType<F>
} }

View file

@ -60,8 +60,7 @@ 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) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access el.innerHTML = child.__html
el.innerHTML = child.__html as string
} else { } else {
el.appendChild(document.createTextNode(String(child))) el.appendChild(document.createTextNode(String(child)))
} }
@ -69,7 +68,6 @@ export function createElement (
return el return el
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(global as any).JSX = { (global as any).JSX = {
createElement, createElement,
} }

View file

@ -7,7 +7,7 @@ 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, T> { interface Matchers<R, T> {
toHtmlEqual: (expected: HTMLElement | string | Array<HTMLElement | string>) => R toHtmlEqual (expected: HTMLElement | string | Array<HTMLElement | string>): R,
} }
} }
} }

View file

@ -11,40 +11,32 @@ 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, T> { 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 = (): Node => AnythingNode export const Anything = () => 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.childNodes.length === 1 && exp.children.length === 1
&& (rec as HTMLElement).innerHTML && rec instanceof HTMLElement
&& rec.innerHTML
) { ) {
if (isWritable(rec, 'innerHTML')) { rec.innerHTML = ''
(rec as HTMLElement).innerHTML = '' rec.children.splice(0, rec.children.length, AnythingNode)
}
rec.childNodes.splice(0, rec.childNodes.length, AnythingNode)
return return
} }
for (let i = 0; i < exp.childNodes.length && i < rec.childNodes.length; i++) { for (let i = 0; i < exp.children.length && i < rec.children.length; i++) {
if (exp.childNodes[i] === AnythingNode) { if (exp.children[i] === AnythingNode) {
rec.childNodes[i] = AnythingNode rec.children[i] = AnythingNode
} else { } else {
filterWildcardChildren(rec.childNodes[i], exp.childNodes[i]) filterWildcardChildren(exp.children[i], rec.children[i])
} }
} }
} }
@ -54,13 +46,11 @@ function clearAttributes (node: Node): void {
node.attributes = {} node.attributes = {}
node.className = '' node.className = ''
} }
node.childNodes.forEach(clearAttributes) node.children.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?.ignoreAttrs) { if (opts?.ignoreAttrs) {
clearAttributes(received) clearAttributes(received)

View file

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

View file

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

View file

@ -1,16 +0,0 @@
{
"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/*"],
"~/*": ["../../../*"],
},
},
}

3618
yarn.lock

File diff suppressed because it is too large Load diff