├── .github └── workflows │ ├── check-release.yml │ ├── enforce-label.yml │ ├── main.yml │ ├── prep-release.yml │ └── publish-release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── RELEASE.md ├── dev-environment.yml ├── docs ├── _static │ ├── icon.svg │ └── try_examples.css ├── bqplot.ipynb ├── changelog.md ├── conf.py ├── configuration.md ├── custom_contents │ ├── data.csv │ └── read_csv.ipynb ├── directives │ ├── jupyterlite.md │ ├── my_markdown_notebook.md │ ├── my_notebook.ipynb │ ├── notebooklite.md │ ├── replite.md │ ├── try_examples.md │ └── voici.md ├── environment.yml ├── full.md ├── index.md ├── installation.md └── sample_overrides.json ├── jupyterlite_sphinx ├── __init__.py ├── _try_examples.py ├── jupyterlite_sphinx.css ├── jupyterlite_sphinx.js └── jupyterlite_sphinx.py └── pyproject.toml /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: ["main"] 5 | pull_request: 6 | 7 | permissions: 8 | contents: 9 | write 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | check_release: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 30 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Base Setup 23 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 24 | - name: Check Release 25 | uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | version_spec: next 29 | 30 | - name: Upload Distributions 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: jupyterlite-sphinx-releaser-dist-${{ github.run_number }} 34 | path: .jupyter_releaser_checkout/dist 35 | -------------------------------------------------------------------------------- /.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/main.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | defaults: 12 | run: 13 | shell: bash -l {0} 14 | 15 | jobs: 16 | run: 17 | runs-on: ${{ matrix.os }} 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | os: [ubuntu-latest] 23 | python-version: ['3.9'] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Setup conda 30 | uses: conda-incubator/setup-miniconda@v3 31 | with: 32 | activate-environment: jupyterlite-sphinx 33 | environment-file: dev-environment.yml 34 | python-version: ${{ matrix.python-version }} 35 | miniforge-version: latest 36 | auto-activate-base: false 37 | 38 | - name: Test PEP8 39 | run: black --check jupyterlite_sphinx 40 | -------------------------------------------------------------------------------- /.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 | # silent: 16 | # description: "Set a placeholder in the changelog and don't publish the release." 17 | # required: false 18 | # type: boolean 19 | since: 20 | description: "Use PRs with activity since this date or git reference" 21 | required: false 22 | since_last_stable: 23 | description: "Use PRs with activity since the last stable git tag" 24 | required: false 25 | type: boolean 26 | jobs: 27 | prep_release: 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: write 31 | steps: 32 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 33 | 34 | - name: Prep Release 35 | id: prep-release 36 | uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | version_spec: ${{ github.event.inputs.version_spec }} 40 | # silent: ${{ github.event.inputs.silent }} 41 | post_version_spec: ${{ github.event.inputs.post_version_spec }} 42 | branch: ${{ github.event.inputs.branch }} 43 | since: ${{ github.event.inputs.since }} 44 | since_last_stable: ${{ github.event.inputs.since_last_stable }} 45 | 46 | - name: "** Next Step **" 47 | run: | 48 | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" 49 | -------------------------------------------------------------------------------- /.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 | environment: release 19 | permissions: 20 | id-token: write 21 | steps: 22 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 23 | 24 | - uses: actions/create-github-app-token@v1 25 | id: app-token 26 | with: 27 | app-id: ${{ vars.APP_ID }} 28 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 29 | 30 | - name: Populate Release 31 | id: populate-release 32 | uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 33 | with: 34 | token: ${{ steps.app-token.outputs.token }} 35 | branch: ${{ github.event.inputs.branch }} 36 | release_url: ${{ github.event.inputs.release_url }} 37 | steps_to_skip: ${{ github.event.inputs.steps_to_skip }} 38 | 39 | - name: Finalize Release 40 | id: finalize-release 41 | env: 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 44 | with: 45 | token: ${{ steps.app-token.outputs.token }} 46 | release_url: ${{ steps.populate-release.outputs.release_url }} 47 | 48 | - name: "** Next Step **" 49 | if: ${{ success() }} 50 | run: | 51 | echo "Verify the final release" 52 | echo ${{ steps.finalize-release.outputs.release_url }} 53 | 54 | - name: "** Failure Message **" 55 | if: ${{ failure() }} 56 | run: | 57 | echo "Failed to Publish the Draft Release Url:" 58 | echo ${{ steps.populate-release.outputs.release_url }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | jupyterlite_sphinx/jupyterlite_sphinx.egg-info 2 | **/.ipynb_checkpoints 3 | **/__pycache__ 4 | docs/build 5 | build 6 | dist 7 | .jupyterlite.doit.db 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/mirrors-prettier 3 | rev: 'v4.0.0-alpha.8' # Use the sha / tag you want to point at 4 | hooks: 5 | - id: prettier 6 | types_or: [css, javascript] 7 | - repo: https://github.com/astral-sh/ruff-pre-commit 8 | # Ruff version. 9 | rev: v0.11.11 10 | hooks: 11 | # Run the linter. 12 | - id: ruff 13 | args: [ --fix ] 14 | 15 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-22.04" 5 | tools: 6 | python: "mambaforge-23.11" 7 | 8 | conda: 9 | environment: dev-environment.yml 10 | 11 | sphinx: 12 | builder: html 13 | configuration: docs/conf.py 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## 0.20.2 6 | 7 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.20.1...6cc40bf3ae417a5c827ce72750b5b6790ecd0c0d)) 8 | 9 | ### Enhancements made 10 | 11 | - Bump jupyterlite-core dependency [#299](https://github.com/jupyterlite/jupyterlite-sphinx/pull/299) ([@martinRenou](https://github.com/martinRenou)) 12 | 13 | ### Maintenance and upkeep improvements 14 | 15 | ### Contributors to this release 16 | 17 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2025-05-08&to=2025-06-03&type=c)) 18 | 19 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2025-05-08..2025-06-03&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2025-05-08..2025-06-03&type=Issues) 20 | 21 | 22 | 23 | ## 0.20.1 24 | 25 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.20.0...637c32e24fd5fe81459cab4e0c8f9d80d0789e15)) 26 | 27 | ### Bugs fixed 28 | 29 | - Fix `IndexError`s when the "Examples" is the last section in a docstring [#292](https://github.com/jupyterlite/jupyterlite-sphinx/pull/292) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 30 | 31 | ### Maintenance and upkeep improvements 32 | 33 | ### Contributors to this release 34 | 35 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2025-04-28&to=2025-05-08&type=c)) 36 | 37 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2025-04-28..2025-05-08&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2025-04-28..2025-05-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2025-04-28..2025-05-08&type=Issues) 38 | 39 | ## 0.20.0 40 | 41 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.19.1...9a59d086cb22b57731d05365f7c10747ef9d2ad1)) 42 | 43 | ### Enhancements made 44 | 45 | - Allow adding contents from outside the Sphinx source directory [#280](https://github.com/jupyterlite/jupyterlite-sphinx/pull/280) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 46 | - Fix try button and shadows for dark theme [#279](https://github.com/jupyterlite/jupyterlite-sphinx/pull/279) ([@IsabelParedes](https://github.com/IsabelParedes)) 47 | 48 | ### Bugs fixed 49 | 50 | - Fix incorrect disable marker for `TryExamples` buttons [#284](https://github.com/jupyterlite/jupyterlite-sphinx/pull/284) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 51 | 52 | ### Maintenance and upkeep improvements 53 | 54 | - pin micromamba to v2.0.5 to fix failing RTD builds [#277](https://github.com/jupyterlite/jupyterlite-sphinx/pull/277) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 55 | 56 | ### Contributors to this release 57 | 58 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2025-02-24&to=2025-04-28&type=c)) 59 | 60 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2025-02-24..2025-04-28&type=Issues) | [@IsabelParedes](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AIsabelParedes+updated%3A2025-02-24..2025-04-28&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2025-02-24..2025-04-28&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2025-02-24..2025-04-28&type=Issues) 61 | 62 | ## 0.19.1 63 | 64 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.19.0...1f2bb9736eef1b9c63e2f7659b8ff965ef02fe85)) 65 | 66 | ### Bugs fixed 67 | 68 | - BUG: Fix SciPy `make dist` doc workflow by ensuring jupyter is run with the current Python executable [#271](https://github.com/jupyterlite/jupyterlite-sphinx/pull/271) ([@steppi](https://github.com/steppi)) 69 | 70 | ### Maintenance and upkeep improvements 71 | 72 | ### Documentation improvements 73 | 74 | - Change button text contrast to comply with WCAG AA [#269](https://github.com/jupyterlite/jupyterlite-sphinx/pull/269) ([@mfisher87](https://github.com/mfisher87)) 75 | 76 | ### Contributors to this release 77 | 78 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2025-02-12&to=2025-02-24&type=c)) 79 | 80 | [@mfisher87](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Amfisher87+updated%3A2025-02-12..2025-02-24&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2025-02-12..2025-02-24&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2025-02-12..2025-02-24&type=Issues) 81 | 82 | ## 0.19.0 83 | 84 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.18.0...9cf9a249d02d99f68eb0c2ad49747607a19ba0b9)) 85 | 86 | ### Enhancements made 87 | 88 | - Better mobile device detection for interactive examples buttons [#250](https://github.com/jupyterlite/jupyterlite-sphinx/pull/250) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 89 | 90 | ### Bugs fixed 91 | 92 | - BUG: Fix list of possible docstring section header patterns for `global_enable_try_examples` [#263](https://github.com/jupyterlite/jupyterlite-sphinx/pull/263) ([@steppi](https://github.com/steppi)) 93 | - Add `ConfigLoader` with deduplicated logging and `try_examples` config caching [#249](https://github.com/jupyterlite/jupyterlite-sphinx/pull/249) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 94 | 95 | ### Maintenance and upkeep improvements 96 | 97 | - Update to `actions/upload-artifact@v4` [#267](https://github.com/jupyterlite/jupyterlite-sphinx/pull/267) ([@jtpio](https://github.com/jtpio)) 98 | 99 | ### Documentation improvements 100 | 101 | - Update documentation around fullscreen usage of `jupyterlite-sphinx` apps [#253](https://github.com/jupyterlite/jupyterlite-sphinx/pull/253) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 102 | 103 | ### Contributors to this release 104 | 105 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2025-01-13&to=2025-02-12&type=c)) 106 | 107 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2025-01-13..2025-02-12&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2025-01-13..2025-02-12&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2025-01-13..2025-02-12&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2025-01-13..2025-02-12&type=Issues) 108 | 109 | ## 0.18.0 110 | 111 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.17.1...a908c141dbbecfacdacf4a3382b526c30eda24d7)) 112 | 113 | ### Enhancements made 114 | 115 | - Allow enabling/disabling REPL code execution in the `Replite` directive [#245](https://github.com/jupyterlite/jupyterlite-sphinx/pull/245) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 116 | 117 | ### Bugs fixed 118 | 119 | - Correctly handle case where "See Also" section follows "Examples" in `global_enable_try_examples` [#251](https://github.com/jupyterlite/jupyterlite-sphinx/pull/251) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 120 | 121 | ### Maintenance and upkeep improvements 122 | 123 | - Drop Python 3.8 [#239](https://github.com/jupyterlite/jupyterlite-sphinx/pull/239) ([@jtpio](https://github.com/jtpio)) 124 | - Relax `jupyterlite-core` and `jupyterlite-xeus` dependencies [#238](https://github.com/jupyterlite/jupyterlite-sphinx/pull/238) ([@jtpio](https://github.com/jtpio)) 125 | 126 | ### Contributors to this release 127 | 128 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-12-22&to=2025-01-13&type=c)) 129 | 130 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-12-22..2025-01-13&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2024-12-22..2025-01-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2024-12-22..2025-01-13&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2024-12-22..2025-01-13&type=Issues) 131 | 132 | ## 0.17.1 133 | 134 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.17.0...f51bdc6e0971c45862af66eb68e10bfe6935f538)) 135 | 136 | ### Bugs fixed 137 | 138 | - Add missing dependency on `jupytext` [#236](https://github.com/jupyterlite/jupyterlite-sphinx/pull/236) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 139 | 140 | ### Contributors to this release 141 | 142 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-12-22&to=2024-12-22&type=c)) 143 | 144 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-12-22..2024-12-22&type=Issues) 145 | 146 | ## 0.17.0 147 | 148 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.16.5...548b2151dfd593ae6633348e4df10348191495a9)) 149 | 150 | ### Enhancements made 151 | 152 | - Add a new-tabbed variant for the `Replite` directive, and allow customisation of its button text [#228](https://github.com/jupyterlite/jupyterlite-sphinx/pull/228) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 153 | - Allow global and custom button texts for the new-tabbed variants of the `JupyterLite`, `NotebookLite`, and the `Voici` directives [#227](https://github.com/jupyterlite/jupyterlite-sphinx/pull/227) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 154 | - Allow the use of a custom `overrides.json` file for configuring JupyterLite at runtime [#225](https://github.com/jupyterlite/jupyterlite-sphinx/pull/225) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 155 | - Add an option to open the Notebook UI and Voici apps in a new tab via the`NotebookLite` and `Voici` directives [#223](https://github.com/jupyterlite/jupyterlite-sphinx/pull/223) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 156 | - Support the usage of Markdown-based notebooks with "Lite" directives [#221](https://github.com/jupyterlite/jupyterlite-sphinx/pull/221) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 157 | 158 | ### Bugs fixed 159 | 160 | - Fix paths for Replite apps [#224](https://github.com/jupyterlite/jupyterlite-sphinx/pull/224) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 161 | 162 | ### Maintenance and upkeep improvements 163 | 164 | - Add some spacing below the Lite iframes [#235](https://github.com/jupyterlite/jupyterlite-sphinx/pull/235) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 165 | - Switch to Miniforge and drop Mambaforge [#233](https://github.com/jupyterlite/jupyterlite-sphinx/pull/233) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 166 | - Bump OS image and dependencies for Read the Docs build config [#229](https://github.com/jupyterlite/jupyterlite-sphinx/pull/229) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 167 | - Fix the opening of notebooks in a new tab when using the `JupyterLite`, `NotebookLite`, and `Voici` directives [#220](https://github.com/jupyterlite/jupyterlite-sphinx/pull/220) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 168 | 169 | ### Contributors to this release 170 | 171 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-08-08&to=2024-12-22&type=c)) 172 | 173 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-08-08..2024-12-22&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2024-08-08..2024-12-22&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2024-08-08..2024-12-22&type=Issues) 174 | 175 | ## 0.16.5 176 | 177 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.16.4...450a81d3a5e3a0ce0b56b67aafaa413323bc22b9)) 178 | 179 | ### Bugs fixed 180 | 181 | - Restore backwards compatibility with Sphinx \<8 [#201](https://github.com/jupyterlite/jupyterlite-sphinx/pull/201) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 182 | 183 | ### Contributors to this release 184 | 185 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-08-07&to=2024-08-08&type=c)) 186 | 187 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-08-07..2024-08-08&type=Issues) 188 | 189 | ## 0.16.4 190 | 191 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.9.3...cf767125be27d1832c9a61ad3e46e7bb19c927ee)) 192 | 193 | ### Enhancements made 194 | 195 | - Strip tagged cells from `.ipynb` notebooks passed to the `NotebookLite` and `JupyterLite` directives [#180](https://github.com/jupyterlite/jupyterlite-sphinx/pull/180) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 196 | - Pass additional configuration options to the `jupyter lite build` command [#169](https://github.com/jupyterlite/jupyterlite-sphinx/pull/169) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 197 | - Add the option to open JupyterLite window in new tab [#165](https://github.com/jupyterlite/jupyterlite-sphinx/pull/165) ([@melissawm](https://github.com/melissawm)) 198 | - Allow usage of global configuration values for `TryExamples` directive if provided by user [#161](https://github.com/jupyterlite/jupyterlite-sphinx/pull/161) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 199 | - Minor refactor + typing info [#155](https://github.com/jupyterlite/jupyterlite-sphinx/pull/155) ([@Carreau](https://github.com/Carreau)) 200 | - Give a pragmatic solution to suppressing noisy output [#153](https://github.com/jupyterlite/jupyterlite-sphinx/pull/153) ([@steppi](https://github.com/steppi)) 201 | - Set lower default verbosity and add config options. [#150](https://github.com/jupyterlite/jupyterlite-sphinx/pull/150) ([@Carreau](https://github.com/Carreau)) 202 | - ENH: Add metadata for parallel_read_safe = True [#148](https://github.com/jupyterlite/jupyterlite-sphinx/pull/148) ([@steppi](https://github.com/steppi)) 203 | - Add option for initial warning cell in try examples directive [#143](https://github.com/jupyterlite/jupyterlite-sphinx/pull/143) ([@steppi](https://github.com/steppi)) 204 | - Hide buttons on smaller screens (mobile). [#141](https://github.com/jupyterlite/jupyterlite-sphinx/pull/141) ([@Carreau](https://github.com/Carreau)) 205 | - Suggestion: Add pre-commit to format js and css files. [#137](https://github.com/jupyterlite/jupyterlite-sphinx/pull/137) ([@Carreau](https://github.com/Carreau)) 206 | - Add a full screen "Open in tab" button [#135](https://github.com/jupyterlite/jupyterlite-sphinx/pull/135) ([@Carreau](https://github.com/Carreau)) 207 | - Add a loading spinner for TryExamples directive. [#133](https://github.com/jupyterlite/jupyterlite-sphinx/pull/133) ([@steppi](https://github.com/steppi)) 208 | - Misc parsing warnings. [#131](https://github.com/jupyterlite/jupyterlite-sphinx/pull/131) ([@Carreau](https://github.com/Carreau)) 209 | - Improve TryExamples customization [#129](https://github.com/jupyterlite/jupyterlite-sphinx/pull/129) ([@steppi](https://github.com/steppi)) 210 | - Add option to disable TryExamples without rebuilding docs [#118](https://github.com/jupyterlite/jupyterlite-sphinx/pull/118) ([@steppi](https://github.com/steppi)) 211 | - Add more configuration options to TryExamples directive and add documentation [#116](https://github.com/jupyterlite/jupyterlite-sphinx/pull/116) ([@steppi](https://github.com/steppi)) 212 | - Update to jupyterlite v0.2 [#113](https://github.com/jupyterlite/jupyterlite-sphinx/pull/113) ([@martinRenou](https://github.com/martinRenou)) 213 | - Add try_examples directive for adding interactivity to sphinx Examples sections [#111](https://github.com/jupyterlite/jupyterlite-sphinx/pull/111) ([@steppi](https://github.com/steppi)) 214 | 215 | ### Bugs fixed 216 | 217 | - Fix compatibility with Sphinx 8 [#199](https://github.com/jupyterlite/jupyterlite-sphinx/pull/199) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 218 | - Fix incorrect regex usage instructions for `TryExamples` JSON configuration file [#194](https://github.com/jupyterlite/jupyterlite-sphinx/pull/194) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 219 | - Restore SameFileError check when copying notebooks [#190](https://github.com/jupyterlite/jupyterlite-sphinx/pull/190) ([@melissawm](https://github.com/melissawm)) 220 | - Fix invalid schema for `jupyterlite_sphinx_strip` tag when `strip_tagged_cells` is `True` [#189](https://github.com/jupyterlite/jupyterlite-sphinx/pull/189) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 221 | - Resolve default-encoding errors on Windows [#187](https://github.com/jupyterlite/jupyterlite-sphinx/pull/187) ([@AA-Turner](https://github.com/AA-Turner)) 222 | - Hotfix: cell metadata to parse should be `jupyterlite_sphinx_strip` [#185](https://github.com/jupyterlite/jupyterlite-sphinx/pull/185) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 223 | - `TryExamples` directive: fix missing kernels in CircleCI deployments [#182](https://github.com/jupyterlite/jupyterlite-sphinx/pull/182) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 224 | - Issue 115 wrap fullscreen JupyterLite links [#181](https://github.com/jupyterlite/jupyterlite-sphinx/pull/181) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 225 | - Give a pragmatic solution to suppressing noisy output [#153](https://github.com/jupyterlite/jupyterlite-sphinx/pull/153) ([@steppi](https://github.com/steppi)) 226 | - Add processing of literal blocks in try examples directive [#134](https://github.com/jupyterlite/jupyterlite-sphinx/pull/134) ([@steppi](https://github.com/steppi)) 227 | 228 | ### Maintenance and upkeep improvements 229 | 230 | - Allow for JupyterLite 0.4.0 [#193](https://github.com/jupyterlite/jupyterlite-sphinx/pull/193) ([@jtpio](https://github.com/jtpio)) 231 | - Use the latest `jupyterlite-xeus` to fix the docs build [#179](https://github.com/jupyterlite/jupyterlite-sphinx/pull/179) ([@jtpio](https://github.com/jtpio)) 232 | - Update to `jupyterlite-core >=0.2,<0.4` [#160](https://github.com/jupyterlite/jupyterlite-sphinx/pull/160) ([@jtpio](https://github.com/jtpio)) 233 | - Update releaser workflows [#159](https://github.com/jupyterlite/jupyterlite-sphinx/pull/159) ([@jtpio](https://github.com/jtpio)) 234 | - Raise informative error message when building man on older sphinx [#158](https://github.com/jupyterlite/jupyterlite-sphinx/pull/158) ([@Carreau](https://github.com/Carreau)) 235 | - Add ruff pre-commit and reformat files with it [#156](https://github.com/jupyterlite/jupyterlite-sphinx/pull/156) ([@Carreau](https://github.com/Carreau)) 236 | - Minor refactor + typing info [#155](https://github.com/jupyterlite/jupyterlite-sphinx/pull/155) ([@Carreau](https://github.com/Carreau)) 237 | - Run pre-commit on all files in this repository. [#145](https://github.com/jupyterlite/jupyterlite-sphinx/pull/145) ([@Carreau](https://github.com/Carreau)) 238 | - Update publish workflow to use the PyPI trusted publisher [#123](https://github.com/jupyterlite/jupyterlite-sphinx/pull/123) ([@jtpio](https://github.com/jtpio)) 239 | 240 | ### Documentation improvements 241 | 242 | - Issue 115 wrap fullscreen JupyterLite links [#181](https://github.com/jupyterlite/jupyterlite-sphinx/pull/181) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 243 | - Some general formatting fixes (punctuation, backticks, etc.) [#172](https://github.com/jupyterlite/jupyterlite-sphinx/pull/172) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 244 | - Fix incorrect math processing [#139](https://github.com/jupyterlite/jupyterlite-sphinx/pull/139) ([@steppi](https://github.com/steppi)) 245 | - Update to `jupyterlite-xeus` [#138](https://github.com/jupyterlite/jupyterlite-sphinx/pull/138) ([@jtpio](https://github.com/jtpio)) 246 | - Improve TryExamples customization [#129](https://github.com/jupyterlite/jupyterlite-sphinx/pull/129) ([@steppi](https://github.com/steppi)) 247 | 248 | ### Contributors to this release 249 | 250 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-09-13&to=2024-08-07&type=c)) 251 | 252 | [@AA-Turner](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AAA-Turner+updated%3A2023-09-13..2024-08-07&type=Issues) | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2023-09-13..2024-08-07&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3ACarreau+updated%3A2023-09-13..2024-08-07&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2023-09-13..2024-08-07&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2023-09-13..2024-08-07&type=Issues) | [@matthewfeickert](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Amatthewfeickert+updated%3A2023-09-13..2024-08-07&type=Issues) | [@mattip](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Amattip+updated%3A2023-09-13..2024-08-07&type=Issues) | [@melissawm](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Amelissawm+updated%3A2023-09-13..2024-08-07&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2023-09-13..2024-08-07&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2023-09-13..2024-08-07&type=Issues) | [@WarrenWeckesser](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AWarrenWeckesser+updated%3A2023-09-13..2024-08-07&type=Issues) 253 | 254 | ## 0.16.3 255 | 256 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.16.2...d5d1cc1b9fb9f1446915dc98d36ed25ad2b2878f)) 257 | 258 | ### Maintenance and upkeep improvements 259 | 260 | - Allow for JupyterLite 0.4.0 [#193](https://github.com/jupyterlite/jupyterlite-sphinx/pull/193) ([@jtpio](https://github.com/jtpio)) 261 | 262 | ### Contributors to this release 263 | 264 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-07-18&to=2024-07-31&type=c)) 265 | 266 | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2024-07-18..2024-07-31&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2024-07-18..2024-07-31&type=Issues) 267 | 268 | ## 0.16.2 269 | 270 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.16.1...45ba6d8bcd312ecbcb30f8e8db92d72dd8756faa)) 271 | 272 | ### Bugs fixed 273 | 274 | - Restore SameFileError check when copying notebooks [#190](https://github.com/jupyterlite/jupyterlite-sphinx/pull/190) ([@melissawm](https://github.com/melissawm)) 275 | 276 | ### Contributors to this release 277 | 278 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-07-18&to=2024-07-18&type=c)) 279 | 280 | [@melissawm](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Amelissawm+updated%3A2024-07-18..2024-07-18&type=Issues) 281 | 282 | ## 0.16.1 283 | 284 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.16.0...a9c704ef2e1d602a87501bc3be284c842c0e8ff2)) 285 | 286 | ### Bugs fixed 287 | 288 | - Fix invalid schema for `jupyterlite_sphinx_strip` tag when `strip_tagged_cells` is `True` [#189](https://github.com/jupyterlite/jupyterlite-sphinx/pull/189) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 289 | 290 | ### Contributors to this release 291 | 292 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-07-18&to=2024-07-18&type=c)) 293 | 294 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-07-18..2024-07-18&type=Issues) 295 | 296 | ## 0.16.0 297 | 298 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.15.0...4074ee2049947f1a537e8b822b5fb5643d4a42b3)) 299 | 300 | ### Enhancements made 301 | 302 | - Strip tagged cells from `.ipynb` notebooks passed to the `NotebookLite` and `JupyterLite` directives [#180](https://github.com/jupyterlite/jupyterlite-sphinx/pull/180) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 303 | 304 | ### Bugs fixed 305 | 306 | - Resolve default-encoding errors on Windows [#187](https://github.com/jupyterlite/jupyterlite-sphinx/pull/187) ([@AA-Turner](https://github.com/AA-Turner)) 307 | - Hotfix: cell metadata to parse should be `jupyterlite_sphinx_strip` [#185](https://github.com/jupyterlite/jupyterlite-sphinx/pull/185) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 308 | - `TryExamples` directive: fix missing kernels in CircleCI deployments [#182](https://github.com/jupyterlite/jupyterlite-sphinx/pull/182) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 309 | - Issue 115 wrap fullscreen JupyterLite links [#181](https://github.com/jupyterlite/jupyterlite-sphinx/pull/181) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 310 | 311 | ### Maintenance and upkeep improvements 312 | 313 | - Use the latest `jupyterlite-xeus` to fix the docs build [#179](https://github.com/jupyterlite/jupyterlite-sphinx/pull/179) ([@jtpio](https://github.com/jtpio)) 314 | 315 | ### Documentation improvements 316 | 317 | - Issue 115 wrap fullscreen JupyterLite links [#181](https://github.com/jupyterlite/jupyterlite-sphinx/pull/181) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 318 | 319 | ### Contributors to this release 320 | 321 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-05-16&to=2024-07-18&type=c)) 322 | 323 | [@AA-Turner](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AAA-Turner+updated%3A2024-05-16..2024-07-18&type=Issues) | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-05-16..2024-07-18&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3ACarreau+updated%3A2024-05-16..2024-07-18&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2024-05-16..2024-07-18&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2024-05-16..2024-07-18&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2024-05-16..2024-07-18&type=Issues) 324 | 325 | ## 0.15.0 326 | 327 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.14.0...90a0b6327c1b2b3badaf925aa08e9a54083b4492)) 328 | 329 | ### Enhancements made 330 | 331 | - Pass additional configuration options to the `jupyter lite build` command [#169](https://github.com/jupyterlite/jupyterlite-sphinx/pull/169) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 332 | - Add the option to open JupyterLite window in new tab [#165](https://github.com/jupyterlite/jupyterlite-sphinx/pull/165) ([@melissawm](https://github.com/melissawm)) 333 | 334 | ### Maintenance and upkeep improvements 335 | 336 | ### Documentation improvements 337 | 338 | - Some general formatting fixes (punctuation, backticks, etc.) [#172](https://github.com/jupyterlite/jupyterlite-sphinx/pull/172) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 339 | 340 | ### Contributors to this release 341 | 342 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-04-30&to=2024-05-16&type=c)) 343 | 344 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-04-30..2024-05-16&type=Issues) | [@melissawm](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Amelissawm+updated%3A2024-04-30..2024-05-16&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2024-04-30..2024-05-16&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2024-04-30..2024-05-16&type=Issues) 345 | 346 | ## 0.14.0 347 | 348 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.13.1...cefbabe2d87e3572f6627c9a6f27923a6f8b8b82)) 349 | 350 | ### Enhancements made 351 | 352 | - Allow usage of global configuration values for `TryExamples` directive if provided by user [#161](https://github.com/jupyterlite/jupyterlite-sphinx/pull/161) ([@agriyakhetarpal](https://github.com/agriyakhetarpal)) 353 | - Minor refactor + typing info [#155](https://github.com/jupyterlite/jupyterlite-sphinx/pull/155) ([@Carreau](https://github.com/Carreau)) 354 | 355 | ### Maintenance and upkeep improvements 356 | 357 | - Update to `jupyterlite-core >=0.2,<0.4` [#160](https://github.com/jupyterlite/jupyterlite-sphinx/pull/160) ([@jtpio](https://github.com/jtpio)) 358 | - Update releaser workflows [#159](https://github.com/jupyterlite/jupyterlite-sphinx/pull/159) ([@jtpio](https://github.com/jtpio)) 359 | - Raise informative error message when building man on older sphinx [#158](https://github.com/jupyterlite/jupyterlite-sphinx/pull/158) ([@Carreau](https://github.com/Carreau)) 360 | - Add ruff pre-commit and reformat files with it [#156](https://github.com/jupyterlite/jupyterlite-sphinx/pull/156) ([@Carreau](https://github.com/Carreau)) 361 | - Minor refactor + typing info [#155](https://github.com/jupyterlite/jupyterlite-sphinx/pull/155) ([@Carreau](https://github.com/Carreau)) 362 | 363 | ### Contributors to this release 364 | 365 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-03-22&to=2024-04-30&type=c)) 366 | 367 | [@agriyakhetarpal](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aagriyakhetarpal+updated%3A2024-03-22..2024-04-30&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3ACarreau+updated%3A2024-03-22..2024-04-30&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2024-03-22..2024-04-30&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2024-03-22..2024-04-30&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2024-03-22..2024-04-30&type=Issues) 368 | 369 | ## 0.13.1 370 | 371 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.13.0...1e80dd142b70c883f7fcc5de4823fe7ca81bfb32)) 372 | 373 | ### Enhancements made 374 | 375 | - Give a pragmatic solution to suppressing noisy output [#153](https://github.com/jupyterlite/jupyterlite-sphinx/pull/153) ([@steppi](https://github.com/steppi)) 376 | 377 | ### Bugs fixed 378 | 379 | - Give a pragmatic solution to suppressing noisy output [#153](https://github.com/jupyterlite/jupyterlite-sphinx/pull/153) ([@steppi](https://github.com/steppi)) 380 | 381 | ### Contributors to this release 382 | 383 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-03-19&to=2024-03-22&type=c)) 384 | 385 | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2024-03-19..2024-03-22&type=Issues) 386 | 387 | ## 0.13.0 388 | 389 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.12.0...4dd4b3074d1a8e573c76b331a7ed20ff886bde2e)) 390 | 391 | ### Enhancements made 392 | 393 | - Set lower default verbosity and add config options. [#150](https://github.com/jupyterlite/jupyterlite-sphinx/pull/150) ([@Carreau](https://github.com/Carreau)) 394 | - ENH: Add metadata for parallel_read_safe = True [#148](https://github.com/jupyterlite/jupyterlite-sphinx/pull/148) ([@steppi](https://github.com/steppi)) 395 | 396 | ### Maintenance and upkeep improvements 397 | 398 | ### Contributors to this release 399 | 400 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2024-03-07&to=2024-03-19&type=c)) 401 | 402 | [@Carreau](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3ACarreau+updated%3A2024-03-07..2024-03-19&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apre-commit-ci+updated%3A2024-03-07..2024-03-19&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2024-03-07..2024-03-19&type=Issues) 403 | 404 | ## 0.12.0 405 | 406 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.11.0...0ef8be22f403e6ebaa46d1b6e1643ce7303a756c)) 407 | 408 | ### Enhancements made 409 | 410 | - Add option for initial warning cell in try examples directive [#143](https://github.com/jupyterlite/jupyterlite-sphinx/pull/143) ([@steppi](https://github.com/steppi)) 411 | - Hide buttons on smaller screens (mobile). [#141](https://github.com/jupyterlite/jupyterlite-sphinx/pull/141) ([@Carreau](https://github.com/Carreau)) 412 | - Suggestion: Add pre-commit to format js and css files. [#137](https://github.com/jupyterlite/jupyterlite-sphinx/pull/137) ([@Carreau](https://github.com/Carreau)) 413 | - Add a full screen "Open in tab" button [#135](https://github.com/jupyterlite/jupyterlite-sphinx/pull/135) ([@Carreau](https://github.com/Carreau)) 414 | - Add a loading spinner for TryExamples directive. [#133](https://github.com/jupyterlite/jupyterlite-sphinx/pull/133) ([@steppi](https://github.com/steppi)) 415 | - Misc parsing warnings. [#131](https://github.com/jupyterlite/jupyterlite-sphinx/pull/131) ([@Carreau](https://github.com/Carreau)) 416 | - Improve TryExamples customization [#129](https://github.com/jupyterlite/jupyterlite-sphinx/pull/129) ([@steppi](https://github.com/steppi)) 417 | - Add option to disable TryExamples without rebuilding docs [#118](https://github.com/jupyterlite/jupyterlite-sphinx/pull/118) ([@steppi](https://github.com/steppi)) 418 | 419 | ### Bugs fixed 420 | 421 | - Add processing of literal blocks in try examples directive [#134](https://github.com/jupyterlite/jupyterlite-sphinx/pull/134) ([@steppi](https://github.com/steppi)) 422 | 423 | ### Maintenance and upkeep improvements 424 | 425 | - Run pre-commit on all files in this repository. [#145](https://github.com/jupyterlite/jupyterlite-sphinx/pull/145) ([@Carreau](https://github.com/Carreau)) 426 | - Update publish workflow to use the PyPI trusted publisher [#123](https://github.com/jupyterlite/jupyterlite-sphinx/pull/123) ([@jtpio](https://github.com/jtpio)) 427 | 428 | ### Documentation improvements 429 | 430 | - Fix incorrect math processing [#139](https://github.com/jupyterlite/jupyterlite-sphinx/pull/139) ([@steppi](https://github.com/steppi)) 431 | - Update to `jupyterlite-xeus` [#138](https://github.com/jupyterlite/jupyterlite-sphinx/pull/138) ([@jtpio](https://github.com/jtpio)) 432 | - Improve TryExamples customization [#129](https://github.com/jupyterlite/jupyterlite-sphinx/pull/129) ([@steppi](https://github.com/steppi)) 433 | 434 | ### Contributors to this release 435 | 436 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-12-22&to=2024-03-07&type=c)) 437 | 438 | [@Carreau](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3ACarreau+updated%3A2023-12-22..2024-03-07&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2023-12-22..2024-03-07&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2023-12-22..2024-03-07&type=Issues) | [@mattip](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Amattip+updated%3A2023-12-22..2024-03-07&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2023-12-22..2024-03-07&type=Issues) 439 | 440 | ## 0.11.0 441 | 442 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.10.0...b1535878aef8233dfea9136fa8fa43c76a9b81e8)) 443 | 444 | ### Enhancements made 445 | 446 | - Add more configuration options to TryExamples directive and add documentation [#116](https://github.com/jupyterlite/jupyterlite-sphinx/pull/116) ([@steppi](https://github.com/steppi)) 447 | 448 | ### Contributors to this release 449 | 450 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-11-09&to=2023-12-22&type=c)) 451 | 452 | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2023-11-09..2023-12-22&type=Issues) 453 | 454 | ## 0.10.0 455 | 456 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.9.3...72393f736e38f36ff6f91a3ae878ba3f54646cad)) 457 | 458 | ### Enhancements made 459 | 460 | - Update to jupyterlite v0.2 [#113](https://github.com/jupyterlite/jupyterlite-sphinx/pull/113) ([@martinRenou](https://github.com/martinRenou)) 461 | - Add try_examples directive for adding interactivity to sphinx Examples sections [#111](https://github.com/jupyterlite/jupyterlite-sphinx/pull/111) ([@steppi](https://github.com/steppi)) 462 | 463 | ### Contributors to this release 464 | 465 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-09-13&to=2023-11-09&type=c)) 466 | 467 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2023-09-13..2023-11-09&type=Issues) | [@steppi](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Asteppi+updated%3A2023-09-13..2023-11-09&type=Issues) 468 | 469 | ## 0.9.3 470 | 471 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.9.2...551a58744536dca3d51e657b1d7ddcb5da102510)) 472 | 473 | ### Bugs fixed 474 | 475 | - Fix search params [#109](https://github.com/jupyterlite/jupyterlite-sphinx/pull/109) ([@brichet](https://github.com/brichet)) 476 | 477 | ### Contributors to this release 478 | 479 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-09-11&to=2023-09-13&type=c)) 480 | 481 | [@brichet](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Abrichet+updated%3A2023-09-11..2023-09-13&type=Issues) 482 | 483 | ## 0.9.2 484 | 485 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.9.1...d69ce1f546d58cb81cb7a976358712a440f842d1)) 486 | 487 | ### Enhancements made 488 | 489 | - Transfer search parameters from page URL to jupyterlite [#108](https://github.com/jupyterlite/jupyterlite-sphinx/pull/108) ([@brichet](https://github.com/brichet)) 490 | 491 | ### Contributors to this release 492 | 493 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-07-24&to=2023-09-11&type=c)) 494 | 495 | [@brichet](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Abrichet+updated%3A2023-07-24..2023-09-11&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2023-07-24..2023-09-11&type=Issues) 496 | 497 | ## 0.9.1 498 | 499 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.9.0...97014e26a62170f0d468918b3dc6c4ceeca28c26)) 500 | 501 | ### Bugs fixed 502 | 503 | - Remove Apps config auto-computation [#104](https://github.com/jupyterlite/jupyterlite-sphinx/pull/104) ([@martinRenou](https://github.com/martinRenou)) 504 | 505 | ### Contributors to this release 506 | 507 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-06-30&to=2023-07-24&type=c)) 508 | 509 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2023-06-30..2023-07-24&type=Issues) 510 | 511 | ## 0.9.0 512 | 513 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/v0.8.0...e22a7e66b17cb87c499487f28761f4fa32f8b061)) 514 | 515 | ### Enhancements made 516 | 517 | - Add Voici directive [#100](https://github.com/jupyterlite/jupyterlite-sphinx/pull/100) ([@martinRenou](https://github.com/martinRenou)) 518 | - Default lite directory to the docs directory [#99](https://github.com/jupyterlite/jupyterlite-sphinx/pull/99) ([@martinRenou](https://github.com/martinRenou)) 519 | - Use xeus-python in docs [#98](https://github.com/jupyterlite/jupyterlite-sphinx/pull/98) ([@martinRenou](https://github.com/martinRenou)) 520 | 521 | ### Maintenance and upkeep improvements 522 | 523 | - Update to `jupyterlite-core==0.1.0`, require Python 3.8 [#96](https://github.com/jupyterlite/jupyterlite-sphinx/pull/96) ([@jtpio](https://github.com/jtpio)) 524 | 525 | ### Documentation improvements 526 | 527 | - Add conda instructions and docs scripts [#97](https://github.com/jupyterlite/jupyterlite-sphinx/pull/97) ([@jtpio](https://github.com/jtpio)) 528 | - Update docs to mention adding other kernels [#94](https://github.com/jupyterlite/jupyterlite-sphinx/pull/94) ([@jtpio](https://github.com/jtpio)) 529 | - Add changelog to the docs, more markdown [#93](https://github.com/jupyterlite/jupyterlite-sphinx/pull/93) ([@jtpio](https://github.com/jtpio)) 530 | - Convert docs to Markdown [#92](https://github.com/jupyterlite/jupyterlite-sphinx/pull/92) ([@jtpio](https://github.com/jtpio)) 531 | - Add notice about `jupyterlite-core` in the changelog [#91](https://github.com/jupyterlite/jupyterlite-sphinx/pull/91) ([@jtpio](https://github.com/jtpio)) 532 | 533 | ### Contributors to this release 534 | 535 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-03-15&to=2023-06-30&type=c)) 536 | 537 | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2023-03-15..2023-06-30&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2023-03-15..2023-06-30&type=Issues) 538 | 539 | ## 0.8.0 540 | 541 | ([Full Changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/0.7.3...51e489426a2e0026bcbc7207c87764bcd936d78b)) 542 | 543 | ⚠️ `jupyterlite-sphinx` now depends on `jupyterlite-core` ⚠️ 544 | 545 | `jupyterlite-sphinx` now depends on the `jupyterlite-core` package instead of `jupyterlite`. 546 | 547 | The `jupyterlite-core` package provides the core functionality for building JupyterLite websites, the CLI and [extension points](https://jupyterlite.readthedocs.io/en/latest/howto/extensions/cli-addons.html). Currently it only includes a JavaScript kernel that runs in Web Worker. If you would like to include a Python kernel in your deployment you will have to add it to your dependencies, for example with: 548 | 549 | ``` 550 | python -m pip install jupyterlite-pyodide-kernel 551 | ``` 552 | 553 | Or next to the `jupyterlite-sphinx` dependency: 554 | 555 | ``` 556 | jupyterlite-sphinx 557 | jupyterlite-pyodide-kernel 558 | ``` 559 | 560 | ### Maintenance and upkeep improvements 561 | 562 | - Depend on `jupyterlite-core` [#89](https://github.com/jupyterlite/jupyterlite-sphinx/pull/89) ([@jtpio](https://github.com/jtpio)) 563 | - Add releaser workflows and changelog [#86](https://github.com/jupyterlite/jupyterlite-sphinx/pull/86) ([@jtpio](https://github.com/jtpio)) 564 | 565 | ### Contributors to this release 566 | 567 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2023-02-03&to=2023-03-15&type=c)) 568 | 569 | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2023-02-03..2023-03-15&type=Issues) 570 | 571 | ## 0.7.3 572 | 573 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/b8a4dec...6ccb288)) 574 | 575 | ### Documentation improvements 576 | 577 | - Update the `piplite_urls` configuration [#79](https://github.com/jupyterlite/jupyterlite-sphinx/pull/79) ([@jtpio](https://github.com/jtpio)) 578 | 579 | ### Other merged PRs 580 | 581 | - Unpin sphinx [#85](https://github.com/jupyterlite/jupyterlite-sphinx/pull/85) ([@lesteve](https://github.com/lesteve)) 582 | - add a github link to the documentation [#82](https://github.com/jupyterlite/jupyterlite-sphinx/pull/82) ([@12rambau](https://github.com/12rambau)) 583 | - DOC fix broken link to custom Jupyterlite configuration [#78](https://github.com/jupyterlite/jupyterlite-sphinx/pull/78) ([@lesteve](https://github.com/lesteve)) 584 | 585 | ### Contributors to this release 586 | 587 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-08-17&to=2023-02-03&type=c)) 588 | 589 | [@12rambau](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3A12rambau+updated%3A2022-08-17..2023-02-03&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2022-08-17..2023-02-03&type=Issues) | [@lesteve](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Alesteve+updated%3A2022-08-17..2023-02-03&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-08-17..2023-02-03&type=Issues) 590 | 591 | ## 0.7.2 592 | 593 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/3927797...b8a4dec)) 594 | 595 | ### Merged PRs 596 | 597 | - Finish reverting config names. [#74](https://github.com/jupyterlite/jupyterlite-sphinx/pull/74) ([@jasongrout](https://github.com/jasongrout)) 598 | - Restore jupyterlite_contents being optionally a string [#73](https://github.com/jupyterlite/jupyterlite-sphinx/pull/73) ([@jasongrout-db](https://github.com/jasongrout-db)) 599 | 600 | ### Contributors to this release 601 | 602 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-08-16&to=2022-08-17&type=c)) 603 | 604 | [@jasongrout](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajasongrout+updated%3A2022-08-16..2022-08-17&type=Issues) | [@jasongrout-db](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajasongrout-db+updated%3A2022-08-16..2022-08-17&type=Issues) 605 | 606 | ## 0.7.1 607 | 608 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/65d951e...3927797)) 609 | 610 | ### Merged PRs 611 | 612 | - Make the jupyterlite_contents glob recursive. [#72](https://github.com/jupyterlite/jupyterlite-sphinx/pull/72) ([@jasongrout](https://github.com/jasongrout)) 613 | 614 | ### Contributors to this release 615 | 616 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-07-26&to=2022-08-16&type=c)) 617 | 618 | [@jasongrout](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajasongrout+updated%3A2022-07-26..2022-08-16&type=Issues) 619 | 620 | ## 0.7.0 621 | 622 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/88c3dfd...65d951e)) 623 | 624 | ### Merged PRs 625 | 626 | - "Try It Live!" button [#67](https://github.com/jupyterlite/jupyterlite-sphinx/pull/67) ([@martinRenou](https://github.com/martinRenou)) 627 | - Make .ipynb source binding an opt-out [#66](https://github.com/jupyterlite/jupyterlite-sphinx/pull/66) ([@martinRenou](https://github.com/martinRenou)) 628 | - add globbing to content [#64](https://github.com/jupyterlite/jupyterlite-sphinx/pull/64) ([@amueller](https://github.com/amueller)) 629 | 630 | ### Contributors to this release 631 | 632 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-06-28&to=2022-07-26&type=c)) 633 | 634 | [@12rambau](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3A12rambau+updated%3A2022-06-28..2022-07-26&type=Issues) | [@amueller](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Aamueller+updated%3A2022-06-28..2022-07-26&type=Issues) | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2022-06-28..2022-07-26&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-06-28..2022-07-26&type=Issues) 635 | 636 | ## 0.6.0 637 | 638 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/c323ed1...88c3dfd)) 639 | 640 | ### Maintenance and upkeep improvements 641 | 642 | - Remove unneeded code [#60](https://github.com/jupyterlite/jupyterlite-sphinx/pull/60) ([@jtpio](https://github.com/jtpio)) 643 | 644 | ### Other merged PRs 645 | 646 | - Revert renaming the config properties [#62](https://github.com/jupyterlite/jupyterlite-sphinx/pull/62) ([@martinRenou](https://github.com/martinRenou)) 647 | 648 | ### Contributors to this release 649 | 650 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-06-24&to=2022-06-28&type=c)) 651 | 652 | [@jtpio](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajtpio+updated%3A2022-06-24..2022-06-28&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-06-24..2022-06-28&type=Issues) 653 | 654 | ## 0.5.0 655 | 656 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/0223c17...c323ed1)) 657 | 658 | ### Enhancements made 659 | 660 | - More consistent naming [#58](https://github.com/jupyterlite/jupyterlite-sphinx/pull/58) ([@martinRenou](https://github.com/martinRenou)) 661 | 662 | ### Other merged PRs 663 | 664 | - Update Pypi description to be the same as the readme [#59](https://github.com/jupyterlite/jupyterlite-sphinx/pull/59) ([@jasongrout](https://github.com/jasongrout)) 665 | - Add jupyterlite_contents config [#24](https://github.com/jupyterlite/jupyterlite-sphinx/pull/24) ([@martinRenou](https://github.com/martinRenou)) 666 | 667 | ### Contributors to this release 668 | 669 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-06-21&to=2022-06-24&type=c)) 670 | 671 | [@jasongrout](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajasongrout+updated%3A2022-06-21..2022-06-24&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-06-21..2022-06-24&type=Issues) 672 | 673 | ## 0.4.9 674 | 675 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/ebc5770...0223c17)) 676 | 677 | ### Merged PRs 678 | 679 | - Add jupyterlite_dir config option [#16](https://github.com/jupyterlite/jupyterlite-sphinx/pull/16) ([@martinRenou](https://github.com/martinRenou)) 680 | 681 | ### Contributors to this release 682 | 683 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-06-17&to=2022-06-21&type=c)) 684 | 685 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-06-17..2022-06-21&type=Issues) 686 | 687 | ## 0.4.8 688 | 689 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/7ee0c09...ebc5770)) 690 | 691 | ### Enhancements made 692 | 693 | - Run jupyter lite with subprocess.run to not suppress stdout [#55](https://github.com/jupyterlite/jupyterlite-sphinx/pull/55) ([@jasongrout](https://github.com/jasongrout)) 694 | - Adopt the path convention of other directives like literalinclude [#54](https://github.com/jupyterlite/jupyterlite-sphinx/pull/54) ([@jasongrout](https://github.com/jasongrout)) 695 | 696 | ### Bugs fixed 697 | 698 | - Fix federated extensions URLs [#56](https://github.com/jupyterlite/jupyterlite-sphinx/pull/56) ([@martinRenou](https://github.com/martinRenou)) 699 | - Allow whitespace in filenames [#52](https://github.com/jupyterlite/jupyterlite-sphinx/pull/52) ([@jasongrout](https://github.com/jasongrout)) 700 | 701 | ### Other merged PRs 702 | 703 | - Clarify how to preview locally, view a notebook in fullscreen, and link config docs [#45](https://github.com/jupyterlite/jupyterlite-sphinx/pull/45) ([@joelostblom](https://github.com/joelostblom)) 704 | 705 | ### Contributors to this release 706 | 707 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-04-05&to=2022-06-17&type=c)) 708 | 709 | [@jasongrout](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajasongrout+updated%3A2022-04-05..2022-06-17&type=Issues) | [@joelostblom](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Ajoelostblom+updated%3A2022-04-05..2022-06-17&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-04-05..2022-06-17&type=Issues) 710 | 711 | ## 0.4.7 712 | 713 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/78aa5b3...7ee0c09)) 714 | 715 | ### Merged PRs 716 | 717 | - Improve iframe URL [#43](https://github.com/jupyterlite/jupyterlite-sphinx/pull/43) ([@martinRenou](https://github.com/martinRenou)) 718 | 719 | ### Contributors to this release 720 | 721 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-03-29&to=2022-04-05&type=c)) 722 | 723 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-03-29..2022-04-05&type=Issues) 724 | 725 | ## 0.4.6 726 | 727 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/9d98a8e...78aa5b3)) 728 | 729 | ### Merged PRs 730 | 731 | - Replite code: Remove empty lines but not indentation [#42](https://github.com/jupyterlite/jupyterlite-sphinx/pull/42) ([@martinRenou](https://github.com/martinRenou)) 732 | 733 | ### Contributors to this release 734 | 735 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-03-28&to=2022-03-29&type=c)) 736 | 737 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-03-28..2022-03-29&type=Issues) 738 | 739 | ## 0.4.5 740 | 741 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/b0e17a9...9d98a8e)) 742 | 743 | ### Merged PRs 744 | 745 | - Cleanup query strings with urllib [#41](https://github.com/jupyterlite/jupyterlite-sphinx/pull/41) ([@martinRenou](https://github.com/martinRenou)) 746 | 747 | ### Contributors to this release 748 | 749 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-03-15&to=2022-03-28&type=c)) 750 | 751 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-03-15..2022-03-28&type=Issues) 752 | 753 | ## 0.4.4 754 | 755 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/506d16e...b0e17a9)) 756 | 757 | ### Merged PRs 758 | 759 | - Revert link fix [#35](https://github.com/jupyterlite/jupyterlite-sphinx/pull/35) ([@martinRenou](https://github.com/martinRenou)) 760 | 761 | ### Contributors to this release 762 | 763 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-03-14&to=2022-03-15&type=c)) 764 | 765 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-03-14..2022-03-15&type=Issues) 766 | 767 | ## 0.4.3 768 | 769 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/e9fc847...506d16e)) 770 | 771 | ### Merged PRs 772 | 773 | - Fix lite links [#32](https://github.com/jupyterlite/jupyterlite-sphinx/pull/32) ([@martinRenou](https://github.com/martinRenou)) 774 | 775 | ### Contributors to this release 776 | 777 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-03-14&to=2022-03-14&type=c)) 778 | 779 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-03-14..2022-03-14&type=Issues) 780 | 781 | ## 0.4.2 782 | 783 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/60aeb58...e9fc847)) 784 | 785 | ### Merged PRs 786 | 787 | - Replite: Fix multiline support [#30](https://github.com/jupyterlite/jupyterlite-sphinx/pull/30) ([@martinRenou](https://github.com/martinRenou)) 788 | - Bail early if there was an error [#29](https://github.com/jupyterlite/jupyterlite-sphinx/pull/29) ([@martinRenou](https://github.com/martinRenou)) 789 | 790 | ### Contributors to this release 791 | 792 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-03-09&to=2022-03-14&type=c)) 793 | 794 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-03-09..2022-03-14&type=Issues) 795 | 796 | ## 0.4.1 797 | 798 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/21e2dbe...60aeb58)) 799 | 800 | ### Merged PRs 801 | 802 | - Make sure we don't add the ipynb source prefix twice [#28](https://github.com/jupyterlite/jupyterlite-sphinx/pull/28) ([@martinRenou](https://github.com/martinRenou)) 803 | - Add LICENSE and update author [#26](https://github.com/jupyterlite/jupyterlite-sphinx/pull/26) ([@martinRenou](https://github.com/martinRenou)) 804 | - Fix sphinx pinning [#25](https://github.com/jupyterlite/jupyterlite-sphinx/pull/25) ([@martinRenou](https://github.com/martinRenou)) 805 | - Add Jupyterlite logo to the docs [#22](https://github.com/jupyterlite/jupyterlite-sphinx/pull/22) ([@martinRenou](https://github.com/martinRenou)) 806 | - Improve documentation frontpage [#20](https://github.com/jupyterlite/jupyterlite-sphinx/pull/20) ([@martinRenou](https://github.com/martinRenou)) 807 | 808 | ### Contributors to this release 809 | 810 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-03-02&to=2022-03-09&type=c)) 811 | 812 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-03-02..2022-03-09&type=Issues) 813 | 814 | ## 0.4.0 815 | 816 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/d781ca9...21e2dbe)) 817 | 818 | ### Merged PRs 819 | 820 | - Add jupyterlite directive and rework docs [#19](https://github.com/jupyterlite/jupyterlite-sphinx/pull/19) ([@martinRenou](https://github.com/martinRenou)) 821 | - Retrolite directive: Show tree if no Notebook specified [#18](https://github.com/jupyterlite/jupyterlite-sphinx/pull/18) ([@martinRenou](https://github.com/martinRenou)) 822 | - Remove docs build from the CI [#15](https://github.com/jupyterlite/jupyterlite-sphinx/pull/15) ([@martinRenou](https://github.com/martinRenou)) 823 | - Fix notebook search [#14](https://github.com/jupyterlite/jupyterlite-sphinx/pull/14) ([@martinRenou](https://github.com/martinRenou)) 824 | - Updating docs [#10](https://github.com/jupyterlite/jupyterlite-sphinx/pull/10) ([@martinRenou](https://github.com/martinRenou)) 825 | - Add link to the ipycanvas docs in the README [#9](https://github.com/jupyterlite/jupyterlite-sphinx/pull/9) ([@martinRenou](https://github.com/martinRenou)) 826 | - Update README [#8](https://github.com/jupyterlite/jupyterlite-sphinx/pull/8) ([@martinRenou](https://github.com/martinRenou)) 827 | - Add basic CI [#7](https://github.com/jupyterlite/jupyterlite-sphinx/pull/7) ([@martinRenou](https://github.com/martinRenou)) 828 | - Fix missing options_spec in Retrolite directive [#6](https://github.com/jupyterlite/jupyterlite-sphinx/pull/6) ([@martinRenou](https://github.com/martinRenou)) 829 | 830 | ### Contributors to this release 831 | 832 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-02-28&to=2022-03-02&type=c)) 833 | 834 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-02-28..2022-03-02&type=Issues) | [@psychemedia](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3Apsychemedia+updated%3A2022-02-28..2022-03-02&type=Issues) 835 | 836 | ## 0.3.0 837 | 838 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/999dffa...d781ca9)) 839 | 840 | ### Contributors to this release 841 | 842 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-02-25&to=2022-02-28&type=c)) 843 | 844 | ## 0.2.0 845 | 846 | ([full changelog](https://github.com/jupyterlite/jupyterlite-sphinx/compare/e5aacec...999dffa)) 847 | 848 | ### Contributors to this release 849 | 850 | ([GitHub contributors page for this release](https://github.com/jupyterlite/jupyterlite-sphinx/graphs/contributors?from=2022-01-04&to=2022-02-25&type=c)) 851 | 852 | [@martinRenou](https://github.com/search?q=repo%3Ajupyterlite%2Fjupyterlite-sphinx+involves%3AmartinRenou+updated%3A2022-01-04..2022-02-25&type=Issues) 853 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, JupyterLite Contributors 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 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * 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 | * 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include setup.py 4 | 5 | graft jupyterlite_sphinx 6 | graft docs 7 | 8 | # Patterns to exclude from any directory 9 | global-exclude *~ 10 | global-exclude *.pyc 11 | global-exclude *.pyo 12 | global-exclude .git 13 | global-exclude .ipynb_checkpoints 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JupyterLite Sphinx 2 | 3 | A Sphinx extension that provides utilities for embedding [JupyterLite](https://jupyterlite.readthedocs.io) in your documentation. 4 | 5 | ## Docs 6 | 7 | https://jupyterlite-sphinx.readthedocs.io 8 | 9 | ## Example 10 | 11 | Practical examples are in the [ipycanvas](https://ipycanvas.readthedocs.io) and [ipyleaflet](https://ipyleaflet.readthedocs.io) documentation. 12 | 13 | ## Installation: 14 | 15 | ```bash 16 | pip install jupyterlite-sphinx 17 | ``` 18 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing JupyterLite Sphinx 2 | 3 | ## Using `jupyter_releaser` 4 | 5 | The recommended way to make a release is to use [`jupyter_releaser`](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html). 6 | -------------------------------------------------------------------------------- /dev-environment.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlite-sphinx-dev 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - pip 6 | - jupyter_server 7 | - jupyterlab_server 8 | - jupyterlite-core >=0.3,<0.7 9 | - jupytext 10 | - pydata-sphinx-theme 11 | - micromamba=2.0.5 12 | - myst-parser 13 | - docutils 14 | - sphinx 15 | - black 16 | - voici 17 | - pip: 18 | - . 19 | - jupyterlite-xeus >=2.1.2 20 | -------------------------------------------------------------------------------- /docs/_static/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 25 | 29 | 33 | 37 | 41 | 45 | 49 | 53 | 57 | 61 | 65 | 69 | 70 | 95 | 100 | 101 | 103 | 104 | 106 | image/svg+xml 107 | 109 | 110 | 111 | 112 | 113 | 119 | 124 | 125 | 130 | 133 | 140 | 150 | 160 | 170 | 174 | 178 | 183 | 184 | 189 | 193 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /docs/_static/try_examples.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jupyter-light-primary: #f7dc1e; 3 | --jupyter-light-primary-muted: #fff221; 4 | } 5 | 6 | .try_examples_button { 7 | background-color: var(--jupyter-light-primary); 8 | border: none; 9 | padding: 5px 10px; 10 | border-radius: 15px; 11 | font-family: vibur; 12 | font-size: larger; 13 | box-shadow: 0 2px 5px rgba(108, 108, 108, 0.2); 14 | color: black; 15 | } 16 | 17 | .try_examples_button:hover { 18 | background-color: var(--jupyter-light-primary-muted); 19 | transform: scale(1.02); 20 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 21 | cursor: pointer; 22 | color: white; 23 | } 24 | 25 | .try_examples_button_container { 26 | display: flex; 27 | justify-content: flex-end; 28 | } 29 | 30 | .try_examples_outer_container, 31 | .try_examples_outer_iframe { 32 | flex-direction: column-reverse; 33 | display: flex; 34 | } 35 | 36 | /* override class + hidden, otherwise any attempt of custom css to 37 | * set the display mode would lead to the iframe/container always visible. 38 | * */ 39 | 40 | .try_examples_outer_container.hidden, 41 | .try_examples_outer_iframe.hidden { 42 | display: none; 43 | } 44 | 45 | /* customisation when the buttons containers have the blue-bottom class */ 46 | 47 | /* here we just show how to: 48 | * - change the color of the button 49 | * - change the color on hover. 50 | * 51 | * As we _used to have_ option to show the button above/below, and left/right 52 | * we show how to achieve the same with flex-direction 53 | */ 54 | 55 | .blue-bottom .try_examples_button_container { 56 | justify-content: flex-start; 57 | } 58 | 59 | .blue-bottom .try_examples_button { 60 | background-color: #00bcd4; 61 | color: white; 62 | } 63 | 64 | .blue-bottom button.try_examples_button:hover { 65 | background-color: #2196f3; 66 | } 67 | -------------------------------------------------------------------------------- /docs/bqplot.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# `bqplot` Interactive Demo\n", 8 | "\n", 9 | "Plotting in JupyterLite\n", 10 | "\n", 11 | "`bqplot` can be installed in this deployment (it provides the bqplot federated extension), but you will need to make your own deployment to have access to other interactive widgets libraries." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from bqplot import Figure, Axis, Lines, LinearScale, Bars\n", 21 | "\n", 22 | "import numpy as np\n", 23 | "\n", 24 | "np.random.seed(0)\n", 25 | "\n", 26 | "n = 100\n", 27 | "\n", 28 | "x = list(range(n))\n", 29 | "y = np.cumsum(np.random.randn(n)) + 100.\n", 30 | "\n", 31 | "sc_x = LinearScale()\n", 32 | "sc_y = LinearScale()\n", 33 | "\n", 34 | "lines = Lines(\n", 35 | " x=x, y=y,\n", 36 | " scales={'x': sc_x, 'y': sc_y}\n", 37 | ")\n", 38 | "ax_x = Axis(scale=sc_x, label='Index')\n", 39 | "ax_y = Axis(scale=sc_y, orientation='vertical', label='lines')\n", 40 | "\n", 41 | "Figure(marks=[lines], axes=[ax_x, ax_y], title='Lines')" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "lines.colors = ['green']" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "lines.fill = 'bottom'" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "lines.marker = 'circle'" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": null, 74 | "metadata": {}, 75 | "outputs": [], 76 | "source": [ 77 | "n = 100\n", 78 | "\n", 79 | "x = list(range(n))\n", 80 | "y = np.cumsum(np.random.randn(n))\n", 81 | "\n", 82 | "sc_x = LinearScale()\n", 83 | "sc_y = LinearScale()\n", 84 | "\n", 85 | "bars = Bars(\n", 86 | " x=x, y=y,\n", 87 | " scales={'x': sc_x, 'y': sc_y}\n", 88 | ")\n", 89 | "ax_x = Axis(scale=sc_x, label='Index')\n", 90 | "ax_y = Axis(scale=sc_y, orientation='vertical', label='bars')\n", 91 | "\n", 92 | "Figure(marks=[bars], axes=[ax_x, ax_y], title='Bars', animation_duration=1000)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "bars.y = np.cumsum(np.random.randn(n))" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "### Plots which use Nested Buffers" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "import numpy as np\n", 118 | "\n", 119 | "np.random.seed(0)\n", 120 | "y1 = np.cumsum(np.random.randn(150)) + 100.\n", 121 | "y2 = np.cumsum(np.random.randn(150)) + 100.\n", 122 | "y3 = np.cumsum(np.random.randn(150)) + 100.\n", 123 | "y4 = np.cumsum(np.random.randn(150)) + 100.\n", 124 | "\n", 125 | "sc_x = LinearScale()\n", 126 | "sc_y = LinearScale()\n", 127 | "\n", 128 | "lines = Lines(x=np.arange(len(y1)), y=[y1, y2, y3, y4],\n", 129 | " scales={'x': sc_x, 'y': sc_y})\n", 130 | "ax_x = Axis(scale=sc_x, label='Index')\n", 131 | "ax_y = Axis(scale=sc_y, orientation='vertical', label='lines')\n", 132 | "\n", 133 | "Figure(marks=[lines], axes=[ax_x, ax_y], title='Lines')" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [] 142 | } 143 | ], 144 | "metadata": { 145 | "language_info": { 146 | "name": "python" 147 | }, 148 | "orig_nbformat": 4 149 | }, 150 | "nbformat": 4, 151 | "nbformat_minor": 2 152 | } 153 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | ```{include} ../CHANGELOG.md 2 | ``` 3 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | extensions = [ 4 | "sphinx.ext.mathjax", 5 | "jupyterlite_sphinx", 6 | "myst_parser", 7 | ] 8 | 9 | html_theme = "pydata_sphinx_theme" 10 | html_logo = "_static/icon.svg" 11 | 12 | jupyterlite_contents = "./custom_contents" 13 | jupyterlite_bind_ipynb_suffix = False 14 | strip_tagged_cells = True 15 | 16 | # Enable this to use the provided sample overrides JSON file. 17 | # jupyterlite_overrides = "sample_overrides.json" 18 | 19 | # Enable this to unsilence JupyterLite and aid debugging 20 | # within our own documentation. 21 | # jupyterlite_silence = False 22 | 23 | master_doc = "index" 24 | 25 | # General information about the project. 26 | project = "JupyterLite Sphinx extension" 27 | 28 | # theme configuration 29 | html_theme_options = { 30 | "icon_links": [ 31 | { 32 | "name": "GitHub", 33 | "url": "https://github.com/jupyterlite/jupyterlite-sphinx", 34 | "icon": "fa-brands fa-github", 35 | } 36 | ] 37 | } 38 | 39 | html_static_path = ["_static"] 40 | html_css_files = ["try_examples.css"] 41 | 42 | suppress_warnings = ["myst.xref_missing"] 43 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | JupyterLite-sphinx can be configured in your `conf.py` file by setting some global Python variables: 4 | 5 | ## JupyterLite content 6 | 7 | You can embed custom content (notebooks and data files) in your JupyterLite build by providing the following config: 8 | 9 | ```python 10 | jupyterlite_contents = ["./path/to/my/notebooks/", "my_other_notebook.ipynb"] 11 | ``` 12 | 13 | `jupyterlite_contents` can be a string or a list of strings. Each string is expanded using the Python `glob.glob` function with its recursive option. See the [glob documentation](https://docs.python.org/3/library/glob.html#glob.glob) and the [wildcard pattern documentation](https://docs.python.org/3/library/fnmatch.html#fnmatch.fnmatch) for more details. This option supports both paths relative to the docs source directory and absolute ones. 14 | 15 | ## JupyterLite dir 16 | 17 | By default, jupyterlite-sphinx runs the `jupyter lite build` command in the docs directory, you can overwrite this behavior and ask jupyterlite to build in a given directory: 18 | 19 | ```python 20 | # Build in the current directory 21 | jupyterlite_dir = "/path/to/your/lite/dir" 22 | ``` 23 | 24 | ## Pre-installed packages 25 | 26 | In order to have Python packages pre-installed in the kernel environment, you can use [jupyterlite-xeus](https://jupyterlite-xeus.readthedocs.io), with the `xeus-python` kernel. 27 | 28 | You would need `jupyterlite-xeus` installed in your docs build environment. 29 | 30 | You can pre-install packages by adding an `environment.yml` file in the docs directory, with `xeus-python` defined as one of the dependencies. It will pre-build the environment when running the `jupyter lite build`. 31 | 32 | Furthermore, this automatically installs any labextension that it finds, for example, installing `ipyleaflet` will make `ipyleaflet` work without the need to manually install the `jupyter-leaflet` labextension. 33 | 34 | Say you want to install NumPy, Matplotlib and ipycanvas, it can be done by creating an `environment.yml` file with the following content: 35 | 36 | ```yaml 37 | name: xeus-python-kernel 38 | channels: 39 | - https://repo.mamba.pm/emscripten-forge 40 | - https://repo.mamba.pm/conda-forge 41 | dependencies: 42 | - numpy 43 | - matplotlib 44 | - ipycanvas 45 | ``` 46 | 47 | ## JupyterLite configuration 48 | 49 | You can provide [custom configuration files](https://jupyterlite.readthedocs.io/en/stable/howto/configure/config_files.html) 50 | to your JupyterLite deployment for build-time configuration and settings overrides. 51 | 52 | The build-time configuration can be used to change the default settings for JupyterLite, such 53 | as changing which assets are included, the locations of the assets, which plugins are enabled, 54 | and more. 55 | 56 | The runtime configuration can be used to change the settings of the JupyterLite deployment 57 | after it has been built, such as changing the theme, the default kernel, the default language, 58 | and more. 59 | 60 | 62 | 63 | ```python 64 | # Build-time configuration for JupyterLite 65 | jupyterlite_config = "jupyter_lite_config.json" 66 | # Override plugins and extension settings 67 | jupyterlite_overrides = "overrides.json" 68 | ``` 69 | 70 | # Setting default button texts for the `JupyterLite`, `NotebookLite`, `Replite`, and `Voici` directives 71 | 72 | When using the `:new_tab:` option in the `JupyterLite`, `NotebookLite`, `Replite`, and `Voici` directives, 73 | the button text defaults to "Open as a notebook", "Open in a REPL", and "Open with Voici", respectively. 74 | 75 | You can optionally the button text on a global level for these directives by setting the 76 | following values in your `conf.py` file: 77 | 78 | ```python 79 | jupyterlite_new_tab_button_text = "My custom JupyterLite button text" 80 | notebooklite_new_tab_button_text = "My custom NotebookLite button text" 81 | replite_new_tab_button_text = "My custom Replite button text" 82 | voici_new_tab_button_text = "My custom Voici button text" 83 | ``` 84 | 85 | You can override this text on a per-directive basis by passing the `:new_tab_button_text:` option 86 | to the directive. Note that this is compatible only if `:new_tab:` is also provided. 87 | 88 | ## REPL code auto-execution with the `Replite` directive 89 | 90 | It is possible to control whether code snippets in REPL environments automatically executes when loaded. 91 | For this, you may set `replite_auto_execute = False` globally in `conf.py` with (defaults to `True` if 92 | not present), or override it on a per-directive basis with `:execute: True` or `:execute: False`. 93 | 94 | ## Strip particular tagged cells from IPython Notebooks 95 | 96 | When using the `NotebookLite`, `JupyterLite`, or `Voici` directives with a notebook passed to them, you can 97 | strip particular tagged cells from the notebook before rendering it in the JupyterLite console. 98 | 99 | This behaviour can be enabled by setting the following config: 100 | 101 | ```python 102 | strip_tagged_cells = True 103 | ``` 104 | 105 | and then by tagging the cells you want to strip with the `jupyterlite_sphinx_strip` tag in the JSON metadata 106 | of the cell, like this: 107 | 108 | ```json 109 | "metadata": { 110 | "tags": [ 111 | "jupyterlite_sphinx_strip" 112 | ] 113 | } 114 | ``` 115 | 116 | This is useful when you want to remove some cells from the rendered notebook in the JupyterLite 117 | console, for example, cells that are used for adding reST-based directives or other 118 | Sphinx-specific content. It can be used to remove either code cells or Markdown cells. 119 | 120 | For example, you can use this feature to remove the `toctree` directive from the rendered notebook 121 | in the JupyterLite console: 122 | 123 | ```json 124 | { 125 | "cells": [ 126 | { 127 | "cell_type": "markdown", 128 | "metadata": { 129 | "tags": [ 130 | "jupyterlite_sphinx_strip" 131 | ] 132 | }, 133 | "source": [ 134 | "# Table of Contents\n", 135 | "\n", 136 | "```{toctree}\n", 137 | ":maxdepth: 2\n", 138 | "\n", 139 | "directives/jupyterlite\n", 140 | "directives/notebooklite\n", 141 | "directives/replite\n", 142 | "directives/voici\n", 143 | "directives/try_examples\n", 144 | "full\n", 145 | "changelog\n", 146 | "```" 147 | ] 148 | } 149 | ] 150 | } 151 | ``` 152 | 153 | where the cell with the `toctree` directive will be removed from the rendered notebook in 154 | the JupyterLite console. 155 | 156 | In the case of a MyST notebook, you can use the following syntax to tag the cells: 157 | 158 | ````markdown 159 | 160 | +++ {"tags": ["jupyterlite_sphinx_strip"]} 161 | 162 | # Heading 1 163 | 164 | This is a Markdown cell that will be stripped from the rendered notebook in the 165 | JupyterLite console. 166 | 167 | +++ 168 | 169 | ```{code-cell} ipython3 170 | :tags: [jupyterlite_sphinx_strip] 171 | 172 | # This is a code cell that will be stripped from the rendered notebook in the 173 | # JupyterLite console. 174 | def foo(): 175 | print(3) 176 | ``` 177 | 178 | ```{code-cell} ipython3 179 | # This cell will not be stripped 180 | def bar(): 181 | print(4) 182 | ``` 183 | ```` 184 | 185 | The Markdown cells are not wrapped, and hence the `+++` and `+++` markers are used to 186 | indicate where the cells start and end. For more details around writing and customising 187 | MyST-flavoured notebooks, please refer to the 188 | [MyST Markdown overview](https://jupyterbook.org/en/stable/content/myst.html). 189 | 190 | Note that this feature is only available for the `NotebookLite`, `JupyterLite`, and the 191 | `Voici` directives and works with the `.md` (MyST) or `.ipynb` files passed to them. It 192 | is not implemented for the `TryExamples` directive. 193 | 194 | ## Disable the `.ipynb` docs source binding 195 | 196 | By default, jupyterlite-sphinx binds the `.ipynb` source suffix so that it renders Notebooks included in the doctree with JupyterLite. 197 | This is known to bring warnings with plugins like `sphinx-gallery`, or to conflict with `nbsphinx`. 198 | 199 | You can disable this behavior by setting the following config: 200 | 201 | ```python 202 | jupyterlite_bind_ipynb_suffix = False 203 | ``` 204 | 205 | ### Suppressing JupyterLite logging 206 | 207 | `jupyterlite` can produce large amounts of output to the terminal when docs are building. 208 | By default, this output is silenced, but will still be printed if the invocation of 209 | `jupyter lite build` fails. To unsilence this output, set 210 | 211 | ```python 212 | jupyterlite_silence = False 213 | ``` 214 | 215 | in your Sphinx `conf.py`. 216 | 217 | ## Additional CLI arguments for `jupyter lite build` 218 | 219 | Additional arguments can be passed to the `jupyter lite build` command using the configuration 220 | option `jupyterlite_build_command_options` in `conf.py`. The following example shows how to 221 | specify an alternative location for the `xeus` kernel's `environment.yml` file as discussed 222 | [here](https://github.com/jupyterlite/xeus#usage). 223 | 224 | ```python 225 | jupyterlite_build_command_options = { 226 | "XeusAddon.environment_file": "jupyterlite_environment.yml", 227 | } 228 | ``` 229 | 230 | This causes the additional option `--XeusAddon.environment_file=jupyterlite_environment.yml` 231 | to be passed to `jupyter lite build` internally within `jupyterlite-sphinx`. Note that one 232 | does not include the leading dashes, `--`, in the keys. 233 | 234 | The options `--contents`, `--output-dir`, and `--lite-dir` cannot be passed to `jupyter lite build` in this way. 235 | These can instead be set with 236 | the [`jupyterlite_contents`](#jupyterlite-content) and the[`jupyterlite_dir`](#jupyterlite-dir) configuration 237 | options described above. 238 | 239 | This is an advanced feature and users are responsible for providing sensible command line options. 240 | The standard precedence rules between `jupyter lite build` CLI options and other means of configuration apply. 241 | See the [jupyter lite CLI](https://jupyterlite.readthedocs.io/en/latest/reference/cli.html) documentation 242 | for more info. 243 | -------------------------------------------------------------------------------- /docs/custom_contents/data.csv: -------------------------------------------------------------------------------- 1 | A,B,C,D 2 | 1,2,3,4 3 | 7,8,9,10 4 | 32,45,90,54 5 | -------------------------------------------------------------------------------- /docs/custom_contents/read_csv.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "kernelspec": { 4 | "name": "python", 5 | "display_name": "Pyolite", 6 | "language": "python" 7 | }, 8 | "language_info": { 9 | "codemirror_mode": { 10 | "name": "python", 11 | "version": 3 12 | }, 13 | "file_extension": ".py", 14 | "mimetype": "text/x-python", 15 | "name": "python", 16 | "nbconvert_exporter": "python", 17 | "pygments_lexer": "ipython3", 18 | "version": "3.8" 19 | } 20 | }, 21 | "nbformat_minor": 5, 22 | "nbformat": 4, 23 | "cells": [ 24 | { 25 | "cell_type": "code", 26 | "source": "import pandas as pd", 27 | "metadata": { 28 | "trusted": true 29 | }, 30 | "execution_count": null, 31 | "outputs": [], 32 | "id": "09909708-6e45-4f96-92bc-af61031b2311" 33 | }, 34 | { 35 | "cell_type": "code", 36 | "source": "pd.read_csv('data.csv')", 37 | "metadata": { 38 | "trusted": true 39 | }, 40 | "execution_count": null, 41 | "outputs": [], 42 | "id": "17ad47b4-76dd-46f6-b4da-eb3ac6f96d48" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /docs/directives/jupyterlite.md: -------------------------------------------------------------------------------- 1 | # JupyterLite directive 2 | 3 | `jupyterlite-sphinx` provides a `jupyterlite` directive that allows you to embed JupyterLab in your docs. 4 | 5 | ```rst 6 | .. jupyterlite:: 7 | :width: 100% 8 | :height: 600px 9 | :prompt: Try JupyterLite! 10 | :prompt_color: #00aa42 11 | ``` 12 | 13 | ```{eval-rst} 14 | .. jupyterlite:: 15 | :width: 100% 16 | :height: 600px 17 | :prompt: Try JupyterLite! 18 | :prompt_color: #00aa42 19 | ``` 20 | 21 | You can also pass a Notebook file to open automatically: 22 | 23 | ```rst 24 | .. jupyterlite:: my_notebook.ipynb 25 | :width: 100% 26 | :height: 600px 27 | :prompt: Try JupyterLite! 28 | :prompt_color: #00aa42 29 | ``` 30 | 31 | ```{eval-rst} 32 | .. jupyterlite:: my_notebook.ipynb 33 | :width: 100% 34 | :height: 600px 35 | :prompt: Try JupyterLite! 36 | :prompt_color: #00aa42 37 | ``` 38 | 39 | The notebook can also be a MyST-flavoured Markdown file that will be converted to a Jupyter Notebook before being opened. 40 | 41 | ```rst 42 | .. jupyterlite:: my_markdown_notebook.md 43 | :width: 100% 44 | :height: 600px 45 | :prompt: Try JupyterLite! 46 | :prompt_color: #00aa42 47 | ``` 48 | 49 | ```{eval-rst} 50 | .. jupyterlite:: my_markdown_notebook.md 51 | :width: 100% 52 | :height: 600px 53 | :prompt: Try JupyterLite! 54 | :prompt_color: #00aa42 55 | ``` 56 | 57 | If you use the `:new_tab:` option in the directive, the Notebook will be opened in a new browser tab. 58 | The tab will render the full-fledged Lab interface, which is more complete and showcases all features 59 | of JupyterLite. 60 | 61 | ```rst 62 | .. jupyterlite:: my_notebook.ipynb 63 | :new_tab: True 64 | ``` 65 | 66 | ```{eval-rst} 67 | .. jupyterlite:: my_notebook.ipynb 68 | :new_tab: True 69 | ``` 70 | 71 | When using this option, it is also possible to customise the button text, overriding the 72 | global value using an additional `:new_tab_button_text:` parameter: 73 | 74 | ```rst 75 | .. jupyterlite:: my_notebook.ipynb 76 | :new_tab: True 77 | :new_tab_button_text: My custom JupyterLite button text 78 | ``` 79 | 80 | ```{eval-rst} 81 | .. jupyterlite:: my_notebook.ipynb 82 | :new_tab: True 83 | :new_tab_button_text: My custom JupyterLite button text 84 | ``` 85 | 86 | ## Search parameters 87 | 88 | The directive `search_params` allows to transfer some search parameters from the documentation URL to the Jupyterlite URL.\ 89 | Jupyterlite will then be able to fetch these parameters from its own URL.\ 90 | For example `:search_params: ["param1", "param2"]` will transfer the parameters *param1* and *param2*. 91 | Use a boolean value to transfer all or none of the parameters (default to none): `:search_params: True` 92 | -------------------------------------------------------------------------------- /docs/directives/my_markdown_notebook.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | text_representation: 4 | extension: .md 5 | format_name: myst 6 | format_version: 0.13 7 | jupytext_version: 1.16.4 8 | kernelspec: 9 | display_name: Python 3 (ipykernel) 10 | language: python 11 | name: python3 12 | --- 13 | 14 | # This is a MyST Markdown-flavoured notebook 15 | 16 | ```{code-cell} ipython3 17 | def foo(): 18 | print(3) 19 | ``` 20 | 21 | ```{code-cell} ipython3 22 | foo() 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/directives/my_notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "62360ff0", 6 | "metadata": {}, 7 | "source": [ 8 | "# This is a Jupyter Notebook" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "id": "a5077823", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "def foo():\n", 19 | " print(3)" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "id": "84361143", 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "name": "stdout", 30 | "output_type": "stream", 31 | "text": [ 32 | "3\n" 33 | ] 34 | } 35 | ], 36 | "source": [ 37 | "foo()" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "id": "b9f8bed0", 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [] 47 | } 48 | ], 49 | "metadata": { 50 | "kernelspec": { 51 | "display_name": "Python 3 (ipykernel)", 52 | "language": "python", 53 | "name": "python3" 54 | }, 55 | "language_info": { 56 | "codemirror_mode": { 57 | "name": "ipython", 58 | "version": 3 59 | }, 60 | "file_extension": ".py", 61 | "mimetype": "text/x-python", 62 | "name": "python", 63 | "nbconvert_exporter": "python", 64 | "pygments_lexer": "ipython3", 65 | "version": "3.10.0" 66 | } 67 | }, 68 | "nbformat": 4, 69 | "nbformat_minor": 5 70 | } 71 | -------------------------------------------------------------------------------- /docs/directives/notebooklite.md: -------------------------------------------------------------------------------- 1 | # NotebookLite directive 2 | 3 | `jupyterlite-sphinx` provides a `notebooklite` directive that allows you to embed the classic Notebook UI in your docs. 4 | 5 | ```rst 6 | .. notebooklite:: 7 | :width: 100% 8 | :height: 600px 9 | :prompt: Try classic Notebook! 10 | ``` 11 | 12 | ```{eval-rst} 13 | .. notebooklite:: 14 | :width: 100% 15 | :height: 600px 16 | :prompt: Try classic Notebook! 17 | ``` 18 | 19 | You can provide a notebook (either Jupyter-based or MyST-Markdown flavoured) to open: 20 | 21 | 1. Jupyter Notebook 22 | 23 | ```rst 24 | .. notebooklite:: my_notebook.ipynb 25 | :width: 100% 26 | :height: 600px 27 | :prompt: Try classic Notebook! 28 | ``` 29 | 30 | ```{eval-rst} 31 | .. notebooklite:: my_notebook.ipynb 32 | :width: 100% 33 | :height: 600px 34 | :prompt: Try classic Notebook! 35 | ``` 36 | 37 | 2. MyST Markdown 38 | 39 | ```rst 40 | .. notebooklite:: my_markdown_notebook.md 41 | :width: 100% 42 | :height: 600px 43 | :prompt: Try classic Notebook! 44 | ``` 45 | 46 | ```{eval-rst} 47 | .. notebooklite:: my_markdown_notebook.md 48 | :width: 100% 49 | :height: 600px 50 | :prompt: Try classic Notebook! 51 | ``` 52 | 53 | If you use the `:new_tab:` option in the directive, the Notebook will be opened in a new browser tab. 54 | The tab will render the classic Notebook UI, which is more minimal and does not showcase the entire 55 | Lab interface. 56 | 57 | ```rst 58 | .. notebooklite:: my_notebook.ipynb 59 | :new_tab: True 60 | ``` 61 | 62 | ```{eval-rst} 63 | .. notebooklite:: my_notebook.ipynb 64 | :new_tab: True 65 | ``` 66 | 67 | When using this option, it is also possible to customise the button text, overriding the 68 | global value using an additional `:new_tab_button_text:` parameter: 69 | 70 | ```rst 71 | .. notebooklite:: my_notebook.ipynb 72 | :new_tab: True 73 | :new_tab_button_text: My custom NotebookLite button text 74 | ``` 75 | 76 | ```{eval-rst} 77 | .. notebooklite:: my_notebook.ipynb 78 | :new_tab: True 79 | :new_tab_button_text: My custom NotebookLite button text 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/directives/replite.md: -------------------------------------------------------------------------------- 1 | # Replite directive 2 | 3 | `jupyterlite-sphinx` provides a `replite` directive that allows you to embed a replite console in your docs. 4 | This directive takes extra options which are the same options as the `replite` package, see for reference. 5 | 6 | ```rst 7 | .. replite:: 8 | :kernel: xeus-python 9 | :height: 600px 10 | :prompt: Try Replite! 11 | :prompt_color: #dc3545 12 | 13 | import matplotlib.pyplot as plt 14 | import numpy as np 15 | 16 | x = np.linspace(0, 2 * np.pi, 200) 17 | y = np.sin(x) 18 | 19 | fig, ax = plt.subplots() 20 | ax.plot(x, y) 21 | plt.show() 22 | ``` 23 | 24 | ```{eval-rst} 25 | .. replite:: 26 | :kernel: xeus-python 27 | :height: 600px 28 | :prompt: Try Replite! 29 | :prompt_color: #dc3545 30 | 31 | import matplotlib.pyplot as plt 32 | import numpy as np 33 | 34 | x = np.linspace(0, 2 * np.pi, 200) 35 | y = np.sin(x) 36 | 37 | fig, ax = plt.subplots() 38 | ax.plot(x, y) 39 | plt.show() 40 | ``` 41 | 42 | If you use the `:new_tab:` option in the directive, the Replite console will be opened in a new browser tab 43 | with the code pre-filled. 44 | 45 | ```rst 46 | .. replite:: 47 | :kernel: xeus-python 48 | :new_tab: True 49 | 50 | import matplotlib.pyplot as plt 51 | import numpy as np 52 | 53 | x = np.linspace(0, 2 * np.pi, 200) 54 | y = np.sin(x) 55 | 56 | fig, ax = plt.subplots() 57 | ax.plot(x, y) 58 | plt.show() 59 | ``` 60 | 61 | ```{eval-rst} 62 | .. replite:: 63 | :kernel: xeus-python 64 | :new_tab: True 65 | 66 | import matplotlib.pyplot as plt 67 | import numpy as np 68 | 69 | x = np.linspace(0, 2 * np.pi, 200) 70 | y = np.sin(x) 71 | 72 | fig, ax = plt.subplots() 73 | ax.plot(x, y) 74 | plt.show() 75 | ``` 76 | 77 | When using this option, it is also possible to customise the button text, overriding the 78 | global value using an additional `:new_tab_button_text:` parameter: 79 | 80 | ```rst 81 | .. replite:: 82 | :kernel: xeus-python 83 | :new_tab: True 84 | :new_tab_button_text: My custom Replite button text 85 | 86 | import matplotlib.pyplot as plt 87 | import numpy as np 88 | 89 | x = np.linspace(0, 2 * np.pi, 200) 90 | y = np.sin(x) 91 | 92 | fig, ax = plt.subplots() 93 | ax.plot(x, y) 94 | plt.show() 95 | ``` 96 | 97 | ```{eval-rst} 98 | .. replite:: 99 | :kernel: xeus-python 100 | :new_tab: True 101 | :new_tab_button_text: My custom Replite button text 102 | 103 | import matplotlib.pyplot as plt 104 | import numpy as np 105 | 106 | x = np.linspace(0, 2 * np.pi, 200) 107 | y = np.sin(x) 108 | 109 | fig, ax = plt.subplots() 110 | ax.plot(x, y) 111 | plt.show() 112 | ``` 113 | 114 | ````{tip} 115 | 116 | With `jupyterlite-core` **versions 0.5.0 and later**, it is also possible to disable the execution of 117 | the code in the Replite console by setting the `:execute:` option to `False`. This option defaults to `True`, 118 | and setting it has no effect in versions prior to 0.5.0. 119 | 120 | The behaviour can also be [configured globally](../configuration.md#replite-auto-execution-with-the-replite-directive) 121 | and then overridden in individual directives as needed. 122 | 123 | ```rst 124 | .. replite:: 125 | :kernel: xeus-python 126 | :new_tab: True # False works too 127 | :new_tab_button_text: Open REPL with the code execution disabled 128 | :execute: False 129 | 130 | import matplotlib.pyplot as plt 131 | import numpy as np 132 | 133 | x = np.linspace(0, 2 * np.pi, 200) 134 | y = np.sin(x) 135 | 136 | fig, ax = plt.subplots() 137 | ax.plot(x, y) 138 | plt.show() 139 | ``` 140 | 141 | ```{eval-rst} 142 | .. replite:: 143 | :kernel: xeus-python 144 | :new_tab: True # False works too 145 | :new_tab_button_text: Open REPL with the code execution disabled 146 | :execute: False 147 | 148 | import matplotlib.pyplot as plt 149 | import numpy as np 150 | 151 | x = np.linspace(0, 2 * np.pi, 200) 152 | y = np.sin(x) 153 | 154 | fig, ax = plt.subplots() 155 | ax.plot(x, y) 156 | plt.show() 157 | ``` 158 | 159 | ```` 160 | -------------------------------------------------------------------------------- /docs/directives/try_examples.md: -------------------------------------------------------------------------------- 1 | # TryExamples directive 2 | 3 | `jupyterlite-sphinx` provides the experimental `try_examples` directive which allows 4 | docstring examples sections written in [doctest format](https://docs.python.org/3/library/doctest.html) to be swapped with an embedded classic Notebook at the push of a button. 5 | 6 | Below is an example of the directive in use. The button has been styled with custom 7 | css as explained in the configuration section below. Without custom css, the button will 8 | be plain and unadorned. 9 | 10 | Note that as starting JupyterLite can download a significant amount of data, and 11 | that the Jupyter interface is not optimized for mobile, the buttons will be 12 | hidden on mobile by default (screen width 480px or smaller). This can be 13 | changed by overwriting with custom CSS. 14 | 15 | 16 | ```rst 17 | Examples 18 | -------- 19 | .. try_examples:: 20 | 21 | Doctest examples sections are parsed and converted to notebooks. Blocks of text 22 | like this become markdown cells. Codeblocks begin with ``>>>``. Contiguous blocks 23 | of code are combined into a single code cell. 24 | 25 | >>> x = 2 26 | >>> y = 2 27 | >>> x + y 28 | 4 29 | 30 | ``...`` is used to continue multiline statements. 31 | 32 | >>> def f(x, y): 33 | ... return x + y 34 | >>> f(2, 2) 35 | 4 36 | 37 | Inline LaTeX like :math:`x + y = 4` is converted, as is block LaTeX like 38 | 39 | .. math:: 40 | 41 | \int_{x=-\infty}^{\infty}e^{-x^2}\mathrm{d}x = \sqrt{\pi} 42 | 43 | If you are displaying `math output `_ 44 | with sphinx. Sphinx links such as the one in the previous sentence are also converted to 45 | markdown format. 46 | ``` 47 | 48 | and here is how this looks and works when rendered. 49 | 50 | 51 | ```{eval-rst} 52 | Examples 53 | -------- 54 | .. try_examples:: 55 | 56 | Doctest examples sections are parsed and converted to notebooks. Blocks of text 57 | like this become markdown cells. Codeblocks begin with `>>>`. Contiguous blocks 58 | of code are combined into a single code cell. 59 | 60 | >>> x = 2 61 | >>> y = 2 62 | >>> x + y 63 | 4 64 | 65 | `...` is used to continue multiline statements. 66 | 67 | >>> def f(x, y): 68 | ... return x + y 69 | >>> f(2, 2) 70 | 4 71 | 72 | Inline LaTeX like :math:`x + y = 4` is converted, as is block LaTeX like 73 | 74 | .. math:: 75 | 76 | \int_{-\infty}^{\infty}e^{-x^2}\mathrm{d}x = \sqrt{\pi} 77 | 78 | If you are displaying `math output `_ 79 | with sphinx. Sphinx links such as the one in the previous sentence are also converted to 80 | markdown format. 81 | ``` 82 | 83 | By default, the height of the embedded notebook's iframe container is calculated to match the height of the rendered doctest examples so that it takes up the same amount of space on the 84 | page. 85 | 86 | ## Configuration 87 | 88 | The position and style of the button can be customized to match your documentation's 89 | design by adding custom css as explained in Sphinx's documentation [here](https://docs.readthedocs.io/en/stable/guides/adding-custom-css.html#how-to-add-custom-css-or-javascript-to-sphinx-documentation). The buttons have class `try_examples_button`. The buttons are placed within 90 | containers with class `try_examples_button_container`, which can be selected to adjust the 91 | positioning of the button. The css for the example above is 92 | 93 | ```css 94 | 95 | .try_examples_button { 96 | color: white; 97 | background-color: #0054a6; 98 | border: none; 99 | padding: 5px 10px; 100 | border-radius: 10px; 101 | margin-bottom: 5px; 102 | box-shadow: 0 2px 5px rgba(108,108,108,0.2); 103 | } 104 | 105 | .try_examples_button:hover { 106 | background-color: #0066cc; 107 | transform: scale(1.02); 108 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 109 | cursor: pointer; 110 | } 111 | 112 | .try_examples_button_container { 113 | display: flex; 114 | justify-content: flex-end; 115 | } 116 | ``` 117 | 118 | 119 | The `try_examples` directive has options 120 | * `:height:` To set a specific value for the height of the [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) containing the embedded notebook. 121 | * `:button_text` To customize the text of the button that replaces the rendered examples with an embedded notebook. 122 | * `:theme:` This works the same as for the other JupyterLite-Sphinx directives. 123 | * `:example_class:` An html class to attach to the outer container for the rendered 124 | examples content and embedded notebook. This can be used in a custom css file to allow 125 | for more precise customization, eg. different button styles across different examples. 126 | * `:warning_text:` Prepend a markdown cell to the notebook containing this text, styled to make it clear this is intended as a warning. 127 | 128 | Here's an example with some options set 129 | 130 | ```rst 131 | .. try_examples:: 132 | :button_text: Try it in your browser! 133 | :height: 400px 134 | :example_class: blue-bottom 135 | :warning_text: Interactive examples are experimental and may not always work as expected. 136 | 137 | The button text has changed and the height now exceeds the size of the content. 138 | 139 | >>> x = 2 140 | >>> y = 2 141 | >>> x + y 142 | 4 143 | 144 | We've also added the ``blue-bottom`` class, the button should appear as blue, 145 | below the examples, and on the left side of the screen. 146 | 147 | See `try_examples.css `_ 148 | to see how we achieved this via custom css. 149 | ``` 150 | 151 | and here is the result 152 | 153 | ```{eval-rst} 154 | .. try_examples:: 155 | :button_text: Try it in your browser! 156 | :height: 400px 157 | :example_class: blue-bottom 158 | :warning_text: Interactive examples are experimental and may not always work as expected. 159 | 160 | The button text has changed and the height now exceeds the size of the content. 161 | 162 | >>> x = 2 163 | >>> y = 2 164 | >>> x + y 165 | 4 166 | 167 | We've also added the ``blue-bottom`` class, the button should appear as blue, 168 | below the examples, and on the left side of the screen. 169 | 170 | See `try_examples.css `_ 171 | to see how we achieved this via custom css. 172 | ``` 173 | 174 | 175 | ### Global Configuration 176 | 177 | For projects with a large number of existing doctest examples, it would be tedious to add 178 | the `try_examples` directive manually to each docstring example. If you are using [sphinx.ext.autodoc](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html) with either [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) or [sphinx.ext.napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html), you 179 | can set the option. 180 | 181 | ```python 182 | global_enable_try_examples = True 183 | ``` 184 | 185 | in your sphinx `conf.py` in order to automatically insert the `try_examples` directive 186 | in examples sections during the `"autodoc-process-docstring"` event. This works by 187 | identifying section headings. An examples section includes all of the content beneath 188 | an examples heading, and up to either the next heading or the end of the docstring if 189 | there are no further headings. One of `numpydoc` or `sphinx.ext.napoleon` is required 190 | because these map the section headers to a standardized format. 191 | 192 | If an examples section already contains a `try_examples` directive, no additional 193 | directives will be inserted, allowing for specific cases to be separately configured 194 | if needed. Adding the comment 195 | 196 | 197 | ```rst 198 | .. disable_try_examples 199 | ``` 200 | 201 | as the first non-empty line under 202 | the section header for an examples section will prevent a directive from being inserted, 203 | allowing for specification of examples sections which should not be made interactive. 204 | 205 | 206 | The button text, theme, and warning text can be set globally with the config variables 207 | `try_examples_global_button_text`, `try_examples_global_theme`, and `try_examples_global_warning_text` in `conf.py`; 208 | these apply both to automatically and manually inserted directives. Options set explicitly in a directive will 209 | override the global configuration. 210 | 211 | ```python 212 | global_enable_try_examples = True 213 | try_examples_global_button_text = "Try it in your browser!" 214 | try_examples_global_height = "200px" 215 | try_examples_global_warning_text = "Interactive examples are experimental and may not always work as expected." 216 | ``` 217 | 218 | There is no option to set a global specific height because the proper height 219 | should depend on the size of the examples content. Again, the default height of 220 | the embedded notebook's iframe container matches the height of the associated 221 | rendered doctest example so that it takes up the same amount of space on the 222 | page. For very small examples this may lead to an unusably small notebook. It's possible 223 | to set a global minimum height in the `try_examples.json` configuration file described 224 | below. 225 | 226 | ### try_examples.json configuration file. 227 | 228 | Users may place a configuration file `try_examples.json` in the source root of 229 | their documentation. This configuration file will be copied to the build root of 230 | the deployed documentation. Changes to the configuration file in the build root 231 | will be respected without rebuilding the documentation, allowing for runtime 232 | configuration. 233 | 234 | The current options are 235 | 236 | #### "ignore_patterns" 237 | 238 | The format is a list of 239 | [JavaScript Regex patterns](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions) attached to the key `"ignore_patterns"` like below. 240 | 241 | ```json 242 | { 243 | "ignore_patterns": ["^\/latest/.*", "^\/stable\/reference\/generated\/example.html"] 244 | } 245 | ``` 246 | 247 | `TryExamples` buttons will be hidden in url pathnames matching at least one of these 248 | patterns, effectively disabling the interactive documentation. In the provided example: 249 | 250 | * The pattern `"^\/latest\/.*"` disables interactive examples for urls for the documentation 251 | for the latest version of the package, which may be useful if this documentation is 252 | for a development version for which a corresponding package build is not available 253 | in a JupyterLite kernel. 254 | 255 | * The pattern `"^\/stable\/reference\/generated\/example.html"` targets a particular url 256 | in the documentation for the latest stable release. 257 | 258 | Note that these patterns should match the [pathname](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname) of the url, not the full url. This is the path portion of 259 | the url. For instance, the pathname of https://jupyterlite-sphinx.readthedocs.io/en/latest/directives/try_examples.html is `/en/latest/directives/try_examples.html`. Also, note that since these are JavaScript-based regular 260 | expressions, to use special characters in the regular expression (such as `/`), they 261 | must be escaped with a backslash (`\`). 262 | 263 | Again, the configuration file can be added or edited within the deployed documentation, 264 | allowing for disabling or enabling examples without rebuilding the documentation. 265 | 266 | #### "global_min_height" 267 | 268 | To avoid having unusably small notebooks for very small examples due to the default of 269 | having the embedded notebooks' iframe containers take the same amount of space as the 270 | rendered content they replace, users can set a global minimum height in 271 | `try_examples.json`. 272 | 273 | ```json 274 | { 275 | "global_min_height": "400px" 276 | } 277 | ``` 278 | 279 | This allows the minimum height to be set or changed without rebuilding the docs. This 280 | configuration value will be ignored when a specific height is supplied as an option to 281 | `.. try_examples::`. 282 | 283 | 284 | ## Other considerations 285 | If you are using the `TryExamples` directive in your documentation, you'll need to ensure 286 | that the version of the package installed in the Jupyterlite kernel you are using 287 | matches that of the version you are documenting. 288 | -------------------------------------------------------------------------------- /docs/directives/voici.md: -------------------------------------------------------------------------------- 1 | # Voici directive 2 | 3 | `jupyterlite-sphinx` provides a `voici` directive that allows you to embed a [voici dashboard](https://github.com/voila-dashboards/voici) in your docs. 4 | 5 | ```rst 6 | .. voici:: 7 | :height: 600px 8 | ``` 9 | 10 | ```{eval-rst} 11 | .. voici:: 12 | :height: 600px 13 | ``` 14 | 15 | You can provide a notebook file (either Jupyter-based or MyST-Markdown flavoured) that will be 16 | rendered with Voici: 17 | 18 | 1. Jupyter Notebook 19 | 20 | ```rst 21 | .. voici:: my_notebook.ipynb 22 | :height: 600px 23 | :prompt: Try Voici! 24 | :prompt_color: #dc3545 25 | ``` 26 | 27 | ```{eval-rst} 28 | .. voici:: my_notebook.ipynb 29 | :height: 600px 30 | :prompt: Try Voici! 31 | :prompt_color: #dc3545 32 | ``` 33 | 34 | 2. MyST Markdown 35 | 36 | ```rst 37 | .. voici:: my_markdown_notebook.md 38 | :height: 600px 39 | :prompt: Try Voici! 40 | :prompt_color: `#dc3545` 41 | ``` 42 | 43 | ```{eval-rst} 44 | .. voici:: my_markdown_notebook.md 45 | :height: 600px 46 | :prompt: Try Voici! 47 | :prompt_color: `#dc3545` 48 | ``` 49 | 50 | If you use the `:new_tab:` option in the directive, the Voici dashboard will execute and render 51 | the notebook in a new browser tab, instead of in the current page. 52 | 53 | ```rst 54 | .. voici:: my_notebook.ipynb 55 | :new_tab: True 56 | ``` 57 | 58 | ```{eval-rst} 59 | .. voici:: my_notebook.ipynb 60 | :new_tab: True 61 | ``` 62 | 63 | When using this option, it is also possible to customise the button text, overriding the 64 | global value using an additional `:new_tab_button_text:` parameter: 65 | 66 | ```rst 67 | .. voici:: my_notebook.ipynb 68 | :new_tab: True 69 | :new_tab_button_text: My custom Voici button text 70 | ``` 71 | 72 | ```{eval-rst} 73 | .. voici:: my_notebook.ipynb 74 | :new_tab: True 75 | :new_tab_button_text: My custom Voici button text 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/environment.yml: -------------------------------------------------------------------------------- 1 | name: jupyterlite-sphinx 2 | channels: 3 | - https://repo.mamba.pm/emscripten-forge 4 | - conda-forge 5 | dependencies: 6 | - pandas 7 | - matplotlib 8 | - bqplot 9 | - xeus-python 10 | -------------------------------------------------------------------------------- /docs/full.md: -------------------------------------------------------------------------------- 1 | # Fullscreen access 2 | 3 | Once a JupyterLite example gets activated by a user, an "Open in Tab" button becomes available, which will open the same 4 | JupyterLite instance in a separate tab. 5 | 6 | ## Custom links to JupyterLite apps 7 | 8 | You can access the JupyterLite apps that `jupyterlite-sphinx` deployed for you, in fullscreen, using the following links: 9 | 10 | - [JupyterLab](lite/lab/index.html) 11 | - [Notebook](lite/tree/index.html) 12 | - [REPL](lite/repl/index.html) 13 | - [Voici](lite/voici/index.html) 14 | 15 | ## Tips for handling URLs 16 | 17 | If you want to open a specific notebook in fullscreen JupyterLab/Notebook/Voici, you can use the `path` URL parameter, e.g. 18 | 19 | - `./lite/lab/index.html?path=my_notebook.ipynb` for Lab 20 | - `./lite/notebooks/index.html?path=my_notebook.ipynb` for Notebook 21 | - `./lite/voici/render/my_notebook.html` for Voici 22 | 23 | If you want to add code to the REPL for execution, you can use the `code` URL parameter, e.g. `./lite/repl/index.html?code=print("Hello, world!")`. You may also use `&execute=0` to prevent the code from being executed until you press Enter. 24 | 25 | Info on more configuration options is available in the [REPL documentation](https://jupyterlite.readthedocs.io/en/stable/quickstart/embed-repl.html#configuration). 26 | 27 | Please see the documentation for individual options for each directive and [global configuration options](configuration.md) for more information. 28 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # jupyterlite-sphinx 2 | 3 | A Sphinx extension that provides utilities for embedding JupyterLite in your docs. 4 | 5 | `jupyterlite-sphinx` brings the power of JupyterLite to your Sphinx documentation. It makes a full JupyterLite deployment in your docs and provide some utilities for using that deployment easily. 6 | 7 | ```{eval-rst} 8 | .. replite:: 9 | :kernel: xeus-python 10 | :toolbar: 0 11 | :theme: JupyterLab Light 12 | :width: 100% 13 | :height: 600px 14 | 15 | print("Hello from a JupyterLite console!") 16 | ``` 17 | 18 | ```{toctree} 19 | :maxdepth: 2 20 | 21 | installation 22 | configuration 23 | ``` 24 | 25 | ## Usage 26 | 27 | `jupyterlite-sphinx` provides a collection of directives that allows you to embed the different JupyterLite UIs directly in your documentation page. 28 | Each of those directives can be configured with the following options: 29 | 30 | - `width` (default `"100%"`) the width of the UI 31 | - `height` (default `"600px"`) the height of the UI 32 | - `theme` (default `None`) the JupyterLab theme to use 33 | - `prompt` (default `False`) whether or not to lazy-load the UI. If the value is a string, it will use this value for the prompt button. 34 | - `prompt_color` (default `#f7dc1e`) The color of the prompt button, if there is one. 35 | 36 | ```{toctree} 37 | :maxdepth: 2 38 | 39 | directives/jupyterlite 40 | directives/notebooklite 41 | directives/replite 42 | directives/voici 43 | directives/try_examples 44 | full 45 | changelog 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | You can install `jupyterlite-sphinx` with `pip`: 4 | 5 | ``` 6 | pip install jupyterlite-sphinx 7 | ``` 8 | 9 | `jupyterlite-sphinx` is also available on `conda-forge`. You can install it with `conda` or `mamba`: 10 | 11 | ```bash 12 | # with conda 13 | conda install -c conda-forge jupyterlite-sphinx 14 | 15 | # with mamba 16 | mamba install -c conda-forge jupyterlite-sphinx 17 | ``` 18 | 19 | then you need to add the `jupyterlite-sphinx` extension to your `conf.py` file of your sphinx docs: 20 | 21 | ```python 22 | extensions = [ 23 | 'jupyterlite_sphinx', 24 | # And other sphinx extensions 25 | # ... 26 | ] 27 | ``` 28 | 29 | JupyterLite should automatically show up in your built online documentation. To preview it locally, you can navigate to the build directory (e.g. `_build/html`) and use `python -m http.server` to serve the site. 30 | 31 | ````{note} 32 | By default `jupyterlite-sphinx` does not install a Python kernel. 33 | If you would like have a Python kernel available in your docs you can install either `jupyterlite-pyodide-kernel` or `jupyterlite-xeus` with `pip`: 34 | 35 | ```shell 36 | # to install the Python kernel based on Pyodide 37 | pip install jupyterlite-pyodide-kernel 38 | 39 | # to load Xeus-based kernels 40 | pip install jupyterlite-xeus 41 | ``` 42 | ```` -------------------------------------------------------------------------------- /docs/sample_overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "@jupyterlab/notebook-extension:panel": { 3 | "toolbar": [ 4 | { 5 | "name": "download", 6 | "label": "Download", 7 | "args": {}, 8 | "command": "docmanager:download", 9 | "icon": "ui-components:download", 10 | "rank": 50 11 | } 12 | ] 13 | }, 14 | "@jupyterlab/apputils-extension:themes": { 15 | "theme": "JupyterLab Christmas" 16 | }, 17 | "@jupyter-notebook/application-extension:top": { 18 | "visible": "no" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jupyterlite_sphinx/__init__.py: -------------------------------------------------------------------------------- 1 | from .jupyterlite_sphinx import setup ## noqa 2 | 3 | __version__ = "0.20.2" 4 | -------------------------------------------------------------------------------- /jupyterlite_sphinx/_try_examples.py: -------------------------------------------------------------------------------- 1 | import nbformat as nbf 2 | from nbformat.v4 import new_code_cell, new_markdown_cell 3 | import re 4 | 5 | 6 | def examples_to_notebook(input_lines, *, warning_text=None): 7 | """Parse examples section of a docstring and convert to Jupyter notebook. 8 | 9 | Parameters 10 | ---------- 11 | input_lines : iterable of str. 12 | 13 | warning_text : str[Optional] 14 | If given, add a markdown cell at the top of the generated notebook 15 | containing the given text. The cell will be styled to indicate that 16 | this is a warning. 17 | 18 | Returns 19 | ------- 20 | dict 21 | json for a Jupyter Notebook 22 | 23 | Examples 24 | -------- 25 | >>> from jupyterlite_sphinx._try_examples import examples_to_notebook 26 | 27 | >>> input_lines = [ 28 | >>> "Add two numbers. This block of text will appear as a\n", 29 | >>> "markdown cell. The following block will become a code\n", 30 | >>> "cell with the value 4 contained in the output.", 31 | >>> "\n", 32 | >>> ">>> x = 2\n", 33 | >>> ">>> y = 2\n", 34 | >>> ">>> x + y\n", 35 | >>> "4\n", 36 | >>> "\n", 37 | >>> "Inline LaTeX like :math:`x + y = 4` will be converted\n", 38 | >>> "correctly within markdown cells. As will block LaTeX\n", 39 | >>> "such as\n", 40 | >>> "\n", 41 | >>> ".. math::\n", 42 | >>> "\n", 43 | >>> " x = 2,\\;y = 2 44 | >>> "\n", 45 | >>> " x + y = 4\n", 46 | >>> ] 47 | >>> notebook = examples_to_notebook(input_lines) 48 | """ 49 | nb = nbf.v4.new_notebook() 50 | 51 | if warning_text is not None: 52 | # Two newlines \n\n signal that the inner content should be parsed as 53 | # markdown. 54 | warning = f"
\n\n{warning_text}\n\n
" 55 | nb.cells.append(new_markdown_cell(warning)) 56 | 57 | code_lines = [] 58 | md_lines = [] 59 | output_lines = [] 60 | inside_multiline_code_block = False 61 | 62 | ignore_directives = [".. plot::", ".. only::"] 63 | inside_ignore_directive = False 64 | 65 | for line in input_lines: 66 | line = line.rstrip("\n") 67 | 68 | # Content underneath some directives should be ignored when generating notebook. 69 | if any(line.startswith(directive) for directive in ignore_directives): 70 | inside_ignore_directive = True 71 | continue 72 | if inside_ignore_directive: 73 | if line == "" or line[0].isspace(): 74 | continue 75 | else: 76 | inside_ignore_directive = False 77 | 78 | if line.startswith(">>>"): # This is a code line. 79 | if output_lines: 80 | # If there are pending output lines, we must be starting a new 81 | # code block. 82 | _append_code_cell_and_clear_lines(code_lines, output_lines, nb) 83 | if inside_multiline_code_block: 84 | # A multiline codeblock is ending. 85 | inside_multiline_code_block = False 86 | # If there is any pending markdown text, add it to the notebook 87 | if md_lines: 88 | _append_markdown_cell_and_clear_lines(md_lines, nb) 89 | 90 | # Add line of code, removing '>>> ' prefix 91 | code_lines.append(line[4:]) 92 | elif line.startswith("...") and code_lines: 93 | # This is a line of code in a multiline code block. 94 | inside_multiline_code_block = True 95 | code_lines.append(line[4:]) 96 | elif line.rstrip("\n") == "" and code_lines: 97 | # A blank line means a code block has ended. 98 | _append_code_cell_and_clear_lines(code_lines, output_lines, nb) 99 | elif code_lines: 100 | # Non-blank non ">>>" prefixed line must be output of previous code block. 101 | output_lines.append(line) 102 | else: 103 | # Anything else should be treated as markdown. 104 | md_lines.append(line) 105 | 106 | # After processing all lines, add pending markdown or code to the notebook if 107 | # any exists. 108 | if md_lines: 109 | _append_markdown_cell_and_clear_lines(md_lines, nb) 110 | if code_lines: 111 | _append_code_cell_and_clear_lines(code_lines, output_lines, nb) 112 | 113 | nb["metadata"] = { 114 | "kernelspec": { 115 | "display_name": "Python", 116 | "language": "python", 117 | "name": "python", 118 | }, 119 | "language_info": { 120 | "name": "python", 121 | }, 122 | } 123 | return nb 124 | 125 | 126 | def _append_code_cell_and_clear_lines(code_lines, output_lines, notebook): 127 | """Append new code cell to notebook, clearing lines.""" 128 | code_text = "\n".join(code_lines) 129 | cell = new_code_cell(code_text) 130 | if output_lines: 131 | combined_output = "\n".join(output_lines) 132 | cell.outputs.append( 133 | nbf.v4.new_output( 134 | output_type="execute_result", 135 | data={"text/plain": combined_output}, 136 | ), 137 | ) 138 | notebook.cells.append(cell) 139 | output_lines.clear() 140 | code_lines.clear() 141 | 142 | 143 | def _append_markdown_cell_and_clear_lines(markdown_lines, notebook): 144 | """Append new markdown cell to notebook, clearing lines.""" 145 | markdown_text = "\n".join(markdown_lines) 146 | markdown_text = _process_latex(markdown_text) 147 | markdown_text = _process_literal_blocks(markdown_text) 148 | markdown_text = _strip_ref_identifiers(markdown_text) 149 | markdown_text = _convert_links(markdown_text) 150 | notebook.cells.append(new_markdown_cell(markdown_text)) 151 | markdown_lines.clear() 152 | 153 | 154 | _ref_identifier_pattern = re.compile(r"\[R[a-f0-9]+-(?P\d+)\]_") 155 | _link_pattern = re.compile(r"`(?P[^`<]+)<(?P[^`>]+)>`_") 156 | 157 | 158 | def _convert_sphinx_link(match): 159 | link_text = match.group("link_text").rstrip() 160 | url = match.group("url") 161 | return f"[{link_text}]({url})" 162 | 163 | 164 | def _convert_links(md_text): 165 | """Convert sphinx style links to markdown style links 166 | 167 | Sphinx style links have the form `link text `_. Converts to 168 | markdown format [link text](url). 169 | """ 170 | return _link_pattern.sub(_convert_sphinx_link, md_text) 171 | 172 | 173 | def _strip_ref_identifiers(md_text): 174 | """Remove identifiers from references in notebook. 175 | 176 | Each docstring gets a unique identifier in order to have unique internal 177 | links for each docstring on a page. 178 | 179 | They look like [R4c2dbc17006a-1]_. We strip these out so they don't appear 180 | in the notebooks. The above would be replaced with [1]_. 181 | """ 182 | return _ref_identifier_pattern.sub(r"[\g]", md_text) 183 | 184 | 185 | def _process_latex(md_text): 186 | # Map rst latex directive to $ so latex renders in notebook. 187 | md_text = re.sub( 188 | r":math:\s*`(?P.*?)`", r"$\g$", md_text, flags=re.DOTALL 189 | ) 190 | 191 | lines = md_text.split("\n") 192 | in_math_block = False 193 | wrapped_lines = [] 194 | equation_lines = [] 195 | 196 | for line in lines: 197 | if line.strip() == ".. math::": 198 | in_math_block = True 199 | continue # Skip the '.. math::' line 200 | 201 | if in_math_block: 202 | if line.strip() == "": 203 | if equation_lines: 204 | # Join and wrap the equations, then reset 205 | wrapped_lines.append(f"$$ {' '.join(equation_lines)} $$") 206 | equation_lines = [] 207 | elif line.startswith(" ") or line.startswith("\t"): 208 | equation_lines.append(line.strip()) 209 | else: 210 | wrapped_lines.append(line) 211 | 212 | # If you leave the indented block, the math block ends 213 | if in_math_block and not ( 214 | line.startswith(" ") or line.startswith("\t") or line.strip() == "" 215 | ): 216 | in_math_block = False 217 | if equation_lines: 218 | wrapped_lines.append(f"$$ {' '.join(equation_lines)} $$") 219 | equation_lines = [] 220 | wrapped_lines.append(line) 221 | 222 | # Handle the case where the text ends with a math block 223 | if in_math_block and equation_lines: 224 | wrapped_lines.append(f"$$ {' '.join(equation_lines)} $$") 225 | 226 | return "\n".join(wrapped_lines) 227 | 228 | 229 | def _process_literal_blocks(md_text): 230 | md_lines = md_text.split("\n") 231 | new_lines = [] 232 | in_literal_block = False 233 | literal_block_accumulator = [] 234 | 235 | for line in md_lines: 236 | indent_level = len(line) - len(line.lstrip()) 237 | 238 | if in_literal_block and (indent_level > 0 or line.strip() == ""): 239 | literal_block_accumulator.append(line.lstrip()) 240 | elif in_literal_block: 241 | new_lines.extend(["```"] + literal_block_accumulator + ["```"]) 242 | literal_block_accumulator = [] 243 | if line.endswith("::"): 244 | # If the line endswith ::, a new literal block is starting. 245 | line = line[:-2] # Strip off the :: from the end 246 | if not line: 247 | # If the line contains only ::, we ignore it. 248 | continue 249 | else: 250 | # Only set in_literal_block to False if not starting new 251 | # literal block. 252 | in_literal_block = False 253 | # We've appended the entire literal block which just ended, but 254 | # still need to append the current line. 255 | new_lines.append(line) 256 | else: 257 | if line.endswith("::"): 258 | # A literal block is starting. 259 | in_literal_block = True 260 | line = line[:-2] 261 | if not line: 262 | # As above, if the line contains only ::, ignore it. 263 | continue 264 | new_lines.append(line) 265 | 266 | if literal_block_accumulator: 267 | # Handle case where a literal block ends the markdown cell. 268 | new_lines.extend(["```"] + literal_block_accumulator + ["```"]) 269 | 270 | return "\n".join(new_lines) 271 | 272 | 273 | # try_examples identifies section headers after processing by numpydoc or 274 | # sphinx.ext.napoleon. 275 | # See https://numpydoc.readthedocs.io/en/stable/format.html for info on numpydoc 276 | # sections and 277 | # https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#docstring-sections 278 | # for info on sphinx.ext.napoleon sections. 279 | 280 | # The patterns below were identified by creating a docstring using all section 281 | # headers and processing it with both numpydoc and sphinx.ext.napoleon. 282 | 283 | # Examples section is a rubric for numpydoc and can be configured to be 284 | # either a rubric or admonition in sphinx.ext.napoleon. 285 | _examples_start_pattern = re.compile(r".. (rubric|admonition):: Examples") 286 | _next_section_pattern = re.compile( 287 | "|".join( 288 | # Newer versions of numpydoc enforce section order and only Attributes 289 | # and Methods sections can appear after an Examples section. All potential 290 | # numpydoc section headers are included here to support older or custom 291 | # numpydoc versions. e.g. at the time of this comment, SymPy is using a 292 | # custom version of numpydoc which allows for arbitrary section order. 293 | # sphinx.ext.napoleon allows for arbitrary section order and all potential 294 | # headers are included. 295 | [ 296 | # Notes and References appear as rubrics in numpydoc and 297 | # can be configured to appear as either a rubric or 298 | # admonition in sphinx.ext.napoleon. 299 | r".\.\ rubric:: Notes", 300 | r".\.\ rubric:: References", 301 | r".\.\ admonition:: Notes", 302 | r".\.\ admonition:: References", 303 | # numpydoc only headers 304 | r":Attributes:", 305 | r".\.\ rubric:: Methods", 306 | r":Other Parameters:", 307 | r":Parameters:", 308 | r":Raises:", 309 | r":Returns:", 310 | # If examples section is last, processed by numpydoc may appear at end. 311 | r"\!\! processed by numpydoc \!\!", 312 | # sphinx.ext.napoleon only headers 313 | r"\.\. attribute::", 314 | r"\.\. method::", 315 | r":param .+:", 316 | r":raises .+:", 317 | r":returns:", 318 | # Headers generated by both extensions 319 | r".\.\ seealso::", 320 | r":Yields:", 321 | r":Warns:", 322 | # directives which can start a section with sphinx.ext.napoleon 323 | # with no equivalent when using numpydoc. 324 | r".\.\ attention::", 325 | r".\.\ caution::", 326 | r".\.\ danger::", 327 | r".\.\ error::", 328 | r".\.\ hint::", 329 | r".\.\ important::", 330 | r".\.\ tip::", 331 | r".\.\ todo::", 332 | r".\.\ warning::", 333 | ] 334 | ) 335 | ) 336 | 337 | 338 | def insert_try_examples_directive(lines, **options): 339 | """Adds try_examples directive to Examples section of a docstring. 340 | 341 | Hack to allow for a config option to enable try_examples functionality 342 | in all Examples sections (unless a comment ".. disable_try_examples" is 343 | added explicitly after the section header.) 344 | 345 | 346 | Parameters 347 | ---------- 348 | docstring : list of str 349 | Lines of a docstring at time of "autodoc-process-docstring", which has 350 | been previously processed by numpydoc or sphinx.ext.napoleon. 351 | 352 | Returns 353 | ------- 354 | list of str 355 | Updated version of the input docstring which has a try_examples directive 356 | inserted in the Examples section (if one exists) with all Examples content 357 | indented beneath it. Does nothing if the comment ".. disable_try_examples" 358 | is included at the top of the Examples section. Also a no-op if the 359 | try_examples directive is already included. 360 | """ 361 | # Search for start of an Examples section 362 | for left_index, line in enumerate(lines): 363 | if _examples_start_pattern.search(line): 364 | break 365 | else: 366 | # No Examples section found 367 | return lines[:] 368 | 369 | # Jump to next line 370 | left_index += 1 371 | # Skip empty lines to get to the first content line 372 | while left_index < len(lines) and not lines[left_index].strip(): 373 | left_index += 1 374 | if left_index == len(lines): 375 | # Examples section had no content, no need to insert directive. 376 | return lines[:] 377 | 378 | # Check for the ".. disable_try_examples" comment. 379 | if lines[left_index].strip() == ".. disable_try_examples": 380 | # If so, do not insert directive. 381 | return lines[:] 382 | 383 | # Check if the ".. try_examples::" directive already exists 384 | if ".. try_examples::" == lines[left_index].strip(): 385 | # If so, don't need to insert again. 386 | return lines[:] 387 | 388 | # Find the end of the Examples section 389 | right_index = left_index 390 | while right_index < len(lines) and not _next_section_pattern.search( 391 | lines[right_index] 392 | ): 393 | right_index += 1 394 | 395 | # Check if we've reached the end of the docstring 396 | if right_index < len(lines) and "!! processed by numpydoc !!" in lines[right_index]: 397 | # Sometimes the .. appears on an earlier line than !! processed by numpydoc !! 398 | if not re.search( 399 | r"\.\.\s+\!\! processed by numpy doc \!\!", lines[right_index] 400 | ): 401 | while right_index > 0 and lines[right_index].strip() != "..": 402 | right_index -= 1 403 | 404 | # Add the ".. try_examples::" directive and indent the content of the Examples section 405 | new_lines = ( 406 | lines[:left_index] 407 | + [".. try_examples::"] 408 | + [f" :{key}: {value}" for key, value in options.items()] 409 | + [""] 410 | + [" " + line for line in lines[left_index:right_index]] 411 | ) 412 | 413 | # Append the remainder of the docstring, if there is any 414 | if right_index < len(lines): 415 | new_lines += [""] + lines[right_index:] 416 | 417 | return new_lines 418 | -------------------------------------------------------------------------------- /jupyterlite_sphinx/jupyterlite_sphinx.css: -------------------------------------------------------------------------------- 1 | .jupyterlite_sphinx_raw_iframe { 2 | border-width: 1px; 3 | border-style: solid; 4 | border-color: #d8d8d8; 5 | box-shadow: 0 0.2rem 0.5rem #d8d8d8; 6 | } 7 | 8 | .jupyterlite_sphinx_iframe_container { 9 | border-width: 1px; 10 | border-style: solid; 11 | border-color: #d8d8d8; 12 | position: relative; 13 | cursor: pointer; 14 | box-shadow: 0 0.2rem 0.5rem rgba(19, 23, 29, 0.4); 15 | margin-bottom: 1.5rem; 16 | } 17 | 18 | .jupyterlite_sphinx_iframe { 19 | z-index: 1; 20 | position: relative; 21 | border-style: none; 22 | } 23 | 24 | .jupyterlite_sphinx_try_it_button { 25 | z-index: 0; 26 | position: absolute; 27 | left: 50%; 28 | top: 50%; 29 | transform: translateY(-50%) translateX(-50%); 30 | height: 100px; 31 | width: 100px; 32 | line-height: 100px; 33 | text-align: center; 34 | white-space: nowrap; 35 | background-color: #f7dc1e; 36 | color: #13171d; 37 | border-radius: 50%; 38 | font-family: vibur; 39 | font-size: larger; 40 | box-shadow: 0 0.2rem 0.5rem rgba(19, 23, 29, 0.4); 41 | } 42 | 43 | .try_examples_outer_container { 44 | position: relative; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | } 50 | 51 | .jupyterlite_sphinx_spinner { 52 | /* From https://css-loaders.com/spinner/ */ 53 | position: absolute; 54 | z-index: 0; 55 | top: 50%; 56 | left: 50%; 57 | width: 50px; 58 | aspect-ratio: 1; 59 | border-radius: 50%; 60 | background: 61 | radial-gradient(farthest-side, #ffa516 94%, #0000) top/8px 8px no-repeat, 62 | conic-gradient(#0000 30%, #ffa516); 63 | -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 8px), #000 0); 64 | animation: l13 1s infinite linear; 65 | } 66 | @keyframes l13 { 67 | 100% { 68 | transform: rotate(1turn); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jupyterlite_sphinx/jupyterlite_sphinx.js: -------------------------------------------------------------------------------- 1 | window.jupyterliteShowIframe = (tryItButtonId, iframeSrc) => { 2 | const tryItButton = document.getElementById(tryItButtonId); 3 | const iframe = document.createElement("iframe"); 4 | const buttonRect = tryItButton.getBoundingClientRect(); 5 | 6 | const spinner = document.createElement("div"); 7 | // hardcoded spinner height and width needs to match what is in css. 8 | const spinnerHeight = 50; // px 9 | const spinnerWidth = 50; // px 10 | spinner.classList.add("jupyterlite_sphinx_spinner"); 11 | spinner.style.display = "none"; 12 | // Add negative margins to center the spinner 13 | spinner.style.marginTop = `-${spinnerHeight / 2}px`; 14 | spinner.style.marginLeft = `-${spinnerWidth / 2}px`; 15 | 16 | iframe.src = iframeSrc; 17 | iframe.width = iframe.height = "100%"; 18 | iframe.classList.add("jupyterlite_sphinx_iframe"); 19 | 20 | tryItButton.style.display = "none"; 21 | spinner.style.display = "block"; 22 | 23 | tryItButton.parentNode.appendChild(spinner); 24 | tryItButton.parentNode.appendChild(iframe); 25 | }; 26 | 27 | window.jupyterliteConcatSearchParams = (iframeSrc, params) => { 28 | const baseURL = window.location.origin; 29 | const iframeUrl = new URL(iframeSrc, baseURL); 30 | 31 | let pageParams = new URLSearchParams(window.location.search); 32 | 33 | if (params === true) { 34 | params = Array.from(pageParams.keys()); 35 | } else if (params === false) { 36 | params = []; 37 | } else if (!Array.isArray(params)) { 38 | console.error("The search parameters are not an array"); 39 | } 40 | 41 | params.forEach((param) => { 42 | value = pageParams.get(param); 43 | if (value !== null) { 44 | iframeUrl.searchParams.append(param, value); 45 | } 46 | }); 47 | 48 | if (iframeUrl.searchParams.size) { 49 | return `${iframeSrc.split("?")[0]}?${iframeUrl.searchParams.toString()}`; 50 | } else { 51 | return iframeSrc; 52 | } 53 | }; 54 | 55 | window.tryExamplesShowIframe = ( 56 | examplesContainerId, 57 | iframeContainerId, 58 | iframeParentContainerId, 59 | iframeSrc, 60 | iframeHeight, 61 | ) => { 62 | const examplesContainer = document.getElementById(examplesContainerId); 63 | const iframeParentContainer = document.getElementById( 64 | iframeParentContainerId, 65 | ); 66 | const iframeContainer = document.getElementById(iframeContainerId); 67 | var height; 68 | 69 | let iframe = iframeContainer.querySelector( 70 | "iframe.jupyterlite_sphinx_iframe", 71 | ); 72 | 73 | if (!iframe) { 74 | // Add spinner 75 | const spinner = document.createElement("div"); 76 | // hardcoded spinner width needs to match what is in css. 77 | const spinnerHeight = 50; // px 78 | const spinnerWidth = 50; // px 79 | spinner.classList.add("jupyterlite_sphinx_spinner"); 80 | iframeContainer.appendChild(spinner); 81 | 82 | const examples = examplesContainer.querySelector(".try_examples_content"); 83 | iframe = document.createElement("iframe"); 84 | iframe.src = iframeSrc; 85 | iframe.style.width = "100%"; 86 | if (iframeHeight !== "None") { 87 | height = parseInt(iframeHeight); 88 | } else { 89 | height = Math.max(tryExamplesGlobalMinHeight, examples.offsetHeight); 90 | } 91 | 92 | /* Get spinner position. It will be centered in the iframe, unless the 93 | * iframe extends beyond the viewport, in which case it will be centered 94 | * between the top of the iframe and the bottom of the viewport. 95 | */ 96 | const examplesTop = examples.getBoundingClientRect().top; 97 | const viewportBottom = window.innerHeight; 98 | const spinnerTop = 0.5 * Math.min(viewportBottom - examplesTop, height); 99 | spinner.style.top = `${spinnerTop}px`; 100 | // Add negative margins to center the spinner 101 | spinner.style.marginTop = `-${spinnerHeight / 2}px`; 102 | spinner.style.marginLeft = `-${spinnerWidth / 2}px`; 103 | 104 | iframe.style.height = `${height}px`; 105 | iframe.classList.add("jupyterlite_sphinx_iframe"); 106 | examplesContainer.classList.add("hidden"); 107 | 108 | iframeContainer.appendChild(iframe); 109 | } else { 110 | examplesContainer.classList.add("hidden"); 111 | } 112 | iframeParentContainer.classList.remove("hidden"); 113 | }; 114 | 115 | window.tryExamplesHideIframe = ( 116 | examplesContainerId, 117 | iframeParentContainerId, 118 | ) => { 119 | const examplesContainer = document.getElementById(examplesContainerId); 120 | const iframeParentContainer = document.getElementById( 121 | iframeParentContainerId, 122 | ); 123 | 124 | iframeParentContainer.classList.add("hidden"); 125 | examplesContainer.classList.remove("hidden"); 126 | }; 127 | 128 | // this will be used by the "Open in tab" button that is present next 129 | // # to the "go back" button after an iframe is made visible. 130 | window.openInNewTab = (examplesContainerId, iframeParentContainerId) => { 131 | const examplesContainer = document.getElementById(examplesContainerId); 132 | const iframeParentContainer = document.getElementById( 133 | iframeParentContainerId, 134 | ); 135 | 136 | window.open( 137 | // we make some assumption that there is a single iframe and the the src is what we want to open. 138 | // Maybe we should have tabs open JupyterLab by default. 139 | iframeParentContainer.getElementsByTagName("iframe")[0].getAttribute("src"), 140 | ); 141 | tryExamplesHideIframe(examplesContainerId, iframeParentContainerId); 142 | }; 143 | 144 | /* Global variable for try_examples iframe minHeight. Defaults to 0 but can be 145 | * modified based on configuration in try_examples.json */ 146 | var tryExamplesGlobalMinHeight = 0; 147 | /* Global variable to check if config has been loaded. This keeps it from getting 148 | * loaded multiple times if there are multiple try_examples directives on one page 149 | */ 150 | var tryExamplesConfigLoaded = false; 151 | 152 | // This function is used to check if the current device is a mobile device. 153 | // We assume the authenticity of the user agent string is enough to 154 | // determine that, and we also check the window size as a fallback. 155 | window.isMobileDevice = (() => { 156 | let cachedUAResult = null; 157 | let hasLogged = false; 158 | 159 | const checkUserAgent = () => { 160 | if (cachedUAResult !== null) { 161 | return cachedUAResult; 162 | } 163 | 164 | const mobilePatterns = [ 165 | /Android/i, 166 | /webOS/i, 167 | /iPhone/i, 168 | /iPad/i, 169 | /iPod/i, 170 | /BlackBerry/i, 171 | /IEMobile/i, 172 | /Windows Phone/i, 173 | /Opera Mini/i, 174 | /SamsungBrowser/i, 175 | /UC.*Browser|UCWEB/i, 176 | /MiuiBrowser/i, 177 | /Mobile/i, 178 | /Tablet/i, 179 | ]; 180 | 181 | cachedUAResult = mobilePatterns.some((pattern) => 182 | pattern.test(navigator.userAgent), 183 | ); 184 | return cachedUAResult; 185 | }; 186 | 187 | return () => { 188 | const isMobileBySize = 189 | window.innerWidth <= 480 || window.innerHeight <= 480; 190 | const isLikelyMobile = checkUserAgent() || isMobileBySize; 191 | 192 | if (isLikelyMobile && !hasLogged) { 193 | console.log( 194 | "Either a mobile device detected or the screen was resized. Disabling interactive example buttons to conserve bandwidth.", 195 | ); 196 | hasLogged = true; 197 | } 198 | 199 | return isLikelyMobile; 200 | }; 201 | })(); 202 | 203 | // A config loader with request deduplication + permanent caching 204 | const ConfigLoader = (() => { 205 | let configLoadPromise = null; 206 | 207 | const loadConfig = async (configFilePath) => { 208 | if (window.isMobileDevice()) { 209 | const buttons = document.getElementsByClassName("try_examples_button"); 210 | for (let i = 0; i < buttons.length; i++) { 211 | buttons[i].classList.add("hidden"); 212 | } 213 | tryExamplesConfigLoaded = true; // mock it 214 | return; 215 | } 216 | 217 | if (tryExamplesConfigLoaded) { 218 | return; 219 | } 220 | 221 | // Return the existing promise if the request is in progress, as we 222 | // don't want to make multiple requests for the same file. This 223 | // can happen if there are several try_examples directives on the 224 | // same page. 225 | if (configLoadPromise) { 226 | return configLoadPromise; 227 | } 228 | 229 | // Create and cache the promise for the config request 230 | configLoadPromise = (async () => { 231 | try { 232 | // Add a timestamp as query parameter to ensure a cached version of the 233 | // file is not used. 234 | const timestamp = new Date().getTime(); 235 | const configFileUrl = `${configFilePath}?cb=${timestamp}`; 236 | const currentPageUrl = window.location.pathname; 237 | 238 | const response = await fetch(configFileUrl); 239 | if (!response.ok) { 240 | if (response.status === 404) { 241 | console.log("Optional try_examples config file not found."); 242 | return; 243 | } 244 | throw new Error(`Error fetching ${configFilePath}`); 245 | } 246 | 247 | const data = await response.json(); 248 | if (!data) { 249 | return; 250 | } 251 | 252 | // Set minimum iframe height based on value in config file 253 | if (data.global_min_height) { 254 | tryExamplesGlobalMinHeight = parseInt(data.global_min_height); 255 | } 256 | 257 | // Disable interactive examples if file matches one of the ignore patterns 258 | // by hiding try_examples_buttons. 259 | Patterns = data.ignore_patterns; 260 | for (let pattern of Patterns) { 261 | let regex = new RegExp(pattern); 262 | if (regex.test(currentPageUrl)) { 263 | var buttons = document.getElementsByClassName( 264 | "try_examples_button", 265 | ); 266 | for (var i = 0; i < buttons.length; i++) { 267 | buttons[i].classList.add("hidden"); 268 | } 269 | break; 270 | } 271 | } 272 | } catch (error) { 273 | console.error(error); 274 | } finally { 275 | tryExamplesConfigLoaded = true; 276 | } 277 | })(); 278 | 279 | return configLoadPromise; 280 | }; 281 | 282 | return { 283 | loadConfig, 284 | // for testing/debugging only, could be removed 285 | resetState: () => { 286 | tryExamplesConfigLoaded = false; 287 | configLoadPromise = null; 288 | }, 289 | }; 290 | })(); 291 | 292 | // Add a resize handler that will update the buttons' visibility on 293 | // orientation changes 294 | let resizeTimeout; 295 | window.addEventListener("resize", () => { 296 | clearTimeout(resizeTimeout); 297 | resizeTimeout = setTimeout(() => { 298 | if (!tryExamplesConfigLoaded) return; // since we won't interfere if the config isn't loaded 299 | 300 | const buttons = document.getElementsByClassName("try_examples_button"); 301 | const shouldHide = window.isMobileDevice(); 302 | 303 | for (let i = 0; i < buttons.length; i++) { 304 | if (shouldHide) { 305 | buttons[i].classList.add("hidden"); 306 | } else { 307 | buttons[i].classList.remove("hidden"); 308 | } 309 | } 310 | }, 250); 311 | }); 312 | 313 | window.loadTryExamplesConfig = ConfigLoader.loadConfig; 314 | 315 | window.toggleTryExamplesButtons = () => { 316 | /* Toggle visibility of TryExamples buttons. For use in console for debug 317 | * purposes. */ 318 | var buttons = document.getElementsByClassName("try_examples_button"); 319 | 320 | for (var i = 0; i < buttons.length; i++) { 321 | buttons[i].classList.toggle("hidden"); 322 | } 323 | }; 324 | -------------------------------------------------------------------------------- /jupyterlite_sphinx/jupyterlite_sphinx.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | from uuid import uuid4 5 | import shutil 6 | import re 7 | from typing import Dict, Any, List 8 | 9 | from pathlib import Path 10 | 11 | from urllib.parse import quote 12 | 13 | import subprocess 14 | from subprocess import CompletedProcess 15 | 16 | from docutils.parsers.rst import directives 17 | from docutils.nodes import SkipNode, Element 18 | from docutils import nodes 19 | 20 | from sphinx.application import Sphinx 21 | from sphinx.util.docutils import SphinxDirective 22 | from sphinx.util.fileutil import copy_asset 23 | from sphinx.parsers import RSTParser 24 | 25 | from ._try_examples import examples_to_notebook, insert_try_examples_directive 26 | 27 | import jupytext 28 | import nbformat 29 | 30 | try: 31 | import voici 32 | except ImportError: 33 | voici = None 34 | 35 | HERE = Path(__file__).parent 36 | 37 | CONTENT_DIR = "_contents" 38 | JUPYTERLITE_DIR = "lite" 39 | 40 | 41 | # Used for nodes that do not need to be rendered 42 | def skip(self, node): 43 | raise SkipNode 44 | 45 | 46 | # Used to render an element node as HTML 47 | def visit_element_html(self, node): 48 | self.body.append(node.html()) 49 | raise SkipNode 50 | 51 | 52 | class _PromptedIframe(Element): 53 | def __init__( 54 | self, 55 | rawsource="", 56 | *children, 57 | iframe_src="", 58 | width="100%", 59 | height="100%", 60 | prompt=False, 61 | prompt_color=None, 62 | search_params="false", 63 | **attributes, 64 | ): 65 | super().__init__( 66 | "", 67 | iframe_src=iframe_src, 68 | width=width, 69 | height=height, 70 | prompt=prompt, 71 | prompt_color=prompt_color, 72 | search_params=search_params, 73 | ) 74 | 75 | def html(self): 76 | iframe_src = self["iframe_src"] 77 | search_params = self["search_params"] 78 | 79 | if self["prompt"]: 80 | prompt = ( 81 | self["prompt"] if isinstance(self["prompt"], str) else "Try It Live!" 82 | ) 83 | prompt_color = ( 84 | self["prompt_color"] if self["prompt_color"] is not None else "#f7dc1e" 85 | ) 86 | 87 | placeholder_id = uuid4() 88 | container_style = f'width: {self["width"]}; height: {self["height"]};' 89 | 90 | return f""" 91 |
99 |
104 | {prompt} 105 |
106 |
107 | """ 108 | 109 | return ( 110 | f'' 112 | ) 113 | 114 | 115 | class _InTab(Element): 116 | def __init__( 117 | self, 118 | rawsource="", 119 | *children, 120 | prefix=JUPYTERLITE_DIR, 121 | notebook=None, 122 | lite_options={}, 123 | button_text=None, 124 | **attributes, 125 | ): 126 | app_path = self.lite_app 127 | if notebook is not None: 128 | lite_options["path"] = notebook 129 | app_path = f"{self.lite_app}{self.notebooks_path}" 130 | 131 | options = "&".join( 132 | [f"{key}={quote(value)}" for key, value in lite_options.items()] 133 | ) 134 | self.lab_src = ( 135 | f'{prefix}/{app_path}{f"index.html?{options}" if options else ""}' 136 | ) 137 | 138 | self.button_text = button_text 139 | 140 | super().__init__( 141 | rawsource, 142 | **attributes, 143 | ) 144 | 145 | def html(self): 146 | return ( 147 | '" 150 | ) 151 | 152 | 153 | class _LiteIframe(_PromptedIframe): 154 | def __init__( 155 | self, 156 | rawsource="", 157 | *children, 158 | prefix=JUPYTERLITE_DIR, 159 | content=[], 160 | notebook=None, 161 | lite_options={}, 162 | **attributes, 163 | ): 164 | if content: 165 | code_lines = ["" if not line.strip() else line for line in content] 166 | code = "\n".join(code_lines) 167 | 168 | lite_options["code"] = code 169 | 170 | app_path = self.lite_app 171 | if notebook is not None: 172 | lite_options["path"] = notebook 173 | app_path = f"{self.lite_app}{self.notebooks_path}" 174 | 175 | options = "&".join( 176 | [f"{key}={quote(value)}" for key, value in lite_options.items()] 177 | ) 178 | 179 | iframe_src = f'{prefix}/{app_path}{f"index.html?{options}" if options else ""}' 180 | 181 | if "iframe_src" in attributes: 182 | if attributes["iframe_src"] != iframe_src: 183 | raise ValueError( 184 | f'Two different values of iframe_src {attributes["iframe_src"]=},{iframe_src=}, try upgrading sphinx to v 7.2.0 or more recent' 185 | ) 186 | del attributes["iframe_src"] 187 | 188 | super().__init__(rawsource, *children, iframe_src=iframe_src, **attributes) 189 | 190 | 191 | class RepliteIframe(_LiteIframe): 192 | """Appended to the doctree by the RepliteDirective directive 193 | 194 | Renders an iframe that shows a repl with JupyterLite. 195 | """ 196 | 197 | lite_app = "repl/" 198 | notebooks_path = "" 199 | 200 | 201 | class JupyterLiteIframe(_LiteIframe): 202 | """Appended to the doctree by the JupyterliteDirective directive 203 | 204 | Renders an iframe that shows a Notebook with JupyterLite. 205 | """ 206 | 207 | lite_app = "lab/" 208 | notebooks_path = "" 209 | 210 | 211 | class BaseNotebookTab(_InTab): 212 | """Base class for notebook tab implementations. We subclass this 213 | to create more specific configurations around how tabs are rendered.""" 214 | 215 | lite_app = None 216 | notebooks_path = None 217 | default_button_text = "Open as a notebook" 218 | 219 | 220 | class JupyterLiteTab(BaseNotebookTab): 221 | """Appended to the doctree by the JupyterliteDirective directive 222 | 223 | Renders a button that opens a Notebook with JupyterLite in a new tab. 224 | """ 225 | 226 | lite_app = "lab/" 227 | notebooks_path = "" 228 | 229 | 230 | class NotebookLiteTab(BaseNotebookTab): 231 | """Appended to the doctree by the NotebookliteDirective directive 232 | 233 | Renders a button that opens a Notebook with NotebookLite in a new tab. 234 | """ 235 | 236 | lite_app = "tree/" 237 | notebooks_path = "../notebooks/" 238 | 239 | 240 | # We do not inherit from _InTab here because Replite 241 | # has a different URL structure and we need to ensure 242 | # that the code is serialised to be passed to the URL. 243 | class RepliteTab(Element): 244 | """Appended to the doctree by the RepliteDirective directive 245 | 246 | Renders a button that opens a REPL with JupyterLite in a new tab. 247 | """ 248 | 249 | lite_app = "repl/" 250 | notebooks_path = "" 251 | 252 | def __init__( 253 | self, 254 | rawsource="", 255 | *children, 256 | prefix=JUPYTERLITE_DIR, 257 | content=[], 258 | notebook=None, 259 | lite_options={}, 260 | button_text=None, 261 | **attributes, 262 | ): 263 | # For a new-tabbed variant, we need to ensure we process the content 264 | # into properly encoded code for passing it to the URL. 265 | if content: 266 | code_lines: list[str] = [ 267 | "" if not line.strip() else line for line in content 268 | ] 269 | code = "\n".join(code_lines) 270 | lite_options["code"] = code 271 | 272 | if "execute" in lite_options and lite_options["execute"] == "0": 273 | lite_options["execute"] = "0" 274 | 275 | app_path = self.lite_app 276 | if notebook is not None: 277 | lite_options["path"] = notebook 278 | app_path = f"{self.lite_app}{self.notebooks_path}" 279 | 280 | options = "&".join( 281 | [f"{key}={quote(value)}" for key, value in lite_options.items()] 282 | ) 283 | 284 | self.lab_src = ( 285 | f'{prefix}/{app_path}{f"index.html?{options}" if options else ""}' 286 | ) 287 | 288 | self.button_text = button_text 289 | 290 | super().__init__( 291 | rawsource, 292 | **attributes, 293 | ) 294 | 295 | def html(self): 296 | return ( 297 | '" 300 | ) 301 | 302 | 303 | class NotebookLiteIframe(_LiteIframe): 304 | """Appended to the doctree by the NotebookliteDirective directive 305 | 306 | Renders an iframe that shows a Notebook with NotebookLite. 307 | """ 308 | 309 | lite_app = "tree/" 310 | notebooks_path = "../notebooks/" 311 | 312 | 313 | class VoiciBase: 314 | """Base class with common Voici application paths and URL structure""" 315 | 316 | lite_app = "voici/" 317 | 318 | @classmethod 319 | def get_full_path(cls, notebook=None): 320 | """Get the complete Voici path based on whether a notebook is provided.""" 321 | if notebook is not None: 322 | # For notebooks, use render path with html extension 323 | return f"{cls.lite_app}render/{notebook.replace('.ipynb', '.html')}" 324 | # Default to tree view 325 | return f"{cls.lite_app}tree" 326 | 327 | 328 | class VoiciIframe(_PromptedIframe): 329 | """Appended to the doctree by the VoiciDirective directive 330 | 331 | Renders an iframe that shows a Notebook with Voici. 332 | """ 333 | 334 | def __init__( 335 | self, 336 | rawsource="", 337 | *children, 338 | prefix=JUPYTERLITE_DIR, 339 | notebook=None, 340 | lite_options={}, 341 | **attributes, 342 | ): 343 | app_path = VoiciBase.get_full_path(notebook) 344 | options = "&".join( 345 | [f"{key}={quote(value)}" for key, value in lite_options.items()] 346 | ) 347 | 348 | # If a notebook is provided, open it in the render view. Else, we default to the tree view. 349 | iframe_src = f'{prefix}/{app_path}{f"index.html?{options}" if options else ""}' 350 | 351 | super().__init__(rawsource, *children, iframe_src=iframe_src, **attributes) 352 | 353 | 354 | # We do not inherit from BaseNotebookTab here because 355 | # Voici has a different URL structure. 356 | class VoiciTab(Element): 357 | """Tabbed implementation for the Voici interface""" 358 | 359 | def __init__( 360 | self, 361 | rawsource="", 362 | *children, 363 | prefix=JUPYTERLITE_DIR, 364 | notebook=None, 365 | lite_options={}, 366 | button_text=None, 367 | **attributes, 368 | ): 369 | 370 | self.lab_src = f"{prefix}/" 371 | 372 | app_path = VoiciBase.get_full_path(notebook) 373 | options = "&".join( 374 | [f"{key}={quote(value)}" for key, value in lite_options.items()] 375 | ) 376 | 377 | # If a notebook is provided, open it in a new tab. Else, we default to the tree view. 378 | self.lab_src = f'{prefix}/{app_path}{f"?{options}" if options else ""}' 379 | 380 | self.button_text = button_text 381 | 382 | super().__init__( 383 | rawsource, 384 | **attributes, 385 | ) 386 | 387 | def html(self): 388 | return ( 389 | '" 392 | ) 393 | 394 | 395 | class RepliteDirective(SphinxDirective): 396 | """The ``.. replite::`` directive. 397 | 398 | Adds a replite console to the docs. 399 | """ 400 | 401 | has_content = True 402 | required_arguments = 0 403 | option_spec = { 404 | "width": directives.unchanged, 405 | "height": directives.unchanged, 406 | "kernel": directives.unchanged, 407 | "execute": directives.unchanged, 408 | "toolbar": directives.unchanged, 409 | "theme": directives.unchanged, 410 | "prompt": directives.unchanged, 411 | "prompt_color": directives.unchanged, 412 | "search_params": directives.unchanged, 413 | "new_tab": directives.unchanged, 414 | "new_tab_button_text": directives.unchanged, 415 | } 416 | 417 | def run(self): 418 | width = self.options.pop("width", "100%") 419 | height = self.options.pop("height", "100%") 420 | 421 | prompt = self.options.pop("prompt", False) 422 | prompt_color = self.options.pop("prompt_color", None) 423 | 424 | search_params = search_params_parser(self.options.pop("search_params", False)) 425 | 426 | # We first check the global config, and then the per-directive 427 | # option. It defaults to True for backwards compatibility. 428 | execute = self.options.pop("execute", str(self.env.config.replite_auto_execute)) 429 | 430 | if execute not in ("True", "False"): 431 | raise ValueError("The :execute: option must be either True or False") 432 | 433 | if execute == "False": 434 | self.options["execute"] = "0" 435 | 436 | content = self.content 437 | 438 | button_text = None 439 | 440 | prefix = os.path.relpath( 441 | os.path.join(self.env.app.srcdir, JUPYTERLITE_DIR), 442 | os.path.dirname(self.get_source_info()[0]), 443 | ) 444 | 445 | new_tab = self.options.pop("new_tab", False) 446 | 447 | if new_tab: 448 | directive_button_text = self.options.pop("new_tab_button_text", None) 449 | if directive_button_text is not None: 450 | button_text = directive_button_text 451 | else: 452 | button_text = self.env.config.replite_new_tab_button_text 453 | return [ 454 | RepliteTab( 455 | prefix=prefix, 456 | width=width, 457 | height=height, 458 | prompt=prompt, 459 | prompt_color=prompt_color, 460 | content=content, 461 | search_params=search_params, 462 | lite_options=self.options, 463 | button_text=button_text, 464 | ) 465 | ] 466 | 467 | return [ 468 | RepliteIframe( 469 | prefix=prefix, 470 | width=width, 471 | height=height, 472 | prompt=prompt, 473 | prompt_color=prompt_color, 474 | content=content, 475 | search_params=search_params, 476 | lite_options=self.options, 477 | ) 478 | ] 479 | 480 | 481 | class _LiteDirective(SphinxDirective): 482 | has_content = False 483 | optional_arguments = 1 484 | final_argument_whitespace = True 485 | option_spec = { 486 | "width": directives.unchanged, 487 | "height": directives.unchanged, 488 | "theme": directives.unchanged, 489 | "prompt": directives.unchanged, 490 | "prompt_color": directives.unchanged, 491 | "search_params": directives.unchanged, 492 | "new_tab": directives.unchanged, 493 | "new_tab_button_text": directives.unchanged, 494 | } 495 | 496 | def _target_is_stale(self, source_path: Path, target_path: Path) -> bool: 497 | # Used as a heuristic to determine if a markdown notebook needs to be 498 | # converted or reconverted to ipynb. 499 | if not target_path.exists(): 500 | return True 501 | 502 | return source_path.stat().st_mtime > target_path.stat().st_mtime 503 | 504 | # TODO: Jupytext support many more formats for conversion, but we only 505 | # consider Markdown and IPyNB for now. If we add more formats someday, 506 | # we should also consider them here. 507 | def _assert_no_conflicting_nb_names( 508 | self, source_path: Path, notebooks_dir: Path 509 | ) -> None: 510 | """Check for duplicate notebook names in the documentation sources. 511 | Raises if any notebooks would conflict when converted to IPyNB.""" 512 | target_stem = source_path.stem 513 | target_ipynb = f"{target_stem}.ipynb" 514 | 515 | # Only look for conflicts in source directories and among referenced notebooks. 516 | # We do this to prevent conflicts with other files, say, in the "_contents/" 517 | # directory as a result of a previous failed/interrupted build. 518 | if source_path.parent != notebooks_dir: 519 | 520 | # We only consider conflicts if notebooks are actually referenced in 521 | # a directive, to prevent false posiitves from being raised. 522 | if hasattr(self.env, "jupyterlite_notebooks"): 523 | for existing_nb in self.env.jupyterlite_notebooks: 524 | existing_path = Path(existing_nb) 525 | if ( 526 | existing_path.stem == target_stem 527 | and existing_path != source_path 528 | ): 529 | 530 | raise RuntimeError( 531 | "All notebooks marked for inclusion with JupyterLite must have a " 532 | f"unique file basename. Found conflict between {source_path} and {existing_path}." 533 | ) 534 | 535 | return target_ipynb 536 | 537 | def _strip_notebook_cells( 538 | self, nb: nbformat.NotebookNode 539 | ) -> List[nbformat.NotebookNode]: 540 | """Strip cells based on the presence of the "jupyterlite_sphinx_strip" tag 541 | in the metadata. The content meant to be stripped must be inside its own cell 542 | cell so that the cell itself gets removed from the notebooks. This is so that 543 | we don't end up removing useful data or directives that are not meant to be 544 | removed. 545 | 546 | Parameters 547 | ---------- 548 | nb : nbformat.NotebookNode 549 | The notebook object to be stripped. 550 | 551 | Returns 552 | ------- 553 | List[nbformat.NotebookNode] 554 | A list of cells that are not meant to be stripped. 555 | """ 556 | return [ 557 | cell 558 | for cell in nb.cells 559 | if "jupyterlite_sphinx_strip" not in cell.metadata.get("tags", []) 560 | ] 561 | 562 | def run(self): 563 | width = self.options.pop("width", "100%") 564 | height = self.options.pop("height", "1000px") 565 | 566 | prompt = self.options.pop("prompt", False) 567 | prompt_color = self.options.pop("prompt_color", None) 568 | 569 | search_params = search_params_parser(self.options.pop("search_params", False)) 570 | 571 | new_tab = self.options.pop("new_tab", False) 572 | 573 | button_text = None 574 | 575 | source_location = os.path.dirname(self.get_source_info()[0]) 576 | 577 | prefix = os.path.relpath( 578 | os.path.join(self.env.app.srcdir, JUPYTERLITE_DIR), source_location 579 | ) 580 | 581 | if self.arguments: 582 | # Keep track of the notebooks we are going through, so that we don't 583 | # operate on notebooks that are not meant to be included in the built 584 | # docs, i.e., those that have not been referenced in the docs via our 585 | # directives anywhere. 586 | if not hasattr(self.env, "jupyterlite_notebooks"): 587 | self.env.jupyterlite_notebooks = set() 588 | 589 | # As with other directives like literalinclude, an absolute path is 590 | # assumed to be relative to the document root, and a relative path 591 | # is assumed to be relative to the source file 592 | rel_filename, notebook = self.env.relfn2path(self.arguments[0]) 593 | self.env.note_dependency(rel_filename) 594 | 595 | notebook_path = Path(notebook) 596 | 597 | self.env.jupyterlite_notebooks.add(str(notebook_path)) 598 | 599 | notebooks_dir = Path(self.env.app.srcdir) / CONTENT_DIR 600 | os.makedirs(notebooks_dir, exist_ok=True) 601 | 602 | self._assert_no_conflicting_nb_names(notebook_path, notebooks_dir) 603 | target_name = f"{notebook_path.stem}.ipynb" 604 | target_path = notebooks_dir / target_name 605 | 606 | notebook_is_stripped: bool = self.env.config.strip_tagged_cells 607 | 608 | if notebook_path.suffix.lower() == ".md": 609 | if self._target_is_stale(notebook_path, target_path): 610 | nb = jupytext.read(str(notebook_path)) 611 | if notebook_is_stripped: 612 | nb.cells = self._strip_notebook_cells(nb) 613 | with open(target_path, "w", encoding="utf-8") as f: 614 | nbformat.write(nb, f, version=4) 615 | 616 | notebook = str(target_path) 617 | notebook_name = target_name 618 | else: 619 | notebook_name = notebook_path.name 620 | target_path = notebooks_dir / notebook_name 621 | 622 | if notebook_is_stripped: 623 | nb = nbformat.read(notebook, as_version=4) 624 | nb.cells = self._strip_notebook_cells(nb) 625 | nbformat.write(nb, target_path, version=4) 626 | # If notebook_is_stripped is False, then copy the notebook(s) to notebooks_dir. 627 | # If it is True, then they have already been copied to notebooks_dir by the 628 | # nbformat.write() function above. 629 | else: 630 | try: 631 | shutil.copy(notebook, target_path) 632 | except shutil.SameFileError: 633 | pass 634 | 635 | else: 636 | notebook_name = None 637 | 638 | if new_tab: 639 | directive_button_text = self.options.pop("new_tab_button_text", None) 640 | if directive_button_text is not None: 641 | button_text = directive_button_text 642 | else: 643 | # If none, we use the appropriate global config based on 644 | # the type of directive passed. 645 | if isinstance(self, JupyterLiteDirective): 646 | button_text = self.env.config.jupyterlite_new_tab_button_text 647 | elif isinstance(self, NotebookLiteDirective): 648 | button_text = self.env.config.notebooklite_new_tab_button_text 649 | elif isinstance(self, VoiciDirective): 650 | button_text = self.env.config.voici_new_tab_button_text 651 | 652 | return [ 653 | self.newtab_cls( 654 | prefix=prefix, 655 | notebook=notebook_name, 656 | width=width, 657 | height=height, 658 | prompt=prompt, 659 | prompt_color=prompt_color, 660 | search_params=search_params, 661 | lite_options=self.options, 662 | button_text=button_text, 663 | ) 664 | ] 665 | 666 | return [ 667 | self.iframe_cls( 668 | prefix=prefix, 669 | notebook=notebook_name, 670 | width=width, 671 | height=height, 672 | prompt=prompt, 673 | prompt_color=prompt_color, 674 | search_params=search_params, 675 | lite_options=self.options, 676 | ) 677 | ] 678 | 679 | 680 | class BaseJupyterViewDirective(_LiteDirective): 681 | """Base class for jupyterlite-sphinx directives.""" 682 | 683 | iframe_cls = None # to be defined by subclasses 684 | newtab_cls = None # to be defined by subclasses 685 | 686 | option_spec = { 687 | "width": directives.unchanged, 688 | "height": directives.unchanged, 689 | "theme": directives.unchanged, 690 | "prompt": directives.unchanged, 691 | "prompt_color": directives.unchanged, 692 | "search_params": directives.unchanged, 693 | "new_tab": directives.unchanged, 694 | # "new_tab_button_text" below is useful only if "new_tab" is True, otherwise 695 | # we have "prompt" and "prompt_color" as options already. 696 | "new_tab_button_text": directives.unchanged, 697 | } 698 | 699 | 700 | class JupyterLiteDirective(BaseJupyterViewDirective): 701 | """The ``.. jupyterlite::`` directive. 702 | 703 | Renders a Notebook with JupyterLite in the docs. 704 | """ 705 | 706 | iframe_cls = JupyterLiteIframe 707 | newtab_cls = JupyterLiteTab 708 | 709 | 710 | class NotebookLiteDirective(BaseJupyterViewDirective): 711 | """The ``.. notebooklite::`` directive. 712 | 713 | Renders a Notebook with NotebookLite in the docs. 714 | """ 715 | 716 | iframe_cls = NotebookLiteIframe 717 | newtab_cls = NotebookLiteTab 718 | 719 | 720 | class VoiciDirective(BaseJupyterViewDirective): 721 | """The ``.. voici::`` directive. 722 | 723 | Renders a Notebook with Voici in the docs. 724 | """ 725 | 726 | iframe_cls = VoiciIframe 727 | newtab_cls = VoiciTab 728 | 729 | def run(self): 730 | if voici is None: 731 | raise RuntimeError( 732 | "Voici must be installed if you want to make use of the voici directive: pip install voici" 733 | ) 734 | 735 | return super().run() 736 | 737 | 738 | class NotebookLiteParser(RSTParser): 739 | """Sphinx source parser for Jupyter notebooks. 740 | 741 | Shows the Notebook using notebooklite.""" 742 | 743 | supported = ("jupyterlite_notebook",) 744 | 745 | def parse(self, inputstring, document): 746 | title = os.path.splitext(os.path.basename(document.current_source))[0] 747 | # Make the "absolute" filename relative to the source root 748 | filename = "/" + os.path.relpath(document.current_source, self.env.app.srcdir) 749 | super().parse( 750 | f"{title}\n{'=' * len(title)}\n.. notebooklite:: {filename}", 751 | document, 752 | ) 753 | 754 | 755 | class TryExamplesDirective(SphinxDirective): 756 | """Add button to try doctest examples in Jupyterlite notebook.""" 757 | 758 | has_content = True 759 | required_arguments = 0 760 | option_spec = { 761 | "height": directives.unchanged, 762 | "theme": directives.unchanged, 763 | "button_text": directives.unchanged, 764 | "example_class": directives.unchanged, 765 | "warning_text": directives.unchanged, 766 | } 767 | 768 | def run(self): 769 | if "generated_notebooks" not in self.env.temp_data: 770 | self.env.temp_data["generated_notebooks"] = {} 771 | 772 | directive_key = f"{self.env.docname}-{self.lineno}" 773 | notebook_unique_name = self.env.temp_data["generated_notebooks"].get( 774 | directive_key 775 | ) 776 | 777 | # Use global configuration values from conf.py in manually inserted directives 778 | # if they are provided and the user has not specified a config value in the 779 | # directive itself. 780 | 781 | default_button_text = self.env.config.try_examples_global_button_text 782 | if default_button_text is None: 783 | default_button_text = "Try it with JupyterLite!" 784 | button_text = self.options.pop("button_text", default_button_text) 785 | 786 | default_warning_text = self.env.config.try_examples_global_warning_text 787 | warning_text = self.options.pop("warning_text", default_warning_text) 788 | 789 | default_example_class = self.env.config.try_examples_global_theme 790 | if default_example_class is None: 791 | default_example_class = "" 792 | example_class = self.options.pop("example_class", default_example_class) 793 | 794 | # A global height cannot be set in conf.py 795 | height = self.options.pop("height", None) 796 | 797 | # We need to get the relative path back to the documentation root from 798 | # whichever file the docstring content is in. 799 | docname = self.env.docname 800 | depth = len(docname.split("/")) - 1 801 | relative_path_to_root = "/".join([".."] * depth) 802 | prefix = os.path.join(relative_path_to_root, JUPYTERLITE_DIR) 803 | 804 | lite_app = "tree/" 805 | notebooks_path = "../notebooks/" 806 | 807 | content_container_node = nodes.container( 808 | classes=["try_examples_outer_container", example_class] 809 | ) 810 | examples_div_id = uuid4() 811 | content_container_node["ids"].append(examples_div_id) 812 | # Parse the original content to create nodes 813 | content_node = nodes.container() 814 | content_node["classes"].append("try_examples_content") 815 | self.state.nested_parse(self.content, self.content_offset, content_node) 816 | 817 | if notebook_unique_name is None: 818 | nb = examples_to_notebook(self.content, warning_text=warning_text) 819 | self.content = None 820 | notebooks_dir = Path(self.env.app.srcdir) / CONTENT_DIR 821 | notebook_unique_name = f"{uuid4()}.ipynb".replace("-", "_") 822 | self.env.temp_data["generated_notebooks"][ 823 | directive_key 824 | ] = notebook_unique_name 825 | # Copy the Notebook for NotebookLite to find 826 | os.makedirs(notebooks_dir, exist_ok=True) 827 | with open( 828 | notebooks_dir / Path(notebook_unique_name), "w", encoding="utf-8" 829 | ) as f: 830 | # nbf.write incorrectly formats multiline arrays in output. 831 | json.dump(nb, f, indent=4, ensure_ascii=False) 832 | 833 | self.options["path"] = notebook_unique_name 834 | app_path = f"{lite_app}{notebooks_path}" 835 | options = "&".join( 836 | [f"{key}={quote(value)}" for key, value in self.options.items()] 837 | ) 838 | 839 | iframe_parent_div_id = uuid4() 840 | iframe_div_id = uuid4() 841 | iframe_src = f'{prefix}/{app_path}{f"index.html?{options}" if options else ""}' 842 | 843 | # Parent container (initially hidden) 844 | iframe_parent_container_div_start = ( 845 | f'
' 847 | ) 848 | 849 | iframe_parent_container_div_end = "
" 850 | iframe_container_div = ( 851 | f'
' 853 | f"
" 854 | ) 855 | 856 | # Button with the onclick event to swap embedded notebook back to examples. 857 | go_back_button_html = ( 858 | '" 862 | ) 863 | 864 | full_screen_button_html = ( 865 | '" 869 | ) 870 | 871 | # Button with the onclick event to swap examples with embedded notebook. 872 | try_it_button_html = ( 873 | '
' 874 | '" 879 | "
" 880 | ) 881 | try_it_button_node = nodes.raw("", try_it_button_html, format="html") 882 | 883 | # Combine everything 884 | notebook_container_html = ( 885 | iframe_parent_container_div_start 886 | + '
' 887 | + go_back_button_html 888 | + full_screen_button_html 889 | + "
" 890 | + iframe_container_div 891 | + iframe_parent_container_div_end 892 | ) 893 | content_container_node += try_it_button_node 894 | content_container_node += content_node 895 | 896 | notebook_container = nodes.raw("", notebook_container_html, format="html") 897 | 898 | # Search config file allowing for config changes without rebuilding docs. 899 | config_path = os.path.join(relative_path_to_root, "try_examples.json") 900 | script_html = ( 901 | "" 906 | ) 907 | script_node = nodes.raw("", script_html, format="html") 908 | 909 | return [content_container_node, notebook_container, script_node] 910 | 911 | 912 | def _process_docstring_examples(app: Sphinx, docname: str, source: List[str]) -> None: 913 | source_path: os.PathLike = Path(app.env.doc2path(docname)) 914 | if source_path.suffix == ".py": 915 | source[0] = insert_try_examples_directive(source[0]) 916 | 917 | 918 | def _process_autodoc_docstrings(app, what, name, obj, options, lines): 919 | try_examples_options = { 920 | "theme": app.config.try_examples_global_theme, 921 | "button_text": app.config.try_examples_global_button_text, 922 | "warning_text": app.config.try_examples_global_warning_text, 923 | } 924 | try_examples_options = { 925 | key: value for key, value in try_examples_options.items() if value is not None 926 | } 927 | modified_lines = insert_try_examples_directive(lines, **try_examples_options) 928 | lines.clear() 929 | lines.extend(modified_lines) 930 | 931 | 932 | def conditional_process_examples(app, config): 933 | if config.global_enable_try_examples: 934 | app.connect("source-read", _process_docstring_examples) 935 | app.connect("autodoc-process-docstring", _process_autodoc_docstrings) 936 | 937 | 938 | def inited(app: Sphinx, config): 939 | # Create the content dir 940 | os.makedirs(os.path.join(app.srcdir, CONTENT_DIR), exist_ok=True) 941 | 942 | if ( 943 | config.jupyterlite_bind_ipynb_suffix 944 | and ".ipynb" not in config.source_suffix 945 | and ".ipynb" not in app.registry.source_suffix 946 | ): 947 | app.add_source_suffix(".ipynb", "jupyterlite_notebook") 948 | 949 | 950 | def jupyterlite_build(app: Sphinx, error): 951 | if error is not None: 952 | # Do not build JupyterLite 953 | return 954 | 955 | if app.builder.format == "html": 956 | print("[jupyterlite-sphinx] Running JupyterLite build") 957 | jupyterlite_config = app.env.config.jupyterlite_config 958 | jupyterlite_overrides = app.env.config.jupyterlite_overrides 959 | jupyterlite_contents = app.env.config.jupyterlite_contents 960 | 961 | jupyterlite_dir = str(app.env.config.jupyterlite_dir) 962 | 963 | jupyterlite_build_command_options: Dict[str, Any] = ( 964 | app.env.config.jupyterlite_build_command_options 965 | ) 966 | 967 | config = [] 968 | overrides = [] 969 | if jupyterlite_config: 970 | config = ["--config", jupyterlite_config] 971 | 972 | if jupyterlite_overrides: 973 | # JupyterLite's build command does not validate the existence 974 | # of the JSON file, so we do it ourselves. 975 | # We will raise a FileNotFoundError if the file does not exist 976 | # in the Sphinx project directory. 977 | overrides_path = Path(app.srcdir) / jupyterlite_overrides 978 | if not Path(overrides_path).exists(): 979 | raise FileNotFoundError( 980 | f"Overrides file {overrides_path} does not exist. " 981 | "Please check your configuration." 982 | ) 983 | 984 | overrides = ["--settings-overrides", jupyterlite_overrides] 985 | 986 | if jupyterlite_contents is None: 987 | jupyterlite_contents = [] 988 | elif isinstance(jupyterlite_contents, str): 989 | jupyterlite_contents = [jupyterlite_contents] 990 | 991 | # Expand globs in the contents strings 992 | contents = [] 993 | for pattern in jupyterlite_contents: 994 | pattern_path = Path(pattern) 995 | 996 | base_path = ( 997 | pattern_path.parent 998 | if pattern_path.is_absolute() 999 | else Path(app.srcdir) / pattern_path.parent 1000 | ) 1001 | glob_pattern = pattern_path.name 1002 | 1003 | matched_paths = base_path.glob(glob_pattern) 1004 | 1005 | for matched_path in matched_paths: 1006 | # If the matched path is absolute, we keep it as is, and 1007 | # if it is relative, we convert it to a path relative to 1008 | # the documentation source directory. 1009 | contents_path = ( 1010 | str(matched_path) 1011 | if matched_path.is_absolute() 1012 | else str(matched_path.relative_to(app.srcdir)) 1013 | ) 1014 | 1015 | contents.extend(["--contents", contents_path]) 1016 | 1017 | apps_option = [] 1018 | for liteapp in ["notebooks", "edit", "lab", "repl", "tree", "consoles"]: 1019 | apps_option.extend(["--apps", liteapp]) 1020 | if voici is not None: 1021 | apps_option.extend(["--apps", "voici"]) 1022 | 1023 | command = [ 1024 | sys.executable, 1025 | "-m", 1026 | "jupyter", 1027 | "lite", 1028 | "build", 1029 | "--debug", 1030 | *config, 1031 | *overrides, 1032 | *contents, 1033 | "--contents", 1034 | os.path.join(app.srcdir, CONTENT_DIR), 1035 | "--output-dir", 1036 | os.path.join(app.outdir, JUPYTERLITE_DIR), 1037 | *apps_option, 1038 | "--lite-dir", 1039 | jupyterlite_dir, 1040 | ] 1041 | 1042 | if jupyterlite_build_command_options is not None: 1043 | for key, value in jupyterlite_build_command_options.items(): 1044 | # Check for conflicting options from the default command we use 1045 | # while building. We don't want to allow these to be overridden 1046 | # unless they are explicitly set through Sphinx config. 1047 | if key in ["contents", "output-dir", "lite-dir"]: 1048 | jupyterlite_command_error_message = f""" 1049 | Additional option, {key}, passed to `jupyter lite build` through 1050 | `jupyterlite_build_command_options` in conf.py is already an existing 1051 | option. "contents", "output_dir", and "lite_dir" can be configured in 1052 | conf.py as described in the jupyterlite-sphinx documentation: 1053 | https://jupyterlite-sphinx.readthedocs.io/en/stable/configuration.html 1054 | """ 1055 | raise RuntimeError(jupyterlite_command_error_message) 1056 | command.extend([f"--{key}", str(value)]) 1057 | 1058 | assert all( 1059 | [isinstance(s, str) for s in command] 1060 | ), f"Expected all commands arguments to be a str, got {command}" 1061 | 1062 | kwargs: Dict[str, Any] = {} 1063 | if app.env.config.jupyterlite_silence: 1064 | kwargs["stdout"] = subprocess.PIPE 1065 | kwargs["stderr"] = subprocess.PIPE 1066 | 1067 | completed_process: CompletedProcess[bytes] = subprocess.run( 1068 | command, cwd=app.srcdir, check=True, **kwargs 1069 | ) 1070 | 1071 | if completed_process.returncode != 0: 1072 | if app.env.config.jupyterlite_silence: 1073 | print( 1074 | "`jupyterlite build` failed but its output has been silenced." 1075 | " stdout and stderr are reproduced below.\n" 1076 | ) 1077 | print("stdout:", completed_process.stdout.decode()) 1078 | print("stderr:", completed_process.stderr.decode()) 1079 | 1080 | # Raise the original exception that would have occurred with check=True 1081 | raise subprocess.CalledProcessError( 1082 | returncode=completed_process.returncode, 1083 | cmd=command, 1084 | output=completed_process.stdout, 1085 | stderr=completed_process.stderr, 1086 | ) 1087 | 1088 | print("[jupyterlite-sphinx] JupyterLite build done") 1089 | 1090 | # Cleanup 1091 | try: 1092 | shutil.rmtree(os.path.join(app.srcdir, CONTENT_DIR)) 1093 | os.remove(".jupyterlite.doit.db") 1094 | except FileNotFoundError: 1095 | pass 1096 | 1097 | 1098 | def setup(app): 1099 | # Initialize NotebookLite parser 1100 | app.add_source_parser(NotebookLiteParser) 1101 | 1102 | app.connect("config-inited", inited) 1103 | # We need to build JupyterLite at the end, when all the content was created 1104 | app.connect("build-finished", jupyterlite_build) 1105 | 1106 | # Config options 1107 | app.add_config_value("jupyterlite_config", None, rebuild="html") 1108 | app.add_config_value("jupyterlite_overrides", None, rebuild="html") 1109 | app.add_config_value("jupyterlite_dir", str(app.srcdir), rebuild="html") 1110 | app.add_config_value("jupyterlite_contents", None, rebuild="html") 1111 | app.add_config_value("jupyterlite_bind_ipynb_suffix", True, rebuild="html") 1112 | app.add_config_value("jupyterlite_silence", True, rebuild=True) 1113 | app.add_config_value("strip_tagged_cells", False, rebuild=True) 1114 | 1115 | # Pass a dictionary of additional options to the JupyterLite build command 1116 | app.add_config_value("jupyterlite_build_command_options", None, rebuild="html") 1117 | 1118 | app.add_config_value("global_enable_try_examples", default=False, rebuild=True) 1119 | app.add_config_value("try_examples_global_theme", default=None, rebuild=True) 1120 | app.add_config_value("try_examples_global_warning_text", default=None, rebuild=True) 1121 | app.add_config_value( 1122 | "try_examples_global_button_text", 1123 | default=None, 1124 | rebuild="html", 1125 | ) 1126 | 1127 | # Allow customising the button text for each directive (this is useful 1128 | # only when "new_tab" is set to True) 1129 | app.add_config_value( 1130 | "jupyterlite_new_tab_button_text", "Open as a notebook", rebuild="html" 1131 | ) 1132 | app.add_config_value( 1133 | "notebooklite_new_tab_button_text", "Open as a notebook", rebuild="html" 1134 | ) 1135 | app.add_config_value("voici_new_tab_button_text", "Open with Voici", rebuild="html") 1136 | app.add_config_value( 1137 | "replite_new_tab_button_text", "Open in a REPL", rebuild="html" 1138 | ) 1139 | 1140 | # Initialize NotebookLite and JupyterLite directives 1141 | app.add_node( 1142 | NotebookLiteIframe, 1143 | html=(visit_element_html, None), 1144 | latex=(skip, None), 1145 | textinfo=(skip, None), 1146 | text=(skip, None), 1147 | man=(skip, None), 1148 | ) 1149 | app.add_directive("notebooklite", NotebookLiteDirective) 1150 | # For backward compatibility 1151 | app.add_directive("retrolite", NotebookLiteDirective) 1152 | app.add_node( 1153 | JupyterLiteIframe, 1154 | html=(visit_element_html, None), 1155 | latex=(skip, None), 1156 | textinfo=(skip, None), 1157 | text=(skip, None), 1158 | man=(skip, None), 1159 | ) 1160 | for node_class in [NotebookLiteTab, JupyterLiteTab]: 1161 | app.add_node( 1162 | node_class, 1163 | html=(visit_element_html, None), 1164 | latex=(skip, None), 1165 | textinfo=(skip, None), 1166 | text=(skip, None), 1167 | man=(skip, None), 1168 | ) 1169 | app.add_directive("jupyterlite", JupyterLiteDirective) 1170 | 1171 | # Initialize Replite directive and tab 1172 | app.add_node( 1173 | RepliteIframe, 1174 | html=(visit_element_html, None), 1175 | latex=(skip, None), 1176 | textinfo=(skip, None), 1177 | text=(skip, None), 1178 | man=(skip, None), 1179 | ) 1180 | app.add_node( 1181 | RepliteTab, 1182 | html=(visit_element_html, None), 1183 | latex=(skip, None), 1184 | textinfo=(skip, None), 1185 | text=(skip, None), 1186 | man=(skip, None), 1187 | ) 1188 | app.add_directive("replite", RepliteDirective) 1189 | app.add_config_value("replite_auto_execute", True, rebuild="html") 1190 | 1191 | # Initialize Voici directive and tabbed interface 1192 | app.add_node( 1193 | VoiciIframe, 1194 | html=(visit_element_html, None), 1195 | latex=(skip, None), 1196 | textinfo=(skip, None), 1197 | text=(skip, None), 1198 | man=(skip, None), 1199 | ) 1200 | app.add_node( 1201 | VoiciTab, 1202 | html=(visit_element_html, None), 1203 | latex=(skip, None), 1204 | textinfo=(skip, None), 1205 | text=(skip, None), 1206 | man=(skip, None), 1207 | ) 1208 | app.add_directive("voici", VoiciDirective) 1209 | 1210 | # Initialize TryExamples directive 1211 | app.add_directive("try_examples", TryExamplesDirective) 1212 | app.connect("config-inited", conditional_process_examples) 1213 | 1214 | # CSS and JS assets 1215 | copy_asset(str(HERE / "jupyterlite_sphinx.css"), str(Path(app.outdir) / "_static")) 1216 | copy_asset(str(HERE / "jupyterlite_sphinx.js"), str(Path(app.outdir) / "_static")) 1217 | 1218 | app.add_css_file("https://fonts.googleapis.com/css?family=Vibur") 1219 | app.add_css_file("jupyterlite_sphinx.css") 1220 | 1221 | app.add_js_file("jupyterlite_sphinx.js") 1222 | 1223 | # Copy optional try examples runtime config if it exists. 1224 | try_examples_config_path = Path(app.srcdir) / "try_examples.json" 1225 | if try_examples_config_path.exists(): 1226 | copy_asset(str(try_examples_config_path), app.outdir) 1227 | 1228 | return {"parallel_read_safe": True} 1229 | 1230 | 1231 | def search_params_parser(search_params: str) -> str: 1232 | pattern = re.compile(r"^\[(?:\s*[\"']{1}([^=\s\,&=\?\/]+)[\"']{1}\s*\,?)+\]$") 1233 | if not search_params: 1234 | return "false" 1235 | if search_params in ["True", "False"]: 1236 | return search_params.lower() 1237 | elif pattern.match(search_params): 1238 | return search_params.replace('"', "'") 1239 | else: 1240 | raise ValueError( 1241 | 'The search_params directive must be either True, False or ["param1", "param2"].\n' 1242 | 'The params name shouldn\'t contain any of the following characters ["\\", "\'", """, ",", "?", "=", "&", " ").' 1243 | ) 1244 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "jupyterlite-sphinx" 7 | dynamic = ["version"] 8 | description = "Sphinx extension for deploying JupyterLite" 9 | readme = "README.md" 10 | license = { file = "LICENSE" } 11 | requires-python = ">=3.9" 12 | authors = [ 13 | { name = "JupyterLite Contributors" }, 14 | ] 15 | dependencies = [ 16 | "docutils", 17 | "jupyter_server", 18 | "jupyterlab_server", 19 | "jupyterlite-core >=0.2,<0.7", 20 | "jupytext", 21 | "nbformat", 22 | "sphinx>=4", 23 | ] 24 | 25 | [project.optional-dependencies] 26 | dev = [ 27 | "hatch", 28 | ] 29 | 30 | docs = [ 31 | "myst_parser", 32 | "pydata-sphinx-theme", 33 | "jupyterlite-xeus>=0.1.8,<4", 34 | ] 35 | 36 | [tool.hatch.version] 37 | path = "jupyterlite_sphinx/__init__.py" 38 | 39 | [tool.hatch.build.targets.sdist] 40 | include = [ 41 | "/jupyterlite_sphinx", 42 | ] 43 | 44 | [tool.hatch.envs.docs] 45 | features = ["docs"] 46 | [tool.hatch.envs.docs.scripts] 47 | build = "sphinx-build -W -b html docs docs/build/html" 48 | serve = "python -m http.server --directory docs/build/html" 49 | 50 | [[tool.mypy.overrides]] 51 | module = [ 52 | "voici", 53 | ] 54 | ignore_missing_imports = true 55 | --------------------------------------------------------------------------------