Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
|
2b662a10cb | ||
|
813a55718e | ||
|
ae9c35a367 | ||
|
3afc120a3b | ||
|
ed9d86b894 | ||
|
9c3002271f | ||
|
b27a8d79f7 | ||
|
eead007e6f | ||
|
dd0f34e7a9 |
25
.github/workflows/ci.yml
vendored
|
@ -26,7 +26,7 @@ jobs:
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
|
||||||
publish:
|
publish-release:
|
||||||
name: Publish to npmjs and GitHub Releases
|
name: Publish to npmjs and GitHub Releases
|
||||||
needs: [test]
|
needs: [test]
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
@ -66,3 +66,26 @@ jobs:
|
||||||
dist/*.tar.gz
|
dist/*.tar.gz
|
||||||
packages/ipynb2html-cli/dist/*.tar.gz
|
packages/ipynb2html-cli/dist/*.tar.gz
|
||||||
packages/ipynb2html-cli/dist/*.zip
|
packages/ipynb2html-cli/dist/*.zip
|
||||||
|
|
||||||
|
deploy-viewer:
|
||||||
|
name: Build and deploy viewer to ipynb.js.org
|
||||||
|
needs: [test]
|
||||||
|
if: github.ref == 'refs/heads/viewer' && github.event_name == 'push' # TODO: replace 'viewer' with 'master'
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # fetch all history to make `git describe` work
|
||||||
|
|
||||||
|
- run: yarn install
|
||||||
|
|
||||||
|
- name: Build ipynb-viewer
|
||||||
|
run: BUILD_DESTDIR=$(pwd)/site yarn workspace ipynb2html-viewer build-site
|
||||||
|
|
||||||
|
- name: Deploy to gh-pages
|
||||||
|
uses: JamesIves/github-pages-deploy-action@releases/v3
|
||||||
|
with:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
BRANCH: gh-pages
|
||||||
|
FOLDER: site
|
||||||
|
CLEAN: true
|
||||||
|
|
1
.vscode/extensions.json
vendored
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
// List of extensions which should be recommended for users of this workspace.
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
|
"cpylua.language-postcss",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"gamunu.vscode-yarn",
|
"gamunu.vscode-yarn",
|
||||||
|
|
5
.vscode/settings.json
vendored
|
@ -8,6 +8,11 @@
|
||||||
"typescript"
|
"typescript"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"files.associations": {
|
||||||
|
// XXX: PostCSS extension doesn't provide as nice autocompletion as
|
||||||
|
// VSCode's native CSS support. But this doesn't understand nested rules...
|
||||||
|
"*.pcss": "css",
|
||||||
|
},
|
||||||
"files.encoding": "utf8",
|
"files.encoding": "utf8",
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
|
|
|
@ -215,6 +215,7 @@ ifndef::npm-readme[]
|
||||||
* 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://postcss.org[PostCSS] for transforming stylesheets to plain CSS
|
||||||
* https://rollupjs.org[Rollup] for building single-file bundles
|
* https://rollupjs.org[Rollup] for building single-file bundles
|
||||||
|
|
||||||
|
|
||||||
|
@ -254,6 +255,7 @@ 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}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}cpylua.language-postcss[language-postcss]
|
||||||
* 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]
|
||||||
|
|
||||||
|
|
16
package.json
|
@ -3,11 +3,11 @@
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ttsc --build",
|
"build": "ttsc --build && wsrun --exclude-missing build-except-ts",
|
||||||
"bundle": "wsrun --exclude-missing bundle",
|
"build-ts": "ttsc --build",
|
||||||
"clean": "rimraf coverage/ dist/ lib/ .eslintcache *.log && wsrun clean",
|
"clean": "rimraf coverage/ dist/ lib/ .eslintcache *.log && wsrun clean",
|
||||||
"lint": "eslint --cache --ext .ts,.tsx,.js .",
|
"lint": "eslint --cache --ext .ts,.tsx,.js .",
|
||||||
"postinstall": "patch-package && run-s build",
|
"postinstall": "patch-package && run-s build-ts",
|
||||||
"publish-all": "wsrun --serial publish",
|
"publish-all": "wsrun --serial publish",
|
||||||
"test": "jest --detectOpenHandles --coverage --verbose",
|
"test": "jest --detectOpenHandles --coverage --verbose",
|
||||||
"version": "./scripts/bump-version && git add README.adoc **/package.json",
|
"version": "./scripts/bump-version && git add README.adoc **/package.json",
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
"@babel/preset-env": "^7.10.3",
|
"@babel/preset-env": "^7.10.3",
|
||||||
"@rollup/plugin-babel": "^5.0.3",
|
"@rollup/plugin-babel": "^5.0.3",
|
||||||
"@rollup/plugin-commonjs": "^13.0.0",
|
"@rollup/plugin-commonjs": "^13.0.0",
|
||||||
|
"@rollup/plugin-html": "^0.2.0",
|
||||||
"@rollup/plugin-node-resolve": "^8.0.1",
|
"@rollup/plugin-node-resolve": "^8.0.1",
|
||||||
"@types/dedent": "^0.7.0",
|
"@types/dedent": "^0.7.0",
|
||||||
"@types/jest": "^24.9.1",
|
"@types/jest": "^24.9.1",
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
"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.5",
|
||||||
|
"cssnano": "^4.1.10",
|
||||||
"csso-cli": "^3.0.0",
|
"csso-cli": "^3.0.0",
|
||||||
"dedent": "^0.7.0",
|
"dedent": "^0.7.0",
|
||||||
"eslint": "^7.3.0",
|
"eslint": "^7.3.0",
|
||||||
|
@ -42,17 +44,25 @@
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"jest": "^25.5.4",
|
"jest": "^25.5.4",
|
||||||
"jest-chain": "^1.1.5",
|
"jest-chain": "^1.1.5",
|
||||||
|
"mkdirp": "^1.0.4",
|
||||||
"node-html-parser": "^1.2.19",
|
"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.2",
|
||||||
|
"postcss-inline-svg": "^4.1.0",
|
||||||
|
"postcss-nesting": "^7.0.1",
|
||||||
|
"postcss-sort-media-queries": "^1.6.24",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.17.1",
|
"rollup": "^2.17.1",
|
||||||
"rollup-plugin-add-git-msg": "^1.1.0",
|
"rollup-plugin-add-git-msg": "^1.1.0",
|
||||||
|
"rollup-plugin-conditional": "^3.1.2",
|
||||||
"rollup-plugin-executable": "^1.6.0",
|
"rollup-plugin-executable": "^1.6.0",
|
||||||
|
"rollup-plugin-livereload": "^1.3.0",
|
||||||
"rollup-plugin-node-externals": "^2.2.0",
|
"rollup-plugin-node-externals": "^2.2.0",
|
||||||
"rollup-plugin-node-license": "^0.2.0",
|
"rollup-plugin-node-license": "^0.2.0",
|
||||||
|
"rollup-plugin-postcss": "2.0.6",
|
||||||
|
"rollup-plugin-serve": "^1.0.1",
|
||||||
"rollup-plugin-terser": "^6.1.0",
|
"rollup-plugin-terser": "^6.1.0",
|
||||||
"rollup-plugin-typescript2": "^0.27.1",
|
"rollup-plugin-typescript2": "^0.27.1",
|
||||||
"tar": "^5.0.5",
|
"tar": "^5.0.5",
|
||||||
|
|
|
@ -29,11 +29,14 @@
|
||||||
"src"
|
"src"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ttsc --build",
|
"build": "run-p build-except-ts build-ts",
|
||||||
"bundle": "rollup -c && ./scripts/pack-bundle",
|
"build-bundle": "rollup -c",
|
||||||
|
"build-except-ts": "run-s build-bundle",
|
||||||
|
"build-ts": "ttsc --build",
|
||||||
"clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
|
"clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
|
||||||
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
||||||
"prepublishOnly": "run-s readme2md",
|
"pack-bundle": "./scripts/pack-bundle",
|
||||||
|
"prepublishOnly": "run-s build readme2md pack-bundle",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,10 +24,11 @@
|
||||||
"src"
|
"src"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ttsc --build",
|
"build": "run-s build-ts",
|
||||||
|
"build-ts": "ttsc --build",
|
||||||
"clean": "rimraf coverage/ lib/ .eslintcache .tsbuildinfo",
|
"clean": "rimraf coverage/ lib/ .eslintcache .tsbuildinfo",
|
||||||
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
||||||
"prepublishOnly": "run-s readme2md",
|
"prepublishOnly": "run-p build readme2md",
|
||||||
"test": "jest --detectOpenHandles --coverage --verbose",
|
"test": "jest --detectOpenHandles --coverage --verbose",
|
||||||
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
||||||
"watch-ts": "ttsc --build --watch"
|
"watch-ts": "ttsc --build --watch"
|
||||||
|
|
33
packages/ipynb2html-viewer/index-html.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
export default ({ files: { css: [css], js: [js] }, publicPath }) => `\
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" lang="en">
|
||||||
|
<meta name="viewport" content="initial-scale=1">
|
||||||
|
<title>.ipynb viewer</title>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="${publicPath}${css.fileName}"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/ipynb2html@0.2.0/dist/notebook.min.css"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.15.10/build/styles/default.min.css"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/ipynb2html@0.2.0/dist/ipynb2html-full.min.js" crossorigin="anonymous"></script>
|
||||||
|
<script defer src="${publicPath}${js.fileName}" crossorigin="anonymous" onload="init()"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
This page needs JavaScript. Enable JavaScript in your browser or use a different browser.
|
||||||
|
</noscript>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
54
packages/ipynb2html-viewer/package.json
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"name": "ipynb2html-viewer",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Web viewer of Jupyter Notebooks",
|
||||||
|
"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": [
|
||||||
|
"browser",
|
||||||
|
"converter",
|
||||||
|
"ipython",
|
||||||
|
"jupyter",
|
||||||
|
"notebook",
|
||||||
|
"webapp"
|
||||||
|
],
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"browser": "dist/ipynb-viewer.min.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"lib",
|
||||||
|
"src",
|
||||||
|
"index.html"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "run-p build:*",
|
||||||
|
"build:dev": "rollup -c",
|
||||||
|
"build:prod": "BUILD_FLAGS=minify rollup -c",
|
||||||
|
"build-except-ts": "run-p build:*",
|
||||||
|
"build-site": "BUILD_FLAGS=minify,hash rollup -c",
|
||||||
|
"build-ts": "ttsc --build",
|
||||||
|
"clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
|
||||||
|
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
||||||
|
"prepublishOnly": "run-p build readme2md",
|
||||||
|
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
||||||
|
"watch": "rollup -c --watch",
|
||||||
|
"watch-ts": "tssc --build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hyperapp/events": "^0.0.4",
|
||||||
|
"hyperapp": "^2.0.4",
|
||||||
|
"ipynb2html": "0.3.0"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
">0.5%",
|
||||||
|
"Firefox ESR",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
13
packages/ipynb2html-viewer/postcss.config.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module.exports = ({ env }) => ({
|
||||||
|
map: {
|
||||||
|
inline: false,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'postcss-inline-svg': {},
|
||||||
|
'postcss-nesting': {},
|
||||||
|
'postcss-sort-media-queries': {
|
||||||
|
sort: 'desktop-first',
|
||||||
|
},
|
||||||
|
'cssnano': env === 'production',
|
||||||
|
},
|
||||||
|
})
|
130
packages/ipynb2html-viewer/rollup.config.js
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
import addGitMsg from 'rollup-plugin-add-git-msg'
|
||||||
|
import babel from '@rollup/plugin-babel'
|
||||||
|
import commonjs from '@rollup/plugin-commonjs'
|
||||||
|
import conditional from 'rollup-plugin-conditional'
|
||||||
|
import html from '@rollup/plugin-html'
|
||||||
|
import license from 'rollup-plugin-node-license'
|
||||||
|
import livereload from 'rollup-plugin-livereload'
|
||||||
|
import postcss from 'rollup-plugin-postcss'
|
||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import serve from 'rollup-plugin-serve'
|
||||||
|
import { terser } from 'rollup-plugin-terser'
|
||||||
|
import ttypescript from 'ttypescript'
|
||||||
|
import typescript from 'rollup-plugin-typescript2'
|
||||||
|
|
||||||
|
import indexTemplate from './index-html'
|
||||||
|
import pkg from './package.json'
|
||||||
|
|
||||||
|
|
||||||
|
const flags = (process.env.BUILD_FLAGS || '').split(',').reduce((obj, flag) => {
|
||||||
|
obj[flag] = true
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
flags.watch = !!process.env.ROLLUP_WATCH
|
||||||
|
|
||||||
|
const assetInfix =
|
||||||
|
flags.hash ? '.[hash]' :
|
||||||
|
flags.minify ? '.min' :
|
||||||
|
''
|
||||||
|
const destDir = process.env.BUILD_DESTDIR || './dist'
|
||||||
|
const extensions = ['.mjs', '.js', '.ts']
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'src/index.ts',
|
||||||
|
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,
|
||||||
|
// 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
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
// Convert PostCSS styles to CSS.
|
||||||
|
postcss({
|
||||||
|
autoModules: false,
|
||||||
|
extract: true,
|
||||||
|
sourceMap: true,
|
||||||
|
minimize: !!flags.minify,
|
||||||
|
}),
|
||||||
|
// Generate index.html from the template.
|
||||||
|
html({
|
||||||
|
template: indexTemplate,
|
||||||
|
}),
|
||||||
|
conditional(!flags.watch, [
|
||||||
|
// 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' }),
|
||||||
|
]),
|
||||||
|
conditional(flags.minify, [
|
||||||
|
// Minify JS.
|
||||||
|
terser({
|
||||||
|
ecma: 5,
|
||||||
|
output: {
|
||||||
|
// Preserve comment injected by addGitMsg and license.
|
||||||
|
comments: RegExp(`(?:\\$\\{${pkg.name}\\}|Bundled npm packages)`),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
// Use only when running in watch mode...
|
||||||
|
conditional(flags.watch, () => [
|
||||||
|
serve({
|
||||||
|
contentBase: destDir,
|
||||||
|
}),
|
||||||
|
livereload(),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
external: [
|
||||||
|
'ipynb2html',
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
dir: destDir,
|
||||||
|
entryFileNames: `ipynb-viewer${assetInfix}.js`,
|
||||||
|
assetFileNames: `ipynb-viewer${assetInfix}.[ext]`,
|
||||||
|
format: 'umd',
|
||||||
|
name: 'init',
|
||||||
|
sourcemap: true,
|
||||||
|
globals: {
|
||||||
|
ipynb2html: 'ipynb2html',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
220
packages/ipynb2html-viewer/src/actions.ts
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-use-before-define */
|
||||||
|
import * as hyperapp from 'hyperapp'
|
||||||
|
import * as ipynb2html from 'ipynb2html'
|
||||||
|
|
||||||
|
import {
|
||||||
|
historyPush,
|
||||||
|
readFile,
|
||||||
|
readFromStorage,
|
||||||
|
request,
|
||||||
|
ResponseError,
|
||||||
|
setPageTitle,
|
||||||
|
writeToStorage,
|
||||||
|
} from './effects'
|
||||||
|
import { State, ErrorMessage } from './types'
|
||||||
|
|
||||||
|
|
||||||
|
type Action<Payload = void> = hyperapp.Action<State, Payload>
|
||||||
|
type Dispatchable<DPayload = void, CPayload = any> = hyperapp.Dispatchable<State, DPayload, CPayload>
|
||||||
|
|
||||||
|
|
||||||
|
const initState: State = {
|
||||||
|
mainView: 'BLANK',
|
||||||
|
fileUrl: null,
|
||||||
|
title: '.ipynb viewer',
|
||||||
|
notebook: null,
|
||||||
|
error: null,
|
||||||
|
dragover: false,
|
||||||
|
historyIdx: 0,
|
||||||
|
fromHistory: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
// FIXME: This is just a quick & dirty solution.
|
||||||
|
const isNotebook = (notebook: any): notebook is ipynb2html.Notebook => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
return typeof notebook === 'object' && !!notebook?.metadata && !!notebook?.cells
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFileDataTransfer = (transfer: DataTransfer | null): transfer is DataTransfer => {
|
||||||
|
return !!transfer && !!Array.from(transfer.items).find(item => item.kind === 'file')
|
||||||
|
}
|
||||||
|
|
||||||
|
const rewriteFileUrl = (url: string): string => {
|
||||||
|
const { host, pathname } = new URL(url)
|
||||||
|
|
||||||
|
switch (host) {
|
||||||
|
case 'gist.github.com':
|
||||||
|
return `https://gist.githubusercontent.com${pathname}/raw/`
|
||||||
|
default:
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateToUrlQuery = (state: State): string => {
|
||||||
|
const params: Record<string, string> = {}
|
||||||
|
if (state.fileUrl) {
|
||||||
|
params.url = state.fileUrl
|
||||||
|
}
|
||||||
|
return new URLSearchParams(params).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
export const Initialize: hyperapp.ActionOnInit<State> = () => {
|
||||||
|
const url = new URLSearchParams(window.location.search).get('url')
|
||||||
|
|
||||||
|
return url
|
||||||
|
? (FetchNotebook({ ...initState, fileUrl: url }) as hyperapp.DispatchableOnInit<State>)
|
||||||
|
: initState
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowNotebook: Action<unknown> = (state, notebook): Dispatchable => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
notebook = typeof notebook === 'string' ? JSON.parse(notebook) : notebook
|
||||||
|
|
||||||
|
if (!isNotebook(notebook)) {
|
||||||
|
return [ShowError, {
|
||||||
|
title: 'Invalid Format',
|
||||||
|
message: 'The file is not in Jupyter Notebook format.',
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const title = ipynb2html.readNotebookTitle(notebook) ?? initState.title
|
||||||
|
|
||||||
|
const effects = [
|
||||||
|
setPageTitle(title),
|
||||||
|
]
|
||||||
|
if (!state.fromHistory) {
|
||||||
|
state.historyIdx++
|
||||||
|
|
||||||
|
effects.push(
|
||||||
|
writeToStorage({
|
||||||
|
key: `notebook/${state.historyIdx}`,
|
||||||
|
value: notebook,
|
||||||
|
session: true,
|
||||||
|
}),
|
||||||
|
historyPush({
|
||||||
|
state: { ...state, notebook: null },
|
||||||
|
urlQuery: stateToUrlQuery(state),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...state,
|
||||||
|
title,
|
||||||
|
notebook,
|
||||||
|
mainView: 'NOTEBOOK',
|
||||||
|
fromHistory: false,
|
||||||
|
error: null,
|
||||||
|
},
|
||||||
|
...effects,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowBlank: Action = (state): Dispatchable => [
|
||||||
|
(state = { ...state, mainView: 'BLANK', fileUrl: null, notebook: null }),
|
||||||
|
setPageTitle(initState.title!),
|
||||||
|
historyPush({
|
||||||
|
state,
|
||||||
|
urlQuery: '',
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const ShowError: Action<Error | ErrorMessage> = (state, error): Dispatchable => {
|
||||||
|
let title = 'title' in error ? error.title : 'Error'
|
||||||
|
let message = error.message
|
||||||
|
let detail = error.message
|
||||||
|
|
||||||
|
if (error instanceof SyntaxError && error.message.startsWith('JSON.parse:')) {
|
||||||
|
title = 'Malformed Notebook'
|
||||||
|
message = 'The file is not a valid JSON.'
|
||||||
|
} else if (error instanceof ResponseError) {
|
||||||
|
title = 'HTTP Error'
|
||||||
|
message = 'Failed to fetch notebook from the given address.'
|
||||||
|
} else if (error instanceof TypeError
|
||||||
|
&& (error.message === 'Failed to fetch' || error.message.startsWith('NetworkError '))
|
||||||
|
) {
|
||||||
|
title = 'Network Error'
|
||||||
|
message = 'Failed to fetch notebook from the given address.'
|
||||||
|
} else {
|
||||||
|
detail = ''
|
||||||
|
}
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
mainView: 'ERROR',
|
||||||
|
error: { title, message, detail },
|
||||||
|
notebook: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SubmitFileUrl: Action = (state): Dispatchable => (
|
||||||
|
state.fileUrl ? FetchNotebook : ShowBlank
|
||||||
|
)
|
||||||
|
|
||||||
|
export const SetFileUrl: Action<string> = (state, fileUrl): Dispatchable => (
|
||||||
|
{ ...state, fileUrl }
|
||||||
|
)
|
||||||
|
|
||||||
|
export const FetchNotebook: Action = (state): Dispatchable => [
|
||||||
|
{ ...state, mainView: 'FETCHING', notebook: null },
|
||||||
|
request({
|
||||||
|
url: rewriteFileUrl(state.fileUrl!),
|
||||||
|
options: {
|
||||||
|
mode: 'cors',
|
||||||
|
redirect: 'follow',
|
||||||
|
referrerPolicy: 'no-referrer',
|
||||||
|
},
|
||||||
|
onSuccess: ShowNotebook,
|
||||||
|
onError: ShowError,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const LoadLocalFile: Action<File> = (state, file): Dispatchable => [
|
||||||
|
{ ...state, fileUrl: null },
|
||||||
|
readFile({ file, onSuccess: ShowNotebook }),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const HistoryPop: Action<PopStateEvent> = (state, event): Dispatchable => [
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
{ ...(state = event.state ?? state), fromHistory: true },
|
||||||
|
readFromStorage({
|
||||||
|
key: `notebook/${state.historyIdx}`,
|
||||||
|
session: true,
|
||||||
|
onSuccess: ShowNotebook,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
export const DragOver: Action<DragEvent> = (state, event): Dispatchable => {
|
||||||
|
if (!isFileDataTransfer(event.dataTransfer)) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
event.dataTransfer.dropEffect = 'copy'
|
||||||
|
|
||||||
|
return { ...state, dragover: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DragLeave: Action = (state): Dispatchable => ({
|
||||||
|
...state,
|
||||||
|
dragover: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const DropFile: Action<DragEvent> = (state, event): Dispatchable => {
|
||||||
|
if (!isFileDataTransfer(event.dataTransfer)) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
const file = event.dataTransfer.files?.[0]
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ ...state, dragover: false, fileUrl: null },
|
||||||
|
readFile({ file, onSuccess: ShowNotebook }),
|
||||||
|
]
|
||||||
|
}
|
113
packages/ipynb2html-viewer/src/effects.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import { AnyState, Dispatch, Dispatchable, Effect } from 'hyperapp'
|
||||||
|
|
||||||
|
|
||||||
|
function fx <State extends AnyState, Props> (
|
||||||
|
fn: (dispatch: Dispatch<State>, props: Props) => void,
|
||||||
|
) {
|
||||||
|
return (props: Props): Effect<State, Props> => [fn, props]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type HistoryPush = (props: { state: any, urlQuery?: string }) => Effect<any>
|
||||||
|
|
||||||
|
export const historyPush: HistoryPush = fx((_, { state, urlQuery }) => {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
if (urlQuery != null) {
|
||||||
|
url.search = urlQuery
|
||||||
|
}
|
||||||
|
history.pushState(state, document.title, url.href)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
type ReadFile = <S extends AnyState>(props: {
|
||||||
|
file: File,
|
||||||
|
onSuccess: Dispatchable<S, string>,
|
||||||
|
onError?: Dispatchable<S, DOMException>,
|
||||||
|
}) => Effect<S>
|
||||||
|
|
||||||
|
export const readFile: ReadFile = fx((dispatch, { file, onSuccess, onError }) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
|
||||||
|
reader.onload = (event) => {
|
||||||
|
dispatch(onSuccess, event.target?.result?.toString() ?? '')
|
||||||
|
}
|
||||||
|
if (onError) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
reader.onerror = () => dispatch(onError, reader.error!)
|
||||||
|
}
|
||||||
|
reader.readAsText(file, 'utf8')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
type ReadFromStorage = <S extends AnyState, T = unknown>(props: {
|
||||||
|
key: string,
|
||||||
|
session: boolean,
|
||||||
|
onSuccess: Dispatchable<S, T>,
|
||||||
|
}) => Effect<S>
|
||||||
|
|
||||||
|
export const readFromStorage: ReadFromStorage = fx((dispatch, { key, session, onSuccess }) => {
|
||||||
|
const storage = session ? window.sessionStorage : window.localStorage
|
||||||
|
|
||||||
|
const value = storage.getItem(key)
|
||||||
|
if (value == null) {
|
||||||
|
// FIXME
|
||||||
|
} else {
|
||||||
|
dispatch(onSuccess, JSON.parse(value))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
type WriteToStorage = (props: {
|
||||||
|
key: string,
|
||||||
|
value: unknown,
|
||||||
|
session: boolean,
|
||||||
|
}) => Effect<any>
|
||||||
|
|
||||||
|
export const writeToStorage: WriteToStorage = fx((_, { key, value, session }) => {
|
||||||
|
const storage = session ? window.sessionStorage : window.localStorage
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
storage.removeItem(key)
|
||||||
|
} else {
|
||||||
|
storage.setItem(key, JSON.stringify(value))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export class ResponseError extends Error {
|
||||||
|
constructor (readonly response: Response) {
|
||||||
|
super(`${response.status} ${response.statusText}`)
|
||||||
|
// See https://github.com/Microsoft/TypeScript/issues/13965 *facepalm*
|
||||||
|
Object.setPrototypeOf(this, new.target.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request = <S extends AnyState>(props: {
|
||||||
|
url: Parameters<typeof fetch>[0],
|
||||||
|
options?: Parameters<typeof fetch>[1],
|
||||||
|
onSuccess: Dispatchable<S, unknown>,
|
||||||
|
onError: Dispatchable<S, ResponseError | Error>,
|
||||||
|
}) => Effect<S>
|
||||||
|
|
||||||
|
export const request: Request = fx((dispatch, { url, options = {}, onSuccess, onError }) => {
|
||||||
|
options.headers = {
|
||||||
|
...options.headers,
|
||||||
|
accept: 'application/json',
|
||||||
|
}
|
||||||
|
fetch(url, options)
|
||||||
|
.then(async response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new ResponseError(response)
|
||||||
|
}
|
||||||
|
return await response.json() as unknown
|
||||||
|
})
|
||||||
|
.then(result => dispatch(onSuccess, result))
|
||||||
|
.catch(error => dispatch(onError, error))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
type SetPageTitle = (title: string) => Effect<any>
|
||||||
|
|
||||||
|
export const setPageTitle: SetPageTitle = fx((_, title: string) => {
|
||||||
|
document.title = title
|
||||||
|
})
|
7
packages/ipynb2html-viewer/src/events.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { createOnCustomEvent } from '@hyperapp/events'
|
||||||
|
|
||||||
|
|
||||||
|
export const onDragOver = createOnCustomEvent<DragEvent>('dragover')
|
||||||
|
export const onDragLeave = createOnCustomEvent<DragEvent>('dragleave')
|
||||||
|
export const onDrop = createOnCustomEvent<DragEvent>('drop')
|
||||||
|
export const onPopState = createOnCustomEvent<PopStateEvent>('popstate')
|
3
packages/ipynb2html-viewer/src/icons/alert.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<path d="M507 426L283 54a31 31 0 00-54 0L5 426a31 31 0 0026 48h450a31 31 0 0026-48zM256 167c13 0 24 8 24 20 0 40-5 96-5 136 0 10-11 14-19 14-10 0-19-4-19-14 0-40-5-96-5-136 0-12 11-20 24-20zm0 244a25 25 0 010-51c14 0 26 12 26 26 0 13-12 25-26 25z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 323 B |
3
packages/ipynb2html-viewer/src/icons/chevron-down.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 444.819 444.819">
|
||||||
|
<path d="M434 114l-21-21c-8-7-16-11-26-11s-19 4-26 11L222 232 84 93a37 37 0 00-52 0l-21 21c-7 7-11 16-11 26s4 19 11 26l186 186a35 35 0 0052 0l185-186a37 37 0 000-52z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 250 B |
3
packages/ipynb2html-viewer/src/icons/chevron-up.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 445 445">
|
||||||
|
<path d="M434 279L248 93c-7-7-16-11-26-11s-18 4-25 11L11 279c-7 7-11 16-11 26s4 18 11 25l21 22c7 7 16 11 26 11s19-4 26-11l138-139 139 139c7 7 16 11 26 11s19-4 26-11l21-22a35 35 0 000-52z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 263 B |
3
packages/ipynb2html-viewer/src/icons/folder.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
|
||||||
|
<path d="M3 4a3 3 0 00-3 3v26l3.2-16.9c.5-2.6 2.4-4.1 5.2-4.1H47v-1a3 3 0 00-3-3H18c-.2-.1-.8-1-1.1-1.5C16 5.3 15.3 4 14 4zm5.4 10c-1.2 0-2.8.4-3.2 2.5L0 44v.1a3 3 0 003 3h39a3 3 0 003-3l5-26.8V17a3 3 0 00-3-3zm0 0" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 289 B |
3
packages/ipynb2html-viewer/src/icons/github.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 671 B |
84
packages/ipynb2html-viewer/src/icons/logo.src.svg
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="120"
|
||||||
|
height="30"
|
||||||
|
viewBox="0 0 31.750001 7.9375002"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||||
|
sodipodi:docname="drawing.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="5.6"
|
||||||
|
inkscape:cx="40.353189"
|
||||||
|
inkscape:cy="24.629519"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1389"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
units="px" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-289.06248)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;line-height:1.25;font-family:Lemonada;-inkscape-font-specification:'Lemonada, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.15247695"
|
||||||
|
x="0.69665426"
|
||||||
|
y="271.22836"
|
||||||
|
id="text12-5"
|
||||||
|
transform="scale(0.91908892,1.088034)"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan10"
|
||||||
|
x="0.69665426"
|
||||||
|
y="271.22836"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;font-family:Lemonada;-inkscape-font-specification:'Lemonada, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.15247695"><tspan
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:5.64444447px;font-family:Lemonada;-inkscape-font-specification:'Lemonada, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.15247695"
|
||||||
|
id="tspan14">ipynb</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="scale(0.92984193,1.0754516)"
|
||||||
|
id="text173-0"
|
||||||
|
y="272.58124"
|
||||||
|
x="19.826342"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.5690515px;line-height:1.25;font-family:Lemonada;-inkscape-font-specification:'Lemonada, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.102001"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.5690515px;font-family:Lemonada;-inkscape-font-specification:'Lemonada, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:0.102001"
|
||||||
|
y="272.58124"
|
||||||
|
x="19.826342"
|
||||||
|
id="tspan171"
|
||||||
|
sodipodi:role="line">viewer</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
6
packages/ipynb2html-viewer/src/icons/logo.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.75 7.94">
|
||||||
|
<g aria-label="ipynbviewer">
|
||||||
|
<path d="M1.52 6.2q-.22 0-.33-.14-.1-.13-.1-.4l.02-.35q.02-.23.08-.55l.13-.75.18-.97-.25-.37q.16-.14.36-.23.2-.09.36-.09.17 0 .24.09.08.09.08.29l-.04.22-.08.41-.1.54-.12.6-.08.59q-.03.27-.03.5 0 .14.03.23.03.1.08.16-.2.21-.43.21zm.41-4.31q-.18 0-.3-.11t-.12-.3q0-.14.08-.26.07-.12.2-.19.12-.06.26-.06.19 0 .3.1.12.11.12.29 0 .15-.07.27-.08.12-.2.19-.12.07-.27.07zM4.26 6.04q-.24 0-.47-.05-.03-.07-.04-.16l-.02-.2q.54 0 .94-.22.4-.22.63-.61.22-.4.22-.92 0-.38-.13-.59-.14-.21-.38-.21-.25 0-.46.28-.22.28-.4.8-.16.53-.3 1.27l.09-2.31.32.22q.17-.47.45-.73t.61-.26q.29 0 .5.18.22.18.33.5.12.33.12.77 0 .48-.15.89-.15.41-.42.71t-.64.47q-.36.17-.8.17zm-.82 1.23q-.2 0-.3-.15-.1-.14-.1-.44l.03-.46.1-.83.15-1.1.18-1.25-.25-.37q.17-.14.36-.23.2-.09.36-.09.18 0 .24.09.08.08.08.28 0 .16-.05.45l-.13.68-.15.84q-.08.45-.13.93-.05.49-.05.98 0 .15.03.26.04.12.1.2-.1.1-.22.15-.12.06-.25.06zM7.02 7.27q-.36 0-.54-.16-.18-.15-.18-.41 0-.12.05-.24t.1-.19q.06.09.14.17.08.08.2.13.13.05.32.05.18 0 .4-.09.22-.09.46-.28.25-.18.48-.48.22-.3.4-.71.19-.42.3-.96.1-.54.1-1.22v-.17l-.01-.17q.11-.1.22-.14.1-.05.2-.05.15 0 .25.1t.1.33q0 .43-.1.93-.09.5-.27 1-.18.5-.44.96-.25.46-.58.82-.34.36-.73.57-.4.2-.87.2zm1.22-1.08q-.34 0-.54-.15-.19-.16-.28-.43-.09-.27-.12-.6l-.03-.7q0-.36-.02-.7-.01-.34-.08-.62-.06-.28-.22-.45.2-.19.44-.19.23 0 .36.15.13.15.18.4.06.25.07.57L8 4.1v.65q.02.3.08.56.05.25.18.4.13.15.37.15zM11.04 6.2q-.23 0-.33-.14-.1-.13-.1-.4l.03-.42q.03-.29.1-.66l.13-.78.15-.76-.24-.37q.15-.13.34-.22.2-.08.36-.08.18 0 .25.09.08.08.08.28 0 .16-.05.36-.04.19-.1.35l.05.03q.28-.57.62-.85.35-.28.76-.28.33 0 .49.2.16.2.16.6 0 .27-.05.57-.05.3-.13.6l-.12.64q-.05.32-.05.63 0 .27.09.4-.2.2-.4.2-.23 0-.34-.13-.1-.13-.1-.4 0-.25.05-.55l.11-.62.12-.58q.04-.27.04-.47 0-.22-.07-.32-.06-.1-.2-.1-.2 0-.39.13-.18.14-.35.39-.17.24-.3.57-.14.33-.21.7-.08.38-.08.78 0 .27.08.4-.2.2-.4.2zM15.3 6.2q-.24 0-.4-.09-.18-.08-.26-.27-.1-.2-.1-.54 0-.2.04-.5.03-.3.1-.73l.14-.95.2-1.15-.25-.36q.15-.15.35-.23.2-.1.36-.1.17 0 .25.1.07.07.07.27 0 .21-.1.65l-.2.99.05.03q.19-.46.47-.71.3-.26.63-.26.3 0 .5.18.22.17.33.48.12.31.12.74 0 .52-.17.97-.17.44-.48.78-.32.33-.74.51t-.91.18zm-.12-.48q.35-.02.66-.17.3-.15.53-.4.23-.25.35-.57.13-.33.13-.7 0-.38-.13-.59-.14-.21-.38-.21-.22 0-.43.19-.21.18-.38.53-.16.35-.26.84-.1.48-.1 1.08z"/>
|
||||||
|
<path d="M19.32 4.18q-.16 0-.25-.08-.08-.06-.12-.24-.03-.17-.03-.47v-.45l-.01-.42q-.01-.2-.06-.37-.04-.16-.14-.28.1-.1.24-.1.14 0 .21.12.08.11.1.37.04.26.04.7V3.85h.03q.27-.44.44-.76.16-.32.24-.54.07-.23.07-.36 0-.18-.07-.32l.15-.07.12-.02q.09 0 .14.08.05.07.05.2 0 .12-.05.29-.05.17-.15.4l-.25.51-.35.64.05.11q-.04.05-.1.09-.07.04-.15.05-.08.03-.15.03zM21.2 4.18q-.1 0-.16-.07-.06-.06-.06-.2l.01-.22.05-.34.09-.5.14-.72-.15-.19q.08-.08.19-.12.1-.04.19-.04.1 0 .13.04.05.05.05.17l-.03.13-.05.27-.08.36-.07.4-.05.39-.02.33v.12l.06.08q-.11.1-.23.1zm.3-2.73q-.1 0-.17-.06-.06-.06-.06-.16 0-.07.04-.14.03-.06.1-.1.07-.04.15-.04.11 0 .17.06.07.05.07.15 0 .08-.04.15-.04.07-.1.1-.07.04-.16.04zM22.94 4.18q-.35 0-.55-.25-.2-.25-.2-.69 0-.32.09-.58.09-.27.25-.47.16-.2.38-.3.22-.11.48-.11t.4.14q.16.15.16.4 0 .28-.17.48-.17.19-.5.29-.32.1-.78.1l.04-.21q.5 0 .77-.17.27-.18.27-.5 0-.13-.07-.2-.06-.09-.18-.09-.15 0-.28.1-.13.1-.24.26-.1.17-.15.4-.06.23-.06.48 0 .3.1.48t.27.18q.22 0 .35-.16.14-.15.15-.42.13 0 .2.06.07.05.07.17 0 .16-.1.3-.11.13-.3.22-.17.09-.4.09zM24.8 4.18q-.15 0-.2-.1-.05-.1-.05-.39v-.54-.5q-.02-.25-.06-.44-.05-.2-.15-.34.1-.1.23-.1.14 0 .22.12.07.11.1.37t.03.7V3.85h.03q.22-.32.36-.6.14-.29.21-.5.08-.22.1-.34l.04-.14q0-.04-.03-.1l-.06-.1-.04-.08.12-.07q.05-.03.1-.03.1 0 .15.08.05.07.05.2t-.04.29l-.13.34-.22.46q-.13.28-.24.46-.1.18-.19.28-.08.1-.17.14-.08.04-.17.04zm1.14 0q-.12 0-.19-.07t-.1-.19l-.03-.28-.1-1.65q.04-.05.1-.07.06-.03.13-.03l.08.23.06 1.73h.03q.23-.44.37-.76.14-.32.2-.54.07-.23.07-.36 0-.18-.06-.32l.14-.07.12-.02q.09 0 .13.08.05.07.05.2 0 .12-.04.29-.04.17-.12.4l-.22.51-.3.64.05.11q-.04.05-.1.09-.06.04-.14.05-.06.03-.13.03zM28.13 4.18q-.35 0-.55-.25-.2-.25-.2-.69 0-.32.09-.58.09-.27.25-.47.16-.2.38-.3.22-.11.48-.11t.4.14q.16.15.16.4 0 .28-.17.48-.17.19-.5.29-.31.1-.78.1l.04-.21q.5 0 .77-.17.28-.18.28-.5 0-.13-.07-.2-.07-.09-.19-.09-.15 0-.28.1-.13.1-.23.26-.1.17-.16.4t-.06.48q0 .3.1.48t.27.18q.22 0 .35-.16.14-.15.15-.42.13 0 .2.06.07.05.07.17 0 .16-.1.3-.11.13-.3.22-.17.09-.4.09zM29.85 4.18q-.12 0-.17-.08-.06-.08-.06-.24l.02-.27.07-.43.1-.5.1-.5-.15-.19.17-.13q.09-.05.16-.05.1 0 .14.07.04.06.04.14 0 .13-.04.28l-.1.3.04.03q.1-.29.21-.47.11-.19.22-.28.11-.09.22-.09.1 0 .16.06t.06.17q0 .13-.09.2-.08.08-.22.08-.13 0-.26.14-.13.13-.23.36-.1.22-.16.5-.06.3-.06.59 0 .14.04.2-.1.1-.21.1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
20
packages/ipynb2html-viewer/src/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { app } from 'hyperapp'
|
||||||
|
import { preventDefault } from '@hyperapp/events'
|
||||||
|
|
||||||
|
import * as actions from './actions'
|
||||||
|
import { onDragLeave, onDragOver, onDrop, onPopState } from './events'
|
||||||
|
import { State } from './types'
|
||||||
|
import { App } from './view'
|
||||||
|
|
||||||
|
|
||||||
|
export default (): void => app<State>({
|
||||||
|
node: document.body,
|
||||||
|
init: actions.Initialize,
|
||||||
|
view: App,
|
||||||
|
subscriptions: () => [
|
||||||
|
onDragOver(actions.DragOver),
|
||||||
|
onDragLeave(preventDefault(actions.DragLeave)),
|
||||||
|
onDrop(actions.DropFile),
|
||||||
|
onPopState(actions.HistoryPop),
|
||||||
|
],
|
||||||
|
})
|
18
packages/ipynb2html-viewer/src/types.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Notebook } from 'ipynb2html'
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
fileUrl: string | null,
|
||||||
|
title: string | null,
|
||||||
|
notebook: Notebook | null,
|
||||||
|
error: ErrorMessage | null,
|
||||||
|
dragover: boolean,
|
||||||
|
historyIdx: number,
|
||||||
|
fromHistory: boolean,
|
||||||
|
mainView: 'BLANK' | 'ERROR' | 'FETCHING' | 'NOTEBOOK',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ErrorMessage = {
|
||||||
|
title: string,
|
||||||
|
message: string,
|
||||||
|
detail?: string,
|
||||||
|
}
|
25
packages/ipynb2html-viewer/src/utils.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { AnyState, Dispatch, Effect } from 'hyperapp'
|
||||||
|
|
||||||
|
|
||||||
|
export function fx <State extends AnyState, Props> (
|
||||||
|
fn: (dispatch: Dispatch<State>, props: Props) => void,
|
||||||
|
) {
|
||||||
|
return (props: Props): Effect<State, Props> => [fn, props]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function match <T extends string, U extends {[K in T]: (k: K) => any}> (
|
||||||
|
value: T,
|
||||||
|
matcher: U,
|
||||||
|
): U extends {[K in T]: (k: K) => infer R} ? R : never {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
|
return matcher[value](value)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function targetFile (event: Event): File {
|
||||||
|
const file = (event.target as HTMLInputElement | null)?.files?.[0]
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
throw TypeError("Event doesn't contain any file")
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
}
|
281
packages/ipynb2html-viewer/src/view.pcss
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: #2a2a2a;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #dedede;
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
color: black;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin-top: 1.6em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 1px solid #cfcfcf;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #24292e;
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .logo {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 7.5rem;
|
||||||
|
height: 2rem;
|
||||||
|
margin: 0 0.6rem;
|
||||||
|
font-size: 0;
|
||||||
|
background: svg-load('icons/logo.svg', fill: #fff) no-repeat left center / contain;
|
||||||
|
|
||||||
|
@media (max-width: 40em) {
|
||||||
|
width: 3.8rem;
|
||||||
|
margin: 0 0.2rem 0 0.5rem;
|
||||||
|
background-size: 6.8rem 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .file-input {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .file-label {
|
||||||
|
display: inline-block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
margin: 0 0.8rem;
|
||||||
|
color: transparent;
|
||||||
|
background: svg-load('icons/folder.svg', fill: #ddd) no-repeat center / contain;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (max-width: 40em) {
|
||||||
|
margin: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .url-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .url-input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.6rem 0;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 2;
|
||||||
|
background-color: hsla(0, 0%, 100%, 0.15);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .github-link {
|
||||||
|
display: block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 1.6rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
margin: 0 1rem;
|
||||||
|
font-size: 0;
|
||||||
|
background: svg-load('icons/github.svg', fill: #eee) no-repeat center / contain;
|
||||||
|
|
||||||
|
@media (max-width: 40em) {
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
display: table;
|
||||||
|
max-width: 28rem;
|
||||||
|
height: 6rem;
|
||||||
|
margin: 32vh auto 0 auto;
|
||||||
|
padding-left: 8.5rem;
|
||||||
|
background: svg-load('icons/alert.svg') no-repeat;
|
||||||
|
background-position: left top;
|
||||||
|
background-size: 6rem 6rem;
|
||||||
|
|
||||||
|
@media (max-width: 40em) {
|
||||||
|
height: unset;
|
||||||
|
margin: 10vh auto 0 auto;
|
||||||
|
padding: 8rem 1rem 0 1rem;
|
||||||
|
text-align: center;
|
||||||
|
background-position: top center;
|
||||||
|
}
|
||||||
|
|
||||||
|
& summary {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.13em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
/* Expand arrow after the summary text. */
|
||||||
|
display: inline-block;
|
||||||
|
width: 0.7em;
|
||||||
|
height: 0.7em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
background: svg-load('icons/chevron-down.svg') no-repeat center / contain;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& details[open] summary::after {
|
||||||
|
/* Collapse arrow after the summary text. */
|
||||||
|
background: svg-load('icons/chevron-up.svg') no-repeat center / contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
& details :not(summary) {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
& :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fetching {
|
||||||
|
width: 12rem;
|
||||||
|
margin: 32vh auto 0 auto;
|
||||||
|
font-size: 1.13rem;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
/* Spinner */
|
||||||
|
display: block;
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
margin: 0 auto 2rem auto;
|
||||||
|
border-top: 0.3rem solid #f37726;
|
||||||
|
border-right: 0.3rem solid transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: rotation 1s linear infinite;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nb-notebook {
|
||||||
|
max-width: 45rem;
|
||||||
|
margin: 1rem auto 2rem auto;
|
||||||
|
padding: 3rem 6rem;
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: 4px 4px 8px #cfcfcf, -4px -4px 8px #cfcfcf;
|
||||||
|
|
||||||
|
@media screen and (max-width: 940px) {
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding-right: 3rem;
|
||||||
|
padding-left: 5rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
padding: 0.1rem 5% 2rem 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0 0 5rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nb-output table {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotation {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 30mm 15mm 15mm 7mm;
|
||||||
|
}
|
80
packages/ipynb2html-viewer/src/view.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-use-before-define */
|
||||||
|
import { h, Lazy, View } from 'hyperapp'
|
||||||
|
import { preventDefault, targetValue } from '@hyperapp/events'
|
||||||
|
import * as ipynb2html from 'ipynb2html'
|
||||||
|
import { $INLINE_JSON } from 'ts-transformer-inline-file'
|
||||||
|
|
||||||
|
import * as actions from './actions'
|
||||||
|
import { State, ErrorMessage } from './types'
|
||||||
|
import { match, targetFile } from './utils'
|
||||||
|
|
||||||
|
import './view.pcss'
|
||||||
|
|
||||||
|
|
||||||
|
const { homepage } = $INLINE_JSON<{ homepage: string }>('../package.json')
|
||||||
|
const renderNotebook = ipynb2html.createRenderer(document)
|
||||||
|
|
||||||
|
export const App: View<State> = (state) => (
|
||||||
|
<body class={{ dragover: state.dragover }}>
|
||||||
|
<Header fileUrl={ state.fileUrl } />
|
||||||
|
<main>{
|
||||||
|
match(state.mainView, {
|
||||||
|
ERROR: () => <ErrorBox error={ state.error! } />,
|
||||||
|
FETCHING: () => <FetchingSpinner />,
|
||||||
|
NOTEBOOK: () => <Lazy view={ Notebook } notebook={ state.notebook } />,
|
||||||
|
BLANK: () => null,
|
||||||
|
})
|
||||||
|
}</main>
|
||||||
|
</body>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Header: View<Pick<State, 'fileUrl'>> = ({ fileUrl }) => (
|
||||||
|
<header id='header'>
|
||||||
|
<a class='logo' href='/'>
|
||||||
|
ipynb<sup>viewer</sup>
|
||||||
|
</a>
|
||||||
|
<label class='file-label' title='Open a notebook'>
|
||||||
|
<input
|
||||||
|
class='file-input'
|
||||||
|
type='file'
|
||||||
|
accept='.ipynb, .json, application/x-ipynb+json'
|
||||||
|
onchange={ [actions.LoadLocalFile, targetFile] }
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<form class='url-form' onSubmit={ preventDefault(actions.SubmitFileUrl) }>
|
||||||
|
<input
|
||||||
|
class='url-input'
|
||||||
|
name='url'
|
||||||
|
type='url'
|
||||||
|
placeholder='Notebook URL'
|
||||||
|
title='URL of ipynb file or Gist with ipynb file to render'
|
||||||
|
pattern='https?://.*'
|
||||||
|
value={ fileUrl }
|
||||||
|
onchange={ [actions.SetFileUrl, targetValue] }
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<a class='github-link' href={ homepage } title='Project’s homepage'>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
|
||||||
|
const Notebook: View<Pick<State, 'notebook'>> = ({ notebook }) => (
|
||||||
|
<div class='nb-notebook' innerHTML={ renderNotebook(notebook as any).innerHTML } />
|
||||||
|
)
|
||||||
|
|
||||||
|
const ErrorBox: View<{ error: ErrorMessage }> = ({ error }) => (
|
||||||
|
<aside class='error' role='alert'>
|
||||||
|
<h2>{ error.title }</h2>
|
||||||
|
<details>
|
||||||
|
<summary>{ error.message }</summary>
|
||||||
|
<p>{ error.detail }</p>
|
||||||
|
</details>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
|
||||||
|
const FetchingSpinner: View<{}> = () => (
|
||||||
|
<aside class='fetching' role='alert'>
|
||||||
|
<p>Fetching notebook…</p>
|
||||||
|
</aside>
|
||||||
|
)
|
16
packages/ipynb2html-viewer/tsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsxFactory": "h",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./lib",
|
||||||
|
"tsBuildInfoFile": "./.tsbuildinfo",
|
||||||
|
"baseUrl": ".",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src",
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../ipynb2html" },
|
||||||
|
],
|
||||||
|
}
|
|
@ -29,12 +29,14 @@
|
||||||
"styles"
|
"styles"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ttsc --build",
|
"build": "run-p build-except-ts build-ts",
|
||||||
"bundle": "rollup -c",
|
"build-bundle": "rollup -c",
|
||||||
|
"build-css": "mkdirp dist/ && csso styles/notebook.css -o dist/notebook.min.css -s dist/notebook.min.css.map",
|
||||||
|
"build-except-ts": "run-p build-bundle build-css",
|
||||||
|
"build-ts": "ttsc --build",
|
||||||
"clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
|
"clean": "rimraf coverage/ dist/ lib/ .eslintcache .tsbuildinfo",
|
||||||
"lint": "PKGDIR=$PWD; cd ../../ && eslint --cache --ext .ts,.tsx,.js $PKGDIR",
|
"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 build readme2md",
|
||||||
"prepublishOnly": "run-p bundle minify-css readme2md",
|
|
||||||
"test": "jest --detectOpenHandles --coverage --verbose",
|
"test": "jest --detectOpenHandles --coverage --verbose",
|
||||||
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
"readme2md": "../../scripts/adoc2md -a npm-readme ../../README.adoc > README.md",
|
||||||
"watch-ts": "ttsc --build --watch"
|
"watch-ts": "ttsc --build --watch"
|
||||||
|
|
85
patches/rollup-plugin-postcss+2.0.6.patch
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
Ported from https://github.com/egoist/rollup-plugin-postcss/pull/226
|
||||||
|
|
||||||
|
diff --git a/node_modules/rollup-plugin-postcss/dist/index.js b/node_modules/rollup-plugin-postcss/dist/index.js
|
||||||
|
index 3d04b2e..01dda37 100644
|
||||||
|
--- a/node_modules/rollup-plugin-postcss/dist/index.js
|
||||||
|
+++ b/node_modules/rollup-plugin-postcss/dist/index.js
|
||||||
|
@@ -789,15 +789,14 @@ var index = ((options = {}) => {
|
||||||
|
},
|
||||||
|
|
||||||
|
generateBundle(opts, bundle) {
|
||||||
|
- return _asyncToGenerator(function* () {
|
||||||
|
- if (extracted.size === 0) return; // TODO: support `[hash]`
|
||||||
|
+ var _this2 = this;
|
||||||
|
|
||||||
|
+ return _asyncToGenerator(function* () {
|
||||||
|
+ if (extracted.size === 0) return;
|
||||||
|
const dir = opts.dir || path.dirname(opts.file);
|
||||||
|
const file = opts.file || path.join(opts.dir, Object.keys(bundle).find(fileName => bundle[fileName].isEntry));
|
||||||
|
|
||||||
|
const getExtracted = () => {
|
||||||
|
- const fileName = typeof postcssLoaderOptions.extract === 'string' ? normalizePath(path.relative(dir, postcssLoaderOptions.extract)) : `${path.basename(file, path.extname(file))}.css`;
|
||||||
|
- const concat = new Concat(true, fileName, '\n');
|
||||||
|
const entries = Array.from(extracted.values());
|
||||||
|
const modules = bundle[normalizePath(path.relative(dir, file))].modules;
|
||||||
|
|
||||||
|
@@ -806,6 +805,32 @@ var index = ((options = {}) => {
|
||||||
|
entries.sort((a, b) => fileList.indexOf(a.id) - fileList.indexOf(b.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
+ let referenceId;
|
||||||
|
+
|
||||||
|
+ if (typeof postcssLoaderOptions.extract === 'string') {
|
||||||
|
+ referenceId = _this2.emitFile({
|
||||||
|
+ type: 'asset',
|
||||||
|
+ source: '',
|
||||||
|
+ // init
|
||||||
|
+ fileName: normalizePath(path.relative(dir, postcssLoaderOptions.extract))
|
||||||
|
+ });
|
||||||
|
+ } else {
|
||||||
|
+ const name = `${path.basename(file, path.extname(file))}.css`; // Base hash digest on concatenation of extracted code...
|
||||||
|
+
|
||||||
|
+ const source = entries.reduce((acc, {
|
||||||
|
+ code
|
||||||
|
+ }) => acc + code, '');
|
||||||
|
+ referenceId = _this2.emitFile({
|
||||||
|
+ type: 'asset',
|
||||||
|
+ source,
|
||||||
|
+ name
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ const fileName = _this2.getFileName(referenceId);
|
||||||
|
+
|
||||||
|
+ const concat = new Concat(true, fileName, '\n');
|
||||||
|
+
|
||||||
|
for (var _i = 0; _i < entries.length; _i++) {
|
||||||
|
const res = entries[_i];
|
||||||
|
const relative = normalizePath(path.relative(dir, res.id));
|
||||||
|
@@ -872,20 +897,14 @@ var index = ((options = {}) => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- const codeFile = {
|
||||||
|
- fileName: codeFileName,
|
||||||
|
- isAsset: true,
|
||||||
|
- source: code
|
||||||
|
- };
|
||||||
|
- bundle[codeFile.fileName] = codeFile;
|
||||||
|
+ bundle[codeFileName].source = code; // update
|
||||||
|
|
||||||
|
if (map) {
|
||||||
|
- const mapFile = {
|
||||||
|
- fileName: mapFileName,
|
||||||
|
- isAsset: true,
|
||||||
|
- source: map
|
||||||
|
- };
|
||||||
|
- bundle[mapFile.fileName] = mapFile;
|
||||||
|
+ _this2.emitFile({
|
||||||
|
+ type: 'asset',
|
||||||
|
+ source: map,
|
||||||
|
+ fileName: mapFileName
|
||||||
|
+ });
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
|
@ -4,5 +4,6 @@
|
||||||
{ "path": "./packages/ipynb2html-core" },
|
{ "path": "./packages/ipynb2html-core" },
|
||||||
{ "path": "./packages/ipynb2html" },
|
{ "path": "./packages/ipynb2html" },
|
||||||
{ "path": "./packages/ipynb2html-cli" },
|
{ "path": "./packages/ipynb2html-cli" },
|
||||||
|
{ "path": "./packages/ipynb2html-viewer" },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
62
types/@hyperapp/events.d.ts
vendored
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
type EventArgument<E extends keyof GlobalEventHandlers> = Parameters<NonNullable<GlobalEventHandlers[E]>>[0];
|
||||||
|
|
||||||
|
export declare function onMouseEnter<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onmouseenter'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onMouseLeave<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onmouseleave'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onMouseMove<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onmousemove'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onMouseOut<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onmouseout'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onMouseOver<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onmouseover'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onMouseUp<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onmouseup'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onTouchStart<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'ontouchstart'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onTouchMove<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'ontouchmove'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onTouchEnd<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'ontouchend'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onKeyDown<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onkeydown'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onKeyUp<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onkeyup'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onFocus<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onfocus'>>): hyperappSubset.Subscription<S>;
|
||||||
|
export declare function onBlur<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, EventArgument<'onblur'>>): hyperappSubset.Subscription<S>;
|
||||||
|
|
||||||
|
export declare function onAnimationFrame<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, DOMHighResTimeStamp>): hyperappSubset.Subscription<S>;
|
||||||
|
|
||||||
|
export declare function eventKey(e: Event): any;
|
||||||
|
export declare function eventDetail(e: Event): any;
|
||||||
|
export declare function targetChecked(e: Event): any;
|
||||||
|
export declare function targetValue(e: Event): any;
|
||||||
|
|
||||||
|
export declare function eventOptions<S extends hyperappSubset.AnyState>(props: { preventDefault?: boolean, stopPropagation?: boolean, action?: hyperappSubset.Dispatchable<S, Event> }): hyperappSubset.Effect<S>;
|
||||||
|
export declare function preventDefault<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, Event>): hyperappSubset.Action<S, Event>;
|
||||||
|
export declare function stopPropagation<S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, Event>): hyperappSubset.Action<S, Event>;
|
||||||
|
|
||||||
|
export declare function dispatchCustomEvent(props: {name: string}): hyperappSubset.Action<any>;
|
||||||
|
export declare function createOnCustomEvent<E extends Event = Event>(eventName: string): <S extends hyperappSubset.AnyState>(action: hyperappSubset.Dispatchable<S, E>) => hyperappSubset.Subscription<any>;
|
||||||
|
|
||||||
|
declare namespace hyperappSubset {
|
||||||
|
type AnyState = boolean | string | number | object | symbol | null | undefined;
|
||||||
|
|
||||||
|
type PayloadCreator<DPayload, CPayload> = ((data: DPayload) => CPayload);
|
||||||
|
|
||||||
|
type Dispatchable<State extends AnyState, DPayload = void, CPayload = any> = (
|
||||||
|
State
|
||||||
|
| [State, ...Effect<State>[]]
|
||||||
|
| ([Action<State, CPayload>, PayloadCreator<DPayload, CPayload>])
|
||||||
|
| ([Action<State, CPayload>, CPayload])
|
||||||
|
| Action<State, void> // (state) => ({ ... }) | (state) => ([{ ... }, effect1, ...])
|
||||||
|
| Action<State, DPayload> // (state, data) => ({ ... }) | (state, data) => ([{ ... }, effect1, ...])
|
||||||
|
);
|
||||||
|
|
||||||
|
type Dispatch<State extends AnyState, NextPayload> = (obj: Dispatchable<State, NextPayload>, data: NextPayload) => State;
|
||||||
|
|
||||||
|
interface EffectRunner<State extends AnyState = AnyState, NextPayload = void, Props = void> {
|
||||||
|
(dispatch: Dispatch<State, NextPayload>, props: Props): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Effect<State extends AnyState = AnyState> = [EffectRunner<State, any, any>, any] | [EffectRunner<State, any, void>];
|
||||||
|
|
||||||
|
interface SubscriptionRunner<State extends AnyState = AnyState, NextPayload = void, Props = void> {
|
||||||
|
(dispatch: Dispatch<State, NextPayload>, props: Props): (() => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription<State extends AnyState = AnyState> = [SubscriptionRunner<State, any, any>, any] | [SubscriptionRunner<State, any, void>];
|
||||||
|
|
||||||
|
interface Action<State extends AnyState, Payload = void> {
|
||||||
|
(state: State, data: Payload): Dispatchable<State>;
|
||||||
|
}
|
||||||
|
}
|
64
types/@hyperapp/http.d.ts
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
export declare function request<S extends hyperappSubset.AnyState>(props: RequestProps<S>): hyperappSubset.Effect<S>;
|
||||||
|
|
||||||
|
type RequestProps<S extends hyperappSubset.AnyState> =
|
||||||
|
JsonRequestProps<S> | TextRequestProps<S> | FormDataRequestProps<S> | BlobRequestProps<S> | ArrayBufferRequestProps<S>;
|
||||||
|
|
||||||
|
interface RequestPropsBase<S extends hyperappSubset.AnyState> {
|
||||||
|
url: Parameters<typeof fetch>[0];
|
||||||
|
options?: Parameters<typeof fetch>[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JsonRequestProps<S extends hyperappSubset.AnyState> extends RequestPropsBase<S> {
|
||||||
|
expect?: 'json';
|
||||||
|
action: hyperappSubset.Dispatchable<S, any>;
|
||||||
|
}
|
||||||
|
interface TextRequestProps<S extends hyperappSubset.AnyState> extends RequestPropsBase<S> {
|
||||||
|
expect: 'text';
|
||||||
|
action: hyperappSubset.Dispatchable<S, string>;
|
||||||
|
}
|
||||||
|
interface FormDataRequestProps<S extends hyperappSubset.AnyState> extends RequestPropsBase<S> {
|
||||||
|
expect: 'formData';
|
||||||
|
action: hyperappSubset.Dispatchable<S, FormData>;
|
||||||
|
}
|
||||||
|
interface BlobRequestProps<S extends hyperappSubset.AnyState> extends RequestPropsBase<S> {
|
||||||
|
expect: 'blob';
|
||||||
|
action: hyperappSubset.Dispatchable<S, Blob>;
|
||||||
|
}
|
||||||
|
interface ArrayBufferRequestProps<S extends hyperappSubset.AnyState> extends RequestPropsBase<S> {
|
||||||
|
expect: 'arrayBuffer';
|
||||||
|
action: hyperappSubset.Dispatchable<S, ArrayBuffer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare namespace hyperappSubset {
|
||||||
|
type AnyState = boolean | string | number | object | symbol | null | undefined;
|
||||||
|
|
||||||
|
type PayloadCreator<DPayload, CPayload> = ((data: DPayload) => CPayload);
|
||||||
|
|
||||||
|
type Dispatchable<State extends AnyState, DPayload = void, CPayload = any> = (
|
||||||
|
State
|
||||||
|
| [State, ...Effect<State>[]]
|
||||||
|
| ([Action<State, CPayload>, PayloadCreator<DPayload, CPayload>])
|
||||||
|
| ([Action<State, CPayload>, CPayload])
|
||||||
|
| Action<State, void> // (state) => ({ ... }) | (state) => ([{ ... }, effect1, ...])
|
||||||
|
| Action<State, DPayload> // (state, data) => ({ ... }) | (state, data) => ([{ ... }, effect1, ...])
|
||||||
|
);
|
||||||
|
|
||||||
|
type Dispatch<State extends AnyState, NextPayload> = (obj: Dispatchable<State, NextPayload>, data: NextPayload) => State;
|
||||||
|
|
||||||
|
interface EffectRunner<State extends AnyState = AnyState, NextPayload = void, Props = void> {
|
||||||
|
(dispatch: Dispatch<State, NextPayload>, props: Props): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Effect<State extends AnyState = AnyState> = [EffectRunner<State, any, any>, any] | [EffectRunner<State, any, void>];
|
||||||
|
|
||||||
|
interface SubscriptionRunner<State extends AnyState = AnyState, NextPayload = void, Props = void> {
|
||||||
|
(dispatch: Dispatch<State, NextPayload>, props: Props): (() => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscription<State extends AnyState = AnyState> = [SubscriptionRunner<State, any, any>, any] | [SubscriptionRunner<State, any, void>];
|
||||||
|
|
||||||
|
interface Action<State extends AnyState, Payload = void> {
|
||||||
|
(state: State, data: Payload): Dispatchable<State>;
|
||||||
|
}
|
||||||
|
}
|
1
types/@hyperapp/index.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
declare module '@hyperapp' {}
|
232
types/hyperapp.d.ts
vendored
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
// TypeScript Version: 3.0
|
||||||
|
|
||||||
|
export as namespace hyperapp;
|
||||||
|
|
||||||
|
/** @namespace [VDOM] */
|
||||||
|
|
||||||
|
/** The VDOM representation of an Element.
|
||||||
|
*
|
||||||
|
* @memberOf [VDOM]
|
||||||
|
*/
|
||||||
|
export interface VNode {
|
||||||
|
name: unknown; // protected (internal implementation)
|
||||||
|
props: unknown; // protected (internal implementation)
|
||||||
|
children: unknown; // protected (internal implementation)
|
||||||
|
node: unknown; // protected (internal implementation)
|
||||||
|
type: unknown; // protected (internal implementation)
|
||||||
|
key: unknown; // protected (internal implementation)
|
||||||
|
lazy: unknown; // protected (internal implementation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possibles state types (all except Array and Function)
|
||||||
|
*/
|
||||||
|
export type AnyState = boolean | string | number | object | symbol | null | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possibles children types
|
||||||
|
*/
|
||||||
|
export type Children = VNode | string | number | null
|
||||||
|
|
||||||
|
/** A Component is a function that returns a custom VNode or View.
|
||||||
|
*
|
||||||
|
* @memberOf [VDOM]
|
||||||
|
*/
|
||||||
|
export interface Component<Attributes = {}> {
|
||||||
|
(attributes: Attributes, children: VNode[]): VNode | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The soft way to create a VNode.
|
||||||
|
* @param name An element name or a Component function
|
||||||
|
* @param attributes Any valid HTML atributes, events, styles, and meta data
|
||||||
|
* @param children The children of the VNode
|
||||||
|
* @returns A VNode tree.
|
||||||
|
*
|
||||||
|
* @memberOf [VDOM]
|
||||||
|
*/
|
||||||
|
export function h<Attributes>(
|
||||||
|
nodeName: Component<Attributes> | string,
|
||||||
|
attributes?: Attributes,
|
||||||
|
...children: (Children | Children[])[]
|
||||||
|
): VNode
|
||||||
|
|
||||||
|
/** @namespace [App] */
|
||||||
|
|
||||||
|
type PayloadCreator<DPayload, CPayload> = ((data: DPayload) => CPayload);
|
||||||
|
|
||||||
|
/** Usable to 1st argument of `dispatch`. Usually, This is a reference to an action to be invoked by Hyperapp, with custom payload
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type Dispatchable<State extends AnyState, DPayload = void, CPayload = any> =
|
||||||
|
| State
|
||||||
|
| [State, ...Effect<State>[]]
|
||||||
|
| [Action<State, CPayload>, PayloadCreator<DPayload, CPayload>]
|
||||||
|
| [Action<State, CPayload>, CPayload]
|
||||||
|
| Action<State, void> // (state) => ({ ... }) | (state) => ([{ ... }, effect1, ...])
|
||||||
|
| Action<State, DPayload>; // (state, data) => ({ ... }) | (state, data) => ([{ ... }, effect1, ...])
|
||||||
|
|
||||||
|
/** Usable to 1st argument of `dispatch`. make strict for `init` (initial state and default payload are always undefined)
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type DispatchableOnInit<State extends AnyState, CPayload = any> =
|
||||||
|
| State
|
||||||
|
| [State, ...Effect<State>[]]
|
||||||
|
| [ActionOnInit<State, CPayload>, CPayload]
|
||||||
|
| ActionOnInit<State, void>;
|
||||||
|
|
||||||
|
/** A definition of `dispatch`. This is passed as an argument of effect or subscription runner.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type Dispatch<State extends AnyState> = <Payload>(obj: Dispatchable<State, Payload>, data: Payload) => State;
|
||||||
|
|
||||||
|
|
||||||
|
/** An effect runner. It is actually invoked when effect is reflected.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export interface EffectRunner<State extends AnyState = AnyState, Props = void> {
|
||||||
|
(dispatch: Dispatch<State>, props: Props): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** An effect as the result of an action.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type Effect<State extends AnyState = AnyState, Props = any> = [EffectRunner<State, Props>, Props] | [EffectRunner<State, void>];
|
||||||
|
|
||||||
|
/** An subscription runner. It is actually invoked when effect is reflected.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export interface SubscriptionRunner<State extends AnyState = AnyState, Props = void> {
|
||||||
|
(dispatch: Dispatch<State>, props: Props): (() => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A reference to an subscription to be managed by Hyperapp, with optional additional parameters
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type Subscription<State extends AnyState = AnyState, Props = any> = [SubscriptionRunner<State, Props>, Props] | [SubscriptionRunner<State, void>];
|
||||||
|
|
||||||
|
/** The interface for a single action implementation.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export interface Action<State extends AnyState, Payload = void> {
|
||||||
|
(state: State, data: Payload): Dispatchable<State>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The interface for a single action implementation, make strict for `init` (given state are always undefined)
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export interface ActionOnInit<State extends AnyState, Payload = void> {
|
||||||
|
(state: undefined, data: Payload): DispatchableOnInit<State>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The view function describes the application UI as a tree of VNodes.
|
||||||
|
* @returns A VNode tree.
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export interface View<State extends AnyState> {
|
||||||
|
(state: State): VNode | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The possible response types for the subscription callback for an application
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type SubscriptionsResult<State extends AnyState> = (Subscription<State> | Falsy)[] | Subscription<State> | Falsy;
|
||||||
|
|
||||||
|
type Falsy = false | '' | 0 | null | undefined;
|
||||||
|
|
||||||
|
/** The lazy view. {@link https://github.com/jorgebucaran/hyperapp/issues/721#issuecomment-402150041}
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export function Lazy<Props extends object>(props: { view: (props: Props) => VNode | null, key?: string | number | null } & Props): VNode;
|
||||||
|
|
||||||
|
/** The set of properties that define a Hyperapp application.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export interface AppProps<State extends AnyState> {
|
||||||
|
init?: DispatchableOnInit<State>;
|
||||||
|
view?: View<State>;
|
||||||
|
node: Element;
|
||||||
|
subscriptions?: (state: State) => SubscriptionsResult<State>;
|
||||||
|
middleware?: Middleware<State>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The middleware function.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type MiddlewareFunc<State extends AnyState = AnyState> = (action: Dispatchable<State>, props: unknown) => void;
|
||||||
|
|
||||||
|
/** The middleware.
|
||||||
|
*
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export type Middleware<State extends AnyState = AnyState> = (func: MiddlewareFunc<State>) => MiddlewareFunc<State>;
|
||||||
|
|
||||||
|
/** The app() call creates and renders a new application.
|
||||||
|
*
|
||||||
|
* @param state The state object.
|
||||||
|
* @param actions The actions object implementation.
|
||||||
|
* @param view The view function.
|
||||||
|
* @param container The DOM element where the app will be rendered to.
|
||||||
|
* @returns The actions wired to the application.
|
||||||
|
* @memberOf [App]
|
||||||
|
*/
|
||||||
|
export function app<State extends AnyState>(app: AppProps<State>): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class attribute value of VNode.
|
||||||
|
*
|
||||||
|
* @memberOf [VDOM]
|
||||||
|
*/
|
||||||
|
export type ClassAttribute = ClassAttributeItem | null | undefined;
|
||||||
|
|
||||||
|
type ClassAttributeItem = (string | { [key: string]: any } | ClassAttributeArray);
|
||||||
|
|
||||||
|
interface ClassAttributeArray extends Array<ClassAttributeItem> { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The style attribute value of VNode.
|
||||||
|
*
|
||||||
|
* @memberOf [VDOM]
|
||||||
|
*/
|
||||||
|
export type StyleAttribute = { [key: string]: any } | null | string | undefined;
|
||||||
|
|
||||||
|
// e.g.) onchange, onupdate, oninput, ...
|
||||||
|
//
|
||||||
|
type EventKeys = keyof GlobalEventHandlers;
|
||||||
|
|
||||||
|
type EventParameterType<Key extends EventKeys> = Parameters<Exclude<GlobalEventHandlers[Key], null | undefined>>[0];
|
||||||
|
|
||||||
|
// <div onclick={A} />
|
||||||
|
// -> A: Dispatchable<any, MouseEvent>
|
||||||
|
//
|
||||||
|
type EventAttributes = Partial<{ [key in EventKeys]: Dispatchable<AnyState, EventParameterType<key>> }>;
|
||||||
|
|
||||||
|
export interface JSXAttribute extends EventAttributes {
|
||||||
|
key?: PropertyKey;
|
||||||
|
class?: ClassAttribute;
|
||||||
|
style?: StyleAttribute;
|
||||||
|
|
||||||
|
[attrName: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// /** @namespace [JSX] */
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
interface Element extends VNode { }
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elemName: string]: JSXAttribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|