├── .github └── workflows │ ├── build.yml │ ├── check-release.yml │ ├── enforce-label.yml │ ├── prep-release.yml │ └── publish-release.yml ├── .gitignore ├── .prettierignore ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASE.md ├── examples ├── example.md └── main-logo.svg ├── install.json ├── marpyter.png ├── marpyter └── __init__.py ├── package.json ├── pyproject.toml ├── setup.py ├── src ├── document │ └── widgetFactory.ts ├── index.ts ├── rendermime │ ├── factory.ts │ └── renderer.ts ├── token.ts └── widget │ ├── marpDocumentWidget.ts │ ├── marpviewer.ts │ └── toolbar.ts ├── style ├── base.css ├── index.css └── index.js ├── tsconfig.json └── yarn.lock /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: '*' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Base Setup 18 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 19 | 20 | - name: Install dependencies 21 | run: python -m pip install -U "jupyterlab>=4.0.0b0,<5" 22 | 23 | - name: Lint the extension 24 | run: | 25 | set -eux 26 | jlpm 27 | jlpm run lint:check 28 | 29 | - name: Build the extension 30 | run: | 31 | set -eux 32 | python -m pip install .[test] 33 | 34 | jupyter labextension list 35 | jupyter labextension list 2>&1 | grep -ie "marpyter.*OK" 36 | python -m jupyterlab.browser_check 37 | 38 | - name: Package the extension 39 | run: | 40 | set -eux 41 | 42 | pip install build 43 | python -m build 44 | pip uninstall -y "marpyter" jupyterlab 45 | 46 | - name: Upload extension packages 47 | uses: actions/upload-artifact@v3 48 | with: 49 | name: extension-artifacts 50 | path: dist/marpyter* 51 | if-no-files-found: error 52 | 53 | test_isolated: 54 | needs: build 55 | runs-on: ubuntu-latest 56 | 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@v3 60 | - name: Install Python 61 | uses: actions/setup-python@v4 62 | with: 63 | python-version: '3.9' 64 | architecture: 'x64' 65 | - uses: actions/download-artifact@v3 66 | with: 67 | name: extension-artifacts 68 | - name: Install and Test 69 | run: | 70 | set -eux 71 | # Remove NodeJS, twice to take care of system and locally installed node versions. 72 | sudo rm -rf $(which node) 73 | sudo rm -rf $(which node) 74 | 75 | pip install "jupyterlab>=4.0.0b0,<5" marpyter*.whl 76 | 77 | jupyter labextension list 78 | jupyter labextension list 2>&1 | grep -ie "marpyter.*OK" 79 | python -m jupyterlab.browser_check --no-browser-test 80 | 81 | 82 | check_links: 83 | name: Check Links 84 | runs-on: ubuntu-latest 85 | timeout-minutes: 15 86 | steps: 87 | - uses: actions/checkout@v3 88 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 89 | - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 90 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | branches: ["*"] 7 | 8 | jobs: 9 | check_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Base Setup 15 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 16 | - name: Install Dependencies 17 | run: | 18 | pip install -e . 19 | - name: Check Release 20 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 21 | with: 22 | 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | 25 | - name: Upload Distributions 26 | uses: actions/upload-artifact@v3 27 | with: 28 | name: marpyter-releaser-dist-${{ github.run_number }} 29 | path: .jupyter_releaser_checkout/dist 30 | -------------------------------------------------------------------------------- /.github/workflows/enforce-label.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - name: enforce-triage-label 13 | uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 14 | -------------------------------------------------------------------------------- /.github/workflows/prep-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 1: Prep Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version_spec: 6 | description: "New Version Specifier" 7 | default: "next" 8 | required: false 9 | branch: 10 | description: "The branch to target" 11 | required: false 12 | post_version_spec: 13 | description: "Post Version Specifier" 14 | required: false 15 | since: 16 | description: "Use PRs with activity since this date or git reference" 17 | required: false 18 | since_last_stable: 19 | description: "Use PRs with activity since the last stable git tag" 20 | required: false 21 | type: boolean 22 | jobs: 23 | prep_release: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 27 | 28 | - name: Prep Release 29 | id: prep-release 30 | uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 31 | with: 32 | token: ${{ secrets.ADMIN_GITHUB_TOKEN }} 33 | version_spec: ${{ github.event.inputs.version_spec }} 34 | post_version_spec: ${{ github.event.inputs.post_version_spec }} 35 | target: ${{ github.event.inputs.target }} 36 | branch: ${{ github.event.inputs.branch }} 37 | since: ${{ github.event.inputs.since }} 38 | since_last_stable: ${{ github.event.inputs.since_last_stable }} 39 | 40 | - name: "** Next Step **" 41 | run: | 42 | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" 43 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 2: Publish Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | branch: 6 | description: "The target branch" 7 | required: false 8 | release_url: 9 | description: "The URL of the draft GitHub release" 10 | required: false 11 | steps_to_skip: 12 | description: "Comma separated list of steps to skip" 13 | required: false 14 | 15 | jobs: 16 | publish_release: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 20 | 21 | - name: Populate Release 22 | id: populate-release 23 | uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 24 | with: 25 | token: ${{ secrets.ADMIN_GITHUB_TOKEN }} 26 | target: ${{ github.event.inputs.target }} 27 | branch: ${{ github.event.inputs.branch }} 28 | release_url: ${{ github.event.inputs.release_url }} 29 | steps_to_skip: ${{ github.event.inputs.steps_to_skip }} 30 | 31 | - name: Finalize Release 32 | id: finalize-release 33 | env: 34 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} 35 | PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} 36 | TWINE_USERNAME: __token__ 37 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 39 | with: 40 | token: ${{ secrets.ADMIN_GITHUB_TOKEN }} 41 | target: ${{ github.event.inputs.target }} 42 | release_url: ${{ steps.populate-release.outputs.release_url }} 43 | 44 | - name: "** Next Step **" 45 | if: ${{ success() }} 46 | run: | 47 | echo "Verify the final release" 48 | echo ${{ steps.finalize-release.outputs.release_url }} 49 | 50 | - name: "** Failure Message **" 51 | if: ${{ failure() }} 52 | run: | 53 | echo "Failed to Publish the Draft Release Url:" 54 | echo ${{ steps.populate-release.outputs.release_url }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | *.log 5 | .eslintcache 6 | .stylelintcache 7 | *.egg-info/ 8 | .ipynb_checkpoints 9 | *.tsbuildinfo 10 | marpyter/labextension 11 | # Version file is handled by hatchling 12 | marpyter/_version.py 13 | 14 | # Created by https://www.gitignore.io/api/python 15 | # Edit at https://www.gitignore.io/?templates=python 16 | 17 | ### Python ### 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | pip-wheel-metadata/ 41 | share/python-wheels/ 42 | .installed.cfg 43 | *.egg 44 | MANIFEST 45 | 46 | # PyInstaller 47 | # Usually these files are written by a python script from a template 48 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 49 | *.manifest 50 | *.spec 51 | 52 | # Installer logs 53 | pip-log.txt 54 | pip-delete-this-directory.txt 55 | 56 | # Unit test / coverage reports 57 | htmlcov/ 58 | .tox/ 59 | .nox/ 60 | .coverage 61 | .coverage.* 62 | .cache 63 | nosetests.xml 64 | coverage/ 65 | coverage.xml 66 | *.cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | 70 | # Translations 71 | *.mo 72 | *.pot 73 | 74 | # Scrapy stuff: 75 | .scrapy 76 | 77 | # Sphinx documentation 78 | docs/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # celery beat schedule file 87 | celerybeat-schedule 88 | 89 | # SageMath parsed files 90 | *.sage.py 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | .spyproject 95 | 96 | # Rope project settings 97 | .ropeproject 98 | 99 | # Mr Developer 100 | .mr.developer.cfg 101 | .project 102 | .pydevproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | .dmypy.json 110 | dmypy.json 111 | 112 | # Pyre type checker 113 | .pyre/ 114 | 115 | # End of https://www.gitignore.io/api/python 116 | 117 | # OSX files 118 | .DS_Store 119 | 120 | # Yarn cache 121 | .yarn/ 122 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | **/lib 4 | **/package.json 5 | !/package.json 6 | marpyter 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableImmutableInstalls: false 2 | 3 | nodeLinker: node-modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## 0.1.0 6 | 7 | No merged PRs 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Trung Le 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # marpyter - Marp for JupyterLab 2 | 3 | [![Github Actions Status](https://github.com/trungleduc/marpyter/workflows/Build/badge.svg)](https://github.com/trungleduc/marpyter/actions/workflows/build.yml) 4 | 5 | `marpyter` is a JupyterLab extension allowing you to create interactive slide decks using Marp directly within JupyterLab. This extension provides a convenient way for previewing Markdown-based slides in real time. 6 | 7 | ![marpyter](marpyter.png) 8 | 9 | ## How to use 10 | 11 | To render a markdown file with `marpyter`, from the file browser panel of JupyterLab, users can right-click on the markdown file -> `Open With` -> `Marp Preview`. 12 | A new panel will be opened with the rendered content of the Marp slides. 13 | 14 | ## Requirements 15 | 16 | - JupyterLab >= 4.0.0 17 | 18 | ## Install 19 | 20 | You can install `marpyter` using mamba, pip or conda: 21 | 22 | ```bash 23 | #Using mamba 24 | mamba install marpyter 25 | #Using conda 26 | conda install marpyter 27 | #Using pip 28 | pip install marpyter 29 | ``` 30 | 31 | ## Uninstall 32 | 33 | To remove the extension, execute: 34 | 35 | ```bash 36 | #Using mamba 37 | mamba uninstall marpyter 38 | #Using conda 39 | conda uninstall marpyter 40 | #Using pip 41 | pip uninstall marpyter 42 | ``` 43 | 44 | ## Contributing 45 | 46 | ### Development install 47 | 48 | Note: You will need NodeJS to build the extension package. 49 | 50 | The `jlpm` command is JupyterLab's pinned version of 51 | [yarn](https://yarnpkg.com/) that is installed with JupyterLab. You may use 52 | `yarn` or `npm` in lieu of `jlpm` below. 53 | 54 | ```bash 55 | # Clone the repo to your local environment 56 | # Change directory to the marpyter directory 57 | # Install package in development mode 58 | pip install -e "." 59 | # Link your development version of the extension with JupyterLab 60 | jupyter labextension develop . --overwrite 61 | # Rebuild extension Typescript source after making changes 62 | jlpm build 63 | ``` 64 | 65 | You can watch the source directory and run JupyterLab at the same time in different terminals to watch for changes in the extension's source and automatically rebuild the extension. 66 | 67 | ```bash 68 | # Watch the source directory in one terminal, automatically rebuilding when needed 69 | jlpm watch 70 | # Run JupyterLab in another terminal 71 | jupyter lab 72 | ``` 73 | 74 | With the watch command running, every saved change will immediately be built locally and available in your running JupyterLab. Refresh JupyterLab to load the change in your browser (you may need to wait several seconds for the extension to be rebuilt). 75 | 76 | By default, the `jlpm build` command generates the source maps for this extension to make it easier to debug using the browser dev tools. To also generate source maps for the JupyterLab core extensions, you can run the following command: 77 | 78 | ```bash 79 | jupyter lab build --minimize=False 80 | ``` 81 | 82 | ### Development uninstall 83 | 84 | ```bash 85 | pip uninstall marpyter 86 | ``` 87 | 88 | In development mode, you will also need to remove the symlink created by `jupyter labextension develop` 89 | command. To find its location, you can run `jupyter labextension list` to figure out where the `labextensions` 90 | folder is located. Then you can remove the symlink named `marpyter` within that folder. 91 | 92 | ### Packaging the extension 93 | 94 | See [RELEASE](RELEASE.md) 95 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Making a new release of marpyter 2 | 3 | The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser). 4 | 5 | ## Manual release 6 | 7 | ### Python package 8 | 9 | This extension can be distributed as Python packages. All of the Python 10 | packaging instructions are in the `pyproject.toml` file to wrap your extension in a 11 | Python package. Before generating a package, you first need to install some tools: 12 | 13 | ```bash 14 | pip install build twine hatch 15 | ``` 16 | 17 | Bump the version using `hatch`. By default this will create a tag. 18 | See the docs on [hatch-nodejs-version](https://github.com/agoose77/hatch-nodejs-version#semver) for details. 19 | 20 | ```bash 21 | hatch version 22 | ``` 23 | 24 | To create a Python source package (`.tar.gz`) and the binary package (`.whl`) in the `dist/` directory, do: 25 | 26 | ```bash 27 | python -m build 28 | ``` 29 | 30 | > `python setup.py sdist bdist_wheel` is deprecated and will not work for this package. 31 | 32 | Then to upload the package to PyPI, do: 33 | 34 | ```bash 35 | twine upload dist/* 36 | ``` 37 | 38 | ### NPM package 39 | 40 | To publish the frontend part of the extension as a NPM package, do: 41 | 42 | ```bash 43 | npm login 44 | npm publish --access public 45 | ``` 46 | 47 | ## Automated releases with the Jupyter Releaser 48 | 49 | The extension repository should already be compatible with the Jupyter Releaser. 50 | 51 | Check out the [workflow documentation](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html) for more information. 52 | 53 | Here is a summary of the steps to cut a new release: 54 | 55 | - Add `ADMIN_GITHUB_TOKEN`, `PYPI_TOKEN` and `NPM_TOKEN` to the [Github Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) in the repository 56 | - Go to the Actions panel 57 | - Run the "Step 1: Prep Release" workflow 58 | - Check the draft changelog 59 | - Run the "Step 2: Publish Release" workflow 60 | 61 | ## Publishing to `conda-forge` 62 | 63 | If the package is not on conda forge yet, check the documentation to learn how to add it: https://conda-forge.org/docs/maintainer/adding_pkgs.html 64 | 65 | Otherwise a bot should pick up the new version publish to PyPI, and open a new PR on the feedstock repository automatically. 66 | -------------------------------------------------------------------------------- /examples/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | theme: gaia 3 | _class: lead 4 | paginate: true 5 | backgroundColor: #fff 6 | backgroundImage: url('https://marp.app/assets/hero-background.svg') 7 | --- 8 | 9 | ![bg left:40% 80%](https://marp.app/assets/marp.svg) 10 | 11 | # **Marp** 12 | 13 | Markdown Presentation Ecosystem 14 | 15 | https://marp.app/ 16 | 17 | --- 18 | 19 | # How to write slides 20 | 21 | Split pages by horizontal ruler (`---`). It's very simple! :satisfied: 22 | 23 | ```markdown 24 | # Slide 1 25 | 26 | foobar 27 | 28 | --- 29 | 30 | # Slide 2 31 | 32 | foobar 33 | ``` 34 | 35 | --- 36 | 37 | # Local file access 38 | 39 | ![width:400px](./main-logo.svg) 40 | -------------------------------------------------------------------------------- /examples/main-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | Group.svg 3 | Created using Figma 0.90 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /install.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageManager": "python", 3 | "packageName": "marpyter", 4 | "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package marpyter" 5 | } 6 | -------------------------------------------------------------------------------- /marpyter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trungleduc/marpyter/27573d2db956ffd0690d50b87b6d69ed5103b726/marpyter.png -------------------------------------------------------------------------------- /marpyter/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | 3 | 4 | def _jupyter_labextension_paths(): 5 | return [{ 6 | "src": "labextension", 7 | "dest": "marpyter" 8 | }] 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marpyter", 3 | "version": "0.1.0", 4 | "description": "A JupyterLab extension for Marp.", 5 | "keywords": [ 6 | "jupyter", 7 | "jupyterlab", 8 | "jupyterlab-extension" 9 | ], 10 | "homepage": "https://github.com/trungleduc/marpyter", 11 | "bugs": { 12 | "url": "https://github.com/trungleduc/marpyter/issues" 13 | }, 14 | "license": "BSD-3-Clause", 15 | "author": { 16 | "name": "Trung Le", 17 | "email": "leductrungxf@gmail.com" 18 | }, 19 | "files": [ 20 | "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", 21 | "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" 22 | ], 23 | "main": "lib/index.js", 24 | "types": "lib/index.d.ts", 25 | "style": "style/index.css", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/trungleduc/marpyter.git" 29 | }, 30 | "scripts": { 31 | "build": "jlpm build:lib && jlpm build:labextension:dev", 32 | "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension", 33 | "build:labextension": "jupyter labextension build .", 34 | "build:labextension:dev": "jupyter labextension build --development True .", 35 | "build:lib": "tsc --sourceMap", 36 | "build:lib:prod": "tsc", 37 | "clean": "jlpm clean:lib", 38 | "clean:lib": "rimraf lib tsconfig.tsbuildinfo", 39 | "clean:lintcache": "rimraf .eslintcache .stylelintcache", 40 | "clean:labextension": "rimraf marpyter/labextension marpyter/_version.py", 41 | "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache", 42 | "eslint": "jlpm eslint:check --fix", 43 | "eslint:check": "eslint . --cache --ext .ts,.tsx", 44 | "install:extension": "jlpm build", 45 | "lint": "jlpm stylelint && jlpm prettier && jlpm eslint", 46 | "lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check", 47 | "prettier": "jlpm prettier:base --write --list-different", 48 | "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 49 | "prettier:check": "jlpm prettier:base --check", 50 | "stylelint": "jlpm stylelint:check --fix", 51 | "stylelint:check": "stylelint --cache \"style/**/*.css\"", 52 | "watch": "run-p watch:src watch:labextension", 53 | "watch:src": "tsc -w", 54 | "watch:labextension": "jupyter labextension watch ." 55 | }, 56 | "dependencies": { 57 | "@jupyterlab/application": "^4.0.0", 58 | "@jupyterlab/coreutils": "^6.0.0", 59 | "@jupyterlab/services": "^7.0.0", 60 | "@lumino/widgets": "^2.1.1", 61 | "@marp-team/marp-core": "^3.6.0", 62 | "util": "^0.12.5" 63 | }, 64 | "devDependencies": { 65 | "@jupyterlab/builder": "^4.0.0", 66 | "@types/json-schema": "^7.0.11", 67 | "@types/react": "^18.0.26", 68 | "@typescript-eslint/eslint-plugin": "^5.55.0", 69 | "@typescript-eslint/parser": "^5.55.0", 70 | "css-loader": "^6.7.1", 71 | "eslint": "^8.36.0", 72 | "eslint-config-prettier": "^8.7.0", 73 | "eslint-plugin-prettier": "^4.2.1", 74 | "mkdirp": "^1.0.3", 75 | "npm-run-all": "^4.1.5", 76 | "prettier": "^2.8.7", 77 | "rimraf": "^4.4.1", 78 | "source-map-loader": "^1.0.2", 79 | "style-loader": "^3.3.1", 80 | "stylelint": "^14.9.1", 81 | "stylelint-config-prettier": "^9.0.4", 82 | "stylelint-config-recommended": "^8.0.0", 83 | "stylelint-config-standard": "^26.0.0", 84 | "stylelint-prettier": "^2.0.0", 85 | "typescript": "~5.0.2", 86 | "yjs": "^13.5.0" 87 | }, 88 | "sideEffects": [ 89 | "style/*.css", 90 | "style/index.js" 91 | ], 92 | "styleModule": "style/index.js", 93 | "publishConfig": { 94 | "access": "public" 95 | }, 96 | "jupyterlab": { 97 | "discovery": { 98 | "server": { 99 | "managers": [ 100 | "pip" 101 | ], 102 | "base": { 103 | "name": "marpyter" 104 | } 105 | } 106 | }, 107 | "extension": true, 108 | "outputDir": "marpyter/labextension" 109 | }, 110 | "eslintIgnore": [ 111 | "node_modules", 112 | "dist", 113 | "coverage", 114 | "**/*.d.ts" 115 | ], 116 | "eslintConfig": { 117 | "extends": [ 118 | "eslint:recommended", 119 | "plugin:@typescript-eslint/eslint-recommended", 120 | "plugin:@typescript-eslint/recommended", 121 | "plugin:prettier/recommended" 122 | ], 123 | "parser": "@typescript-eslint/parser", 124 | "parserOptions": { 125 | "project": "tsconfig.json", 126 | "sourceType": "module" 127 | }, 128 | "plugins": [ 129 | "@typescript-eslint" 130 | ], 131 | "rules": { 132 | "@typescript-eslint/naming-convention": [ 133 | "error", 134 | { 135 | "selector": "interface", 136 | "format": [ 137 | "PascalCase" 138 | ], 139 | "custom": { 140 | "regex": "^I[A-Z]", 141 | "match": true 142 | } 143 | } 144 | ], 145 | "@typescript-eslint/no-unused-vars": [ 146 | "warn", 147 | { 148 | "args": "none" 149 | } 150 | ], 151 | "@typescript-eslint/no-explicit-any": "off", 152 | "@typescript-eslint/no-namespace": "off", 153 | "@typescript-eslint/no-use-before-define": "off", 154 | "@typescript-eslint/quotes": [ 155 | "error", 156 | "single", 157 | { 158 | "avoidEscape": true, 159 | "allowTemplateLiterals": false 160 | } 161 | ], 162 | "curly": [ 163 | "error", 164 | "all" 165 | ], 166 | "eqeqeq": "error", 167 | "prefer-arrow-callback": "error" 168 | } 169 | }, 170 | "prettier": { 171 | "singleQuote": true, 172 | "trailingComma": "none", 173 | "arrowParens": "avoid", 174 | "endOfLine": "auto" 175 | }, 176 | "stylelint": { 177 | "extends": [ 178 | "stylelint-config-recommended", 179 | "stylelint-config-standard", 180 | "stylelint-prettier/recommended" 181 | ], 182 | "rules": { 183 | "property-no-vendor-prefix": null, 184 | "selector-no-vendor-prefix": null, 185 | "value-no-vendor-prefix": null 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling>=1.5.0", "jupyterlab>=4.0.0,<5", "hatch-nodejs-version"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "marpyter" 7 | readme = "README.md" 8 | license = { file = "LICENSE" } 9 | requires-python = ">=3.8" 10 | classifiers = [ 11 | "Framework :: Jupyter", 12 | "Framework :: Jupyter :: JupyterLab", 13 | "Framework :: Jupyter :: JupyterLab :: 4", 14 | "Framework :: Jupyter :: JupyterLab :: Extensions", 15 | "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", 16 | "License :: OSI Approved :: BSD License", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.8", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | ] 24 | dependencies = [ 25 | "jupyter_server>=2.0.1,<3" 26 | ] 27 | dynamic = ["version", "description", "authors", "urls", "keywords"] 28 | 29 | [tool.hatch.version] 30 | source = "nodejs" 31 | 32 | [tool.hatch.metadata.hooks.nodejs] 33 | fields = ["description", "authors", "urls"] 34 | 35 | [tool.hatch.build.targets.sdist] 36 | artifacts = ["marpyter/labextension"] 37 | exclude = [".github", "binder"] 38 | 39 | [tool.hatch.build.targets.wheel.shared-data] 40 | "marpyter/labextension" = "share/jupyter/labextensions/marpyter" 41 | "install.json" = "share/jupyter/labextensions/marpyter/install.json" 42 | "jupyter-config/server-config" = "etc/jupyter/jupyter_server_config.d" 43 | "jupyter-config/nb-config" = "etc/jupyter/jupyter_notebook_config.d" 44 | 45 | [tool.hatch.build.hooks.version] 46 | path = "marpyter/_version.py" 47 | 48 | [tool.hatch.build.hooks.jupyter-builder] 49 | dependencies = ["hatch-jupyter-builder>=0.5"] 50 | build-function = "hatch_jupyter_builder.npm_builder" 51 | ensured-targets = [ 52 | "marpyter/labextension/static/style.js", 53 | "marpyter/labextension/package.json", 54 | ] 55 | skip-if-exists = ["marpyter/labextension/static/style.js"] 56 | 57 | [tool.hatch.build.hooks.jupyter-builder.build-kwargs] 58 | build_cmd = "build:prod" 59 | npm = ["jlpm"] 60 | 61 | [tool.hatch.build.hooks.jupyter-builder.editable-build-kwargs] 62 | build_cmd = "install:extension" 63 | npm = ["jlpm"] 64 | source_dir = "src" 65 | build_dir = "marpyter/labextension" 66 | 67 | [tool.jupyter-releaser.options] 68 | version_cmd = "hatch version" 69 | 70 | [tool.jupyter-releaser.hooks] 71 | before-build-npm = [ 72 | "python -m pip install 'jupyterlab>=4.0.0,<5'", 73 | "jlpm", 74 | "jlpm build:prod" 75 | ] 76 | before-build-python = ["jlpm clean:all"] 77 | 78 | [tool.check-wheel-contents] 79 | ignore = ["W002"] 80 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | __import__('setuptools').setup() 2 | -------------------------------------------------------------------------------- /src/document/widgetFactory.ts: -------------------------------------------------------------------------------- 1 | import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; 2 | import { ABCWidgetFactory, DocumentRegistry } from '@jupyterlab/docregistry'; 3 | import { MarpDocWidget } from '../widget/marpDocumentWidget'; 4 | import { MarpViewer } from '../widget/marpviewer'; 5 | import { RenderedMarp } from '../rendermime/renderer'; 6 | import { MIMETYPE } from '../rendermime/factory'; 7 | import { ToolbarWidget } from '../widget/toolbar'; 8 | import { CommandRegistry } from '@lumino/commands'; 9 | 10 | export class MarpDocWidgetFactory extends ABCWidgetFactory { 11 | constructor(options: MarpDocWidgetFactory.IOptions) { 12 | super(options); 13 | this._rendermime = options.rendermime; 14 | this._commands = options.commands; 15 | } 16 | 17 | /** 18 | * Create a new widget given a context. 19 | * 20 | * @param context Contains the information of the file 21 | * @returns The widget 22 | */ 23 | protected createNewWidget(context: DocumentRegistry.Context): MarpDocWidget { 24 | let renderer: RenderedMarp | undefined; 25 | if (this._rendermime) { 26 | const rendermime = this._rendermime.clone({ 27 | resolver: context.urlResolver 28 | }); 29 | renderer = rendermime.createRenderer(MIMETYPE) as 30 | | RenderedMarp 31 | | undefined; 32 | } 33 | const content = new MarpViewer({ context, renderer }); 34 | 35 | const toolbar = new ToolbarWidget({ commands: this._commands }); 36 | return new MarpDocWidget({ context, content, toolbar }); 37 | } 38 | 39 | private _rendermime?: IRenderMimeRegistry; 40 | private _commands?: CommandRegistry; 41 | } 42 | 43 | export namespace MarpDocWidgetFactory { 44 | export interface IOptions extends DocumentRegistry.IWidgetFactoryOptions { 45 | rendermime?: IRenderMimeRegistry; 46 | commands: CommandRegistry; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ILayoutRestorer, 3 | JupyterFrontEnd, 4 | JupyterFrontEndPlugin 5 | } from '@jupyterlab/application'; 6 | import { WidgetTracker } from '@jupyterlab/apputils'; 7 | import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; 8 | import { ITranslator } from '@jupyterlab/translation'; 9 | 10 | import { MarpDocWidgetFactory } from './document/widgetFactory'; 11 | import { IMarpViewerTracker } from './token'; 12 | import { MarpDocWidget } from './widget/marpDocumentWidget'; 13 | import { marpRendererFactory } from './rendermime/factory'; 14 | import { MarpViewer } from './widget/marpviewer'; 15 | 16 | /** 17 | * Initialization data for the marpyter extension. 18 | */ 19 | const plugin: JupyterFrontEndPlugin = { 20 | id: 'marpyter:plugin', 21 | description: 'A JupyterLab extension.', 22 | autoStart: true, 23 | provides: IMarpViewerTracker, 24 | requires: [IRenderMimeRegistry, ITranslator], 25 | optional: [ILayoutRestorer], 26 | activate: ( 27 | app: JupyterFrontEnd, 28 | rendermime: IRenderMimeRegistry, 29 | translator: ITranslator 30 | ): IMarpViewerTracker => { 31 | console.log('JupyterLab extension marpyter is activated!'); 32 | const trans = translator.load('jupyterlab'); 33 | const { docRegistry } = app; 34 | 35 | rendermime.addFactory(marpRendererFactory); 36 | const namespace = 'marpyter-widget'; 37 | const tracker = new WidgetTracker({ 38 | namespace 39 | }); 40 | 41 | app.commands.addCommand('marpyter:download', { 42 | execute: args => { 43 | const current = tracker.currentWidget; 44 | if (current) { 45 | const marpViwer = current.content as MarpViewer; 46 | const htmlContent = marpViwer.htmlContent; 47 | if (!htmlContent) { 48 | return; 49 | } 50 | const element = document.createElement('a'); 51 | element.setAttribute( 52 | 'href', 53 | 'data:text/plain;charset=utf-8,' + encodeURIComponent(htmlContent) 54 | ); 55 | const fileName = 56 | current.context.path 57 | .split('\\') 58 | ?.pop() 59 | ?.split('/') 60 | ?.pop() 61 | ?.split('.')?.[0] ?? 'Untitled'; 62 | 63 | element.setAttribute('download', `${fileName}.html`); 64 | element.click(); 65 | } 66 | } 67 | }); 68 | const factory = new MarpDocWidgetFactory({ 69 | name: 'Marp Doc', 70 | label: trans.__('Marp Preview'), 71 | fileTypes: ['markdown'], 72 | rendermime, 73 | commands: app.commands 74 | }); 75 | factory.widgetCreated.connect((sender, widget) => { 76 | // Notify the widget tracker if restore data needs to update. 77 | widget.context.pathChanged.connect(() => { 78 | void tracker.save(widget); 79 | }); 80 | void tracker.add(widget); 81 | }); 82 | docRegistry.addWidgetFactory(factory); 83 | 84 | return tracker; 85 | } 86 | }; 87 | 88 | export default [plugin]; 89 | -------------------------------------------------------------------------------- /src/rendermime/factory.ts: -------------------------------------------------------------------------------- 1 | import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; 2 | import { RenderedMarp } from './renderer'; 3 | 4 | export const MIMETYPE = 'text/marp'; 5 | export const marpRendererFactory: IRenderMime.IRendererFactory = { 6 | safe: true, 7 | mimeTypes: [MIMETYPE], 8 | defaultRank: 60, 9 | createRenderer: options => new RenderedMarp(options) 10 | }; 11 | -------------------------------------------------------------------------------- /src/rendermime/renderer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RenderedHTMLCommon, 3 | IRenderMime, 4 | renderHTML 5 | } from '@jupyterlab/rendermime'; 6 | import Marp from '@marp-team/marp-core'; 7 | 8 | export class RenderedMarp extends RenderedHTMLCommon { 9 | constructor(options: IRenderMime.IRendererOptions) { 10 | super(options); 11 | this.addClass('jp-RenderedMarp'); 12 | } 13 | 14 | /** 15 | * Render a mime model. 16 | * 17 | * @param model - The mime model to render. 18 | * 19 | * @returns A promise which resolves when rendering is complete. 20 | */ 21 | render(model: IRenderMime.IMimeModel): Promise { 22 | return renderMarp({ 23 | host: this.node, 24 | source: String(model.data[this.mimeType]), 25 | trusted: model.trusted, 26 | resolver: this.resolver, 27 | sanitizer: this.sanitizer, 28 | linkHandler: this.linkHandler, 29 | shouldTypeset: this.isAttached, 30 | latexTypesetter: this.latexTypesetter 31 | }); 32 | } 33 | } 34 | 35 | export async function renderMarp( 36 | options: renderMarp.IRenderOptions 37 | ): Promise { 38 | // Unpack the options. 39 | const { host, source, ...others } = options; 40 | 41 | // Clear the content if there is no source. 42 | if (!source) { 43 | host.textContent = ''; 44 | return; 45 | } 46 | 47 | const marp = new Marp({ html: true }); 48 | const { html, css } = marp.render(source, { htmlAsArray: false }); 49 | 50 | // Render HTML 51 | const htmlBody = document.createElement('div'); 52 | 53 | await renderHTML({ 54 | host: htmlBody, 55 | source: html, 56 | ...others 57 | }); 58 | const customStyle = '\n'; 59 | const htmlSource = `${customStyle}${htmlBody.innerHTML}`; 60 | host.innerHTML = htmlSource; 61 | } 62 | 63 | export namespace renderMarp { 64 | /** 65 | * The options for the `renderMarkdown` function. 66 | */ 67 | export interface IRenderOptions { 68 | /** 69 | * The host node for the rendered Markdown. 70 | */ 71 | host: HTMLElement; 72 | 73 | /** 74 | * The Markdown source to render. 75 | */ 76 | source: string; 77 | 78 | /** 79 | * Whether the source is trusted. 80 | */ 81 | trusted: boolean; 82 | 83 | /** 84 | * The html sanitizer for untrusted source. 85 | */ 86 | sanitizer: IRenderMime.ISanitizer; 87 | 88 | /** 89 | * An optional url resolver. 90 | */ 91 | resolver: IRenderMime.IResolver | null; 92 | 93 | /** 94 | * An optional link handler. 95 | */ 96 | linkHandler: IRenderMime.ILinkHandler | null; 97 | 98 | /** 99 | * Whether the node should be typeset. 100 | */ 101 | shouldTypeset: boolean; 102 | 103 | /** 104 | * The LaTeX typesetter for the application. 105 | */ 106 | latexTypesetter: IRenderMime.ILatexTypesetter | null; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/token.ts: -------------------------------------------------------------------------------- 1 | import { IWidgetTracker } from '@jupyterlab/apputils'; 2 | import { IDocumentWidget } from '@jupyterlab/docregistry'; 3 | import { Token } from '@lumino/coreutils'; 4 | import { Widget } from '@lumino/widgets'; 5 | 6 | import { MarpDocWidget } from './widget/marpDocumentWidget'; 7 | 8 | export const IMarpViewerTracker = new Token( 9 | 'marpyter:IMarpViewerTracker', 10 | 'A widget tracker for marpyter.' 11 | ); 12 | 13 | export type IMarpViewerTracker = IWidgetTracker; 14 | 15 | export type IMarpDocWidget = IDocumentWidget; 16 | -------------------------------------------------------------------------------- /src/widget/marpDocumentWidget.ts: -------------------------------------------------------------------------------- 1 | import { DocumentWidget } from '@jupyterlab/docregistry'; 2 | import { Widget } from '@lumino/widgets'; 3 | 4 | export class MarpDocWidget extends DocumentWidget { 5 | onResize = (msg: any): void => { 6 | window.dispatchEvent(new Event('resize')); 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/widget/marpviewer.ts: -------------------------------------------------------------------------------- 1 | import { Widget } from '@lumino/widgets'; 2 | import { Marp } from '@marp-team/marp-core'; 3 | import { DocumentRegistry } from '@jupyterlab/docregistry'; 4 | import { PromiseDelegate } from '@lumino/coreutils'; 5 | import { ActivityMonitor } from '@jupyterlab/coreutils'; 6 | import { Message } from '@lumino/messaging'; 7 | import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; 8 | import { MIMETYPE } from '../rendermime/factory'; 9 | import { RenderedMarp } from '../rendermime/renderer'; 10 | export class MarpViewer extends Widget { 11 | constructor(options: { 12 | context: DocumentRegistry.Context; 13 | renderer?: RenderedMarp; 14 | }) { 15 | const node = document.createElement('iframe'); 16 | super({ node }); 17 | this._context = options.context; 18 | this._renderer = options.renderer; 19 | 20 | void this._context.ready.then(async () => { 21 | this._renderContent(); 22 | this._monitor = new ActivityMonitor({ 23 | signal: this._context.model.contentChanged 24 | }); 25 | this._monitor.activityStopped.connect(this.update, this); 26 | 27 | this._ready.resolve(undefined); 28 | }); 29 | 30 | this.addClass('marpyter-viewer'); 31 | } 32 | 33 | get ready(): Promise { 34 | return this._ready.promise; 35 | } 36 | get htmlContent(): string | undefined { 37 | return this._htmlContent; 38 | } 39 | /** 40 | * Dispose of the resources held by the widget. 41 | */ 42 | dispose(): void { 43 | if (this.isDisposed) { 44 | return; 45 | } 46 | if (this._monitor) { 47 | this._monitor.dispose(); 48 | } 49 | this._monitor = null; 50 | super.dispose(); 51 | } 52 | 53 | /** 54 | * Handle an `update-request` message to the widget. 55 | */ 56 | protected onUpdateRequest(msg: Message): void { 57 | if (this._context.isReady && !this.isDisposed) { 58 | void this._renderContent(); 59 | } 60 | } 61 | 62 | private _renderContent(): void { 63 | const content = this._context.model.toString(); 64 | 65 | if (!this._renderer) { 66 | return; 67 | } 68 | 69 | const body = (this.node as HTMLIFrameElement).contentWindow?.document.body; 70 | 71 | if (body) { 72 | this._renderer 73 | .render({ 74 | trusted: true, 75 | data: { [MIMETYPE]: content }, 76 | metadata: {}, 77 | setData: () => { 78 | /** */ 79 | } 80 | }) 81 | .then(() => { 82 | this._htmlContent = body.innerHTML = this._renderer!.node.innerHTML; 83 | }); 84 | } 85 | } 86 | private _context: DocumentRegistry.Context; 87 | private _htmlContent: string | undefined; 88 | private _ready = new PromiseDelegate(); 89 | private _monitor: ActivityMonitor | null = 90 | null; 91 | private _renderer: RenderedMarp | undefined; 92 | } 93 | -------------------------------------------------------------------------------- /src/widget/toolbar.ts: -------------------------------------------------------------------------------- 1 | import { CommandRegistry } from '@lumino/commands'; 2 | import { CommandToolbarButton } from '@jupyterlab/apputils'; 3 | import { Toolbar, saveIcon } from '@jupyterlab/ui-components'; 4 | 5 | export class ToolbarWidget extends Toolbar { 6 | constructor(options: ToolbarWidget.IOptions) { 7 | super(options); 8 | 9 | if (options.commands) { 10 | this.addItem( 11 | 'download', 12 | new CommandToolbarButton({ 13 | id: 'marpyter:download', 14 | label: 'Save HTML', 15 | icon: saveIcon, 16 | commands: options.commands 17 | }) 18 | ); 19 | } 20 | } 21 | } 22 | 23 | export namespace ToolbarWidget { 24 | export interface IOptions extends Toolbar.IOptions { 25 | commands?: CommandRegistry; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /style/base.css: -------------------------------------------------------------------------------- 1 | /* 2 | See the JupyterLab Developer Guide for useful CSS Patterns: 3 | 4 | https://jupyterlab.readthedocs.io/en/stable/developer/css.html 5 | */ 6 | -------------------------------------------------------------------------------- /style/index.css: -------------------------------------------------------------------------------- 1 | @import 'base.css'; 2 | -------------------------------------------------------------------------------- /style/index.js: -------------------------------------------------------------------------------- 1 | import './base.css'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "composite": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "incremental": true, 8 | "jsx": "react", 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noImplicitAny": true, 13 | "noUnusedLocals": false, 14 | "preserveWatchOutput": true, 15 | "resolveJsonModule": true, 16 | "outDir": "lib", 17 | "rootDir": "src", 18 | "strict": true, 19 | "strictNullChecks": true, 20 | "target": "ES2018", 21 | "types": [] 22 | }, 23 | "include": ["src/**/*"] 24 | } 25 | --------------------------------------------------------------------------------