diff --git a/.github/test-environment.yml b/.github/test-environment.yml new file mode 100644 index 00000000..3ff5f8b6 --- /dev/null +++ b/.github/test-environment.yml @@ -0,0 +1,47 @@ +name: test-nbconvert-a11y +channels: + - conda-forge + - microsoft + - nodefaults +dependencies: + # run + - accessible-pygments + - exceptiongroup + - html5lib + - nbconvert + - python-slugify + - mdit-py-plugins + - linkify-it-py >=1,<3 + - markdown-it-py + # runtimes + - python =3.11 + - openjdk + - nodejs =20 + # build + - doit + - hatch + - hatch-vcs + - pip + - python-build + - yarn =3.6 + # audit + - vnu-validator + # lint + - ruff + # deps + - beautifulsoup4 + - ipywidgets + - matplotlib-base + - mkdocs-material + - mkdocstrings + - numpy + - playwright + - pytest + - pytest-html + - pytest-playwright + - pytest-sugar + - pytest-xdist + - requests-cache + # report + - scipy + - pandas diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 0234e39f..6f395eb2 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -1,7 +1,7 @@ -on: +on: pull_request: types: - - opened + - opened jobs: comment: @@ -15,4 +15,4 @@ jobs: with: message: | A preview fo the exported examples will be rendered at - https://iota-school.github.io/notebooks-for-all/branch/${{ github.event.pull_request.head.ref }}/exports/html \ No newline at end of file + https://${{ github.repository_owner }}.github.io/nbconvert-a11y/branch/${{ github.event.pull_request.head.ref }}/exports/html/lorenz-executed-a11y.html diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fbff16ef..f6af11b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,157 +1,169 @@ -name: pytest nbconvert-a11y, axe test exports, build docs. -on: - - push -jobs: - format: - name: format - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-python@v4 - with: - python-version: "3.12" - cache: pip - cache-dependency-path: pyproject.toml - - name: install dev dependencies - run: python -m pip install --upgrade pip hatch - - name: run formatters - run: | - echo "~~~bash" > "${GITHUB_STEP_SUMMARY}" - hatch run format:code 2>&1 | tee --append "${GITHUB_STEP_SUMMARY}" - echo "~~~" >> "${GITHUB_STEP_SUMMARY}" - - name: print diff - run: | - echo "~~~diff" >> "${GITHUB_STEP_SUMMARY}" - git diff | tee --append "${GITHUB_STEP_SUMMARY}" - echo "~~~" >> "${GITHUB_STEP_SUMMARY}" - test: - name: test package and accessibility - defaults: - run: - shell: bash -el {0} - strategy: - matrix: - python-version: - - "3.10" - runs-on: ubuntu-latest - steps: - - name: fetch all history and tags - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Cache conda - uses: actions/cache@v2 - env: - # Increase this value to reset cache if etc/example-environment.yml has not changed - CACHE_NUMBER: 1 - with: - path: ~/conda_pkgs_dir - key: ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ - hashFiles('test-environment.yml') }} - - uses: mamba-org/setup-micromamba@v1 - with: - environment-file: test-environment.yml - cache-environment: true - - name: init plawright - run: | - playwright install --with-deps chromium - - name: init node & files - run: | - npm install vnu-jar axe-core - doit copy - - name: init dev module - run: | - pip install -e. - - name: smoke test - run: | - # the smoke generate html assets that are used in the accessibility testing. - # we run this script to generate assets and test the nbconvert-a11y module. - # failures here will stop any docs builds - pytest tests/test_smoke.py - - name: a11y tests - # always build the docs to see what the new versions look like. - continue-on-error: true - run: | - pytest --deselect tests/test_smoke.py \ - -n auto --self-contained-html --html=tests/exports/pytest/report.html - - name: build wheel and sdist - run: | - python -m build - - uses: actions/upload-artifact@v3 - with: - name: dist - path: dist - - name: mkdocs - run: | - mkdocs build -v - - uses: actions/upload-artifact@v3 - with: - name: site - path: site - publish: - name: publish the mkdocs build to github pages - needs: [test] - runs-on: ubuntu-latest - steps: - - name: checkout repo - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 - with: - name: site - path: site - - name: Deploy main 🚀 - uses: JamesIves/github-pages-deploy-action@v4 - if: ${{ github.ref_name == 'main' }} - with: - folder: site # The folder the action should deploy. - single-commit: true - - name: Deploy non-main 🚀 - uses: JamesIves/github-pages-deploy-action@v4 - if: ${{ github.ref_name != 'main' }} - with: - folder: site # The folder the action should deploy. - single-commit: true - target-folder: branch/${{ github.ref_name }} - release: - name: draft release when tagged - if: startsWith(github.ref, 'refs/tags/') - needs: [test] - runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - steps: - - name: fetch contents - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 - with: - name: dist - path: dist - - uses: actions/setup-python@v4 - with: - python-version: "3.11" - cache: pip - cache-dependency-path: pyproject.toml - - name: install twine and pytest - run: | - pip install twine pytest - - name: Publish package distributions to TestPyPI - run: | - twine upload --repository testpypi \ - --user __token__ --password ${{secrets.HATCH_TEST_INDEX_AUTH}} \ - dist/* - - name: install nbconvert-a11y dependencies from test pip - run: | - pip install \ - --index-url 'https://test.pypi.org/simple/' \ - --extra-index-url 'https://pypi.org/simple/' \ - nbconvert-a11y - - name: test test release - run: | - pytest tests/test_smoke.py - - uses: ncipollo/release-action@v1 - with: - artifacts: "dist/.*" - draft: true # does not trigger a created event \ No newline at end of file +name: pytest nbconvert-a11y, axe test exports, build docs. + +on: + push: + branches: [main] + pull_request: + branches: ['*'] + workflow_dispatch: + +env: + # Increase this value to reset cache if environments have not changed + CACHE_NUMBER: 2 + # squash some known warnings + JUPYTER_PLATFORM_DIRS: 1 + +jobs: + format: + name: format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml + - name: install dev dependencies + run: python -m pip install --upgrade pip hatch + - name: run formatters + run: | + echo "~~~bash" > "${GITHUB_STEP_SUMMARY}" + hatch run format:code 2>&1 | tee --append "${GITHUB_STEP_SUMMARY}" + echo "~~~" >> "${GITHUB_STEP_SUMMARY}" + - name: print diff + run: | + echo "~~~diff" >> "${GITHUB_STEP_SUMMARY}" + git diff | tee --append "${GITHUB_STEP_SUMMARY}" + echo "~~~" >> "${GITHUB_STEP_SUMMARY}" + + test: + name: test package and accessibility + defaults: + run: + shell: bash -el {0} + strategy: + matrix: + python-version: + - "3.10" + runs-on: ubuntu-latest + steps: + - name: fetch all history and tags + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: mamba-org/setup-micromamba@v1 + with: + environment-file: .github/test-environment.yml + - name: init plawright + run: | + playwright install --with-deps chromium + - name: init node & files + run: | + yarn + doit copy + - name: init dev module + run: | + python3 -m pip install -e . --no-deps --no-build-isolation --ignore-installed + - name: check pip env + run: | + python3 -m pip check + - name: smoke test + run: | + # the smoke generate html assets that are used in the accessibility testing. + # we run this script to generate assets and test the nbconvert-a11y module. + # failures here will stop any docs builds + pytest --color=yes tests/test_smoke.py + - name: build wheel and sdist + run: | + pyproject-build + - uses: actions/upload-artifact@v3 + with: + name: dist + path: dist + - name: mkdocs + run: | + mkdocs build -v + - uses: actions/upload-artifact@v3 + with: + name: site + path: site + - name: a11y tests + # always build the docs to see what the new versions look like. + # continue-on-error: true + run: | + pytest \ + --color=yes \ + -n auto \ + --deselect tests/test_smoke.py \ + --self-contained-html \ + --html=tests/exports/pytest/report.html + + publish: + name: publish the mkdocs build to github pages + needs: [test] + runs-on: ubuntu-latest + steps: + - name: checkout repo + uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: site + path: site + - name: Deploy main 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + if: ${{ github.ref_name == 'main' }} + with: + folder: site # The folder the action should deploy. + single-commit: true + - name: Deploy non-main 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + if: ${{ github.ref_name != 'main' }} + with: + folder: site # The folder the action should deploy. + single-commit: true + target-folder: branch/${{ github.ref_name }} + + release: + name: draft release when tagged + if: startsWith(github.ref, 'refs/tags/') + needs: [test] + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + steps: + - name: fetch contents + uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: dist + path: dist + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + cache: pip + cache-dependency-path: pyproject.toml + - name: install twine and pytest + run: | + pip install twine pytest + - name: Publish package distributions to TestPyPI + run: | + twine upload --repository testpypi \ + --user __token__ --password ${{secrets.HATCH_TEST_INDEX_AUTH}} \ + dist/* + - name: install nbconvert-a11y dependencies from test pip + run: | + pip install \ + --index-url 'https://test.pypi.org/simple/' \ + --extra-index-url 'https://pypi.org/simple/' \ + nbconvert-a11y + - name: test test release + run: | + pytest tests/test_smoke.py + - uses: ncipollo/release-action@v1 + with: + artifacts: "dist/.*" + draft: true # does not trigger a created event diff --git a/.gitignore b/.gitignore index 1a6f1b33..fb0c4fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,23 @@ -docs -site -tests/exports -nbconvert_a11y/templates/a11y/light-code.css -nbconvert_a11y/templates/a11y/dark-code.css - -*~ -*# +__pycache__ .#* -*.pyc +.doit.* .python-version -nbconvert_a11y.egg-info -node_modules -__pycache__ -.doit.db.* -nbconvert_a11y/_version.py -tests/outputs/*.html -build/* -tests/out.html *-checkpoint* +*.pyc +*# +*~ +build/* +docs docs/**/*.html docs/**/*.json -settings.json +nbconvert_a11y.egg-info +nbconvert_a11y/_version.py nbconvert_a11y/templates/a11y/axe.js +nbconvert_a11y/templates/a11y/dark-code.css +nbconvert_a11y/templates/a11y/light-code.css +node_modules +settings.json +site +tests/exports +tests/out.html +tests/outputs/*.html diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 00000000..2306181d --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1,25 @@ +enableImmutableInstalls: false +enableInlineBuilds: false +enableTelemetry: false +httpTimeout: 60000 +nodeLinker: node-modules +npmRegistryServer: https://registry.npmjs.org/ +installStatePath: ./build/.cache/yarn/install-state.gz +globalFolder: ./build/.cache/yarn +cacheFolder: ./build/.cache/yarn +# these messages provide no actionable information, and make non-TTY output +# almost unreadable, masking real dependency-related information +# see: https://yarnpkg.com/advanced/error-codes +logFilters: + - code: YN0006 # SOFT_LINK_BUILD + level: discard + - code: YN0007 # MUST_BUILD + level: discard + - code: YN0008 # MUST_REBUILD + level: discard + - code: YN0013 # FETCH_NOT_CACHED + level: discard + - code: YN0019 # UNUSED_CACHE_ENTRY + level: discard + - code: YN0002 # BOO_PEER_DEPS_LIKE_REACT + level: discard diff --git a/README.md b/README.md index 67a59580..2243aa6f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,32 @@ -# Astronomy Notebooks for All -Jupyter Notebooks play a central role in modern data science workflows. Despite their importance, these notebooks are inaccessible to people with disabilities, especially those who rely on assistive technology. Impacted users must find extreme workarounds or [give up using them entirely.](https://www.freelists.org/post/program-l/Accessability-of-Jupyter-notebooks) Students with disabilities have [reported leaving their field](https://github.com/jupyterlab/jupyterlab/issues/9399#issuecomment-740524422) once they learn their chosen career’s foundational tools are inaccessible to them. +# `nbconvert-a11y` -This is a challenging problem to solve. The Notebooks for All project is taking the first steps, initially focusing on static notebooks: -- Running usability feedback sessions with impacted users who rely on a variety of assistive technology -- Capturing what makes notebooks inaccessible with assistive technology, and compiling documents that describe the issues and feedback -- Editing notebooks based on the feedback -- Organizing events to spread awareness in the scientific community about this issue +`nbconvert-a11y` contains templates for accessible notebook representations and accessibility tests for Jupyter notebook products. -## Collaborators -[Space Telescope Science Institute](https://www.stsci.edu/) produces extensive community resources and infrastructure in Jupyter. The Institute has committed to fostering an inclusive environment and has funded this project in 2022-2023 as part of the Director’s Discretionary Fund. Other collaborators include community contributions and work from STEM- and accessibility-focused organizations such as [Iota School](https://iotaschool.com/) and [Quansight Labs](https://www.quansight.com/labs). +```bash +pip install nbconvert-a11y +``` -## Resources -[A Curated List of STScI notebooks](https://github.com/spacetelescope/notebooks) -[Accessibility Analysis of Jupyter Notebook HTML Output](https://www.youtube.com/watch?v=KsUF_HjA97U&t=253s) -[Astronomy Notebooks For All full proposal](resources/proposal-astronomy-notebooks-for-all.md) +`nbconvert-a11y` can be used with the [`nbconvert` command line tool](https://nbconvert.readthedocs.io/en/latest/usage.html). +it provides the `a11y` exporter with several variants that can be used. the default theme uses a flexible table representation + +```bash +jupyter nbconvert --to a11y Untitled.ipynb # flexible table navigation +jupyter nbconvert --to a11y-table Untitled.ipynb # a11y is an alias for a11y-table +jupyter nbconvert --to a11y-landmark Untitled.ipynb # cells are section landmarks +jupyter nbconvert --to a11y-list Untitled.ipynb # cells are list items +``` + +```python +from nbconvert_a11y.exporter import A11y, Table, Section, List +``` + +A an example of the canonical Lorenz differential differential equations can be viewed @ https://deathbeds.github.io/nbconvert-a11y/exports/html/lorenz-executed-a11y.html + +## History + +the `nbconvert-a11y` project is forked from initial development in the [`notebook-for-all`]() repository. +this collaboration between [Space Telescope Science Institute](https://www.stsci.edu/), [Iota School](https://iotaschool.com/) and [Quansight Labs](https://www.quansight.com/labs) +brought input from blind and visual impaired notebook users as to what their most assistive experiences could be. ## License -This repository hosts mixed content types. Suitable licenses apply to each type. All of the repository except the `[user-tests](user-tests)` directory are under a [3-Clause BSD license](LICENSE). All content in the `[user-tests](user-tests)` directory is under a [CC-BY license](https://creativecommons.org/licenses/by/4.0/). +Licensed under a [3-Clause BSD license](LICENSE). diff --git a/convert.py b/convert.py deleted file mode 100644 index 696ac10a..00000000 --- a/convert.py +++ /dev/null @@ -1,9 +0,0 @@ -from nbconvert import get_exporter - -exporter = get_exporter("html5") - -out = exporter().from_filename("tests/notebooks/lorenz.ipynb") - - -with open("tests/out.html", "w") as out_file: - out_file.write(str(out[0])) diff --git a/dodo.py b/dodo.py index e8292970..5681c4e8 100644 --- a/dodo.py +++ b/dodo.py @@ -133,18 +133,18 @@ def readme(target, ext, title): } # this is missing the file_deps and targets # they can be computed - for d in "user-tests resources".split(): - DIR = Path(d) - files = [x for x in DIR.rglob("*") if x.is_file()] - targets = [EXPORTS / x for x in files] - print(EXPORTS / DIR) - yield { - "name": d, - "actions": [(cp, (DIR, EXPORTS / DIR))], - "clean": [f"""rm -rf {EXPORTS / DIR}"""], - "file_dep": files, - "targets": targets, - } + # for d in "user-tests resources".split(): + # DIR = Path(d) + # files = [x for x in DIR.rglob("*") if x.is_file()] + # targets = [EXPORTS / x for x in files] + # print(EXPORTS / DIR) + # yield { + # "name": d, + # "actions": [(cp, (DIR, EXPORTS / DIR))], + # "clean": [f"""rm -rf {EXPORTS / DIR}"""], + # "file_dep": files, + # "targets": targets, + # } # @task_params( diff --git a/nbconvert_a11y/_selectors.py b/nbconvert_a11y/_selectors.py deleted file mode 100644 index 5d70d373..00000000 --- a/nbconvert_a11y/_selectors.py +++ /dev/null @@ -1,7 +0,0 @@ -MAIN = "#notebook, .jp-Notebook" -CELL = ".cell, .jp-Cell" -CODE = ".code_cell, .jp-CodeCell" -MD = ".text_cell, .jp-MarkdownCell" -OUT = ".output, .jp-OutputArea.jp-Cell-outputArea" -IN = ".code_cell .input .input_area, .jp-Editor" -PROMPT = ".input_prompt, .jp-InputPrompt, .jp-OutputPrompt" diff --git a/nbconvert_a11y/audit.py b/nbconvert_a11y/audit.py index 197f5c54..90a47de3 100644 --- a/nbconvert_a11y/audit.py +++ b/nbconvert_a11y/audit.py @@ -1,5 +1,6 @@ """accessibility auditing tools.""" import asyncio +import os import traceback from contextlib import AsyncExitStack, asynccontextmanager from json import dumps @@ -12,6 +13,9 @@ logger = getLogger("a11y-tasks") +ENV_CHROMIUM_CHANNEL = "NBA11Y_CHROMIUM_CHANNEL" +DEFAULT_CHROMIUM_CHANNEL = "chrome-beta" + async def _main( ids: list, @@ -32,7 +36,7 @@ async def _main( browser = await play.chromium.launch( args=['--enable-blink-features="AccessibilityObjectModel"'], headless=True, - channel="chrome-beta", + channel=get_chrome_channel(), ) page = await browser.new_page() @@ -41,13 +45,17 @@ async def _main( await task(browser, page, output) +def get_chrome_channel(): + return os.environ.get(ENV_CHROMIUM_CHANNEL, DEFAULT_CHROMIUM_CHANNEL) + + @asynccontextmanager async def get_browser(): async with playwright.async_api.async_playwright() as play: yield await play.chromium.launch( args=['--enable-blink-features="AccessibilityObjectModel"'], headless=True, - channel="chrome-beta", + channel=get_chrome_channel(), ) diff --git a/nbconvert_a11y/form_exporter.py b/nbconvert_a11y/exporter.py similarity index 51% rename from nbconvert_a11y/form_exporter.py rename to nbconvert_a11y/exporter.py index 0bac147e..86eaff40 100644 --- a/nbconvert_a11y/form_exporter.py +++ b/nbconvert_a11y/exporter.py @@ -8,92 +8,89 @@ from contextlib import suppress from datetime import datetime from functools import lru_cache +from io import StringIO from pathlib import Path import bs4 import nbformat.v4 import pygments from bs4 import BeautifulSoup +from traitlets import Bool, CUnicode, Enum, Unicode + +from nbconvert import Exporter from nbconvert.exporters.html import HTMLExporter -from traitlets import Bool, CUnicode, Unicode singleton = lru_cache(1) HERE = Path(__file__).parent TEMPLATES = HERE / "templates" - +AXE_VERSION = "4.8.2" +AXE = f"https://cdnjs.cloudflare.com/ajax/libs/axe-core/{AXE_VERSION}/axe.min.js" SCHEMA = nbformat.validator._get_schema_json(nbformat.v4) -def strip_comments(tag): - for child in getattr(tag, "children", ()): - with suppress(AttributeError): - if isinstance(child, bs4.Comment): - child.extract() - strip_comments(child) - return tag - - -@lru_cache -def get_markdown_renderer(): - from markdown_it import MarkdownIt - from mdit_py_plugins.anchors import anchors_plugin - - md = MarkdownIt("gfm-like", options_update={"inline_definitions": True, "langPrefix": ""}) - md.use(anchors_plugin) - md.options.update(highlight=highlight) - return md - - -def get_markdown(md, **kwargs): - return get_markdown_renderer().render("".join(md), **kwargs) - +THEMES = { + "a11y": "a11y-{}", + "a11y-high-contrast": "a11y-high-contrast-{}", + "gh": "github-{}", + "gh-colorblind": "github-{}-colorblind", + "gh-high": "github-{}-high-contrast", + "gotthard": "gotthard-{}", + "blinds": "blinds-{}", +} -def highlight(code, lang="python", attrs=None): - import html - import pygments - - with suppress(BaseException): - return pygments.highlight( - code, - pygments.lexers.get_lexer_by_name(lang or "python"), - pygments.formatters.get_formatter_by_name( - "html", debug_token_types=True, title=f"{lang} code", wrapcode=True - ), - ) - return f"""
{html.escape(code)}
""" +class PostProcess(Exporter): + """an exporter that allows post processing after the templating step + this class introduces the `post_process_html` protocol that can be used to modify + exported html. + """ -def get_soup(x): - return bs4.BeautifulSoup(x, features="html5lib") + def from_notebook_node(self, nb, resources=None, **kw): + html, resources = super().from_notebook_node(nb, resources, **kw) + html = self.post_process_html(html) or html + return html, resources + def post_process_html(self, body): + ... -class FormExporter(HTMLExporter): - """an embellished HTMLExporter that allows modifications of exporting and the exported. - the `nbconvert` exporter has a lot machinery for converting notebook data into strings. - this class introduces a `post_process` trait that allows modifications after creating html content. - this method allows tools like `html.parser` and `bs4.BeautifulSoup` to make modifications at the end. +class A11yExporter(PostProcess, HTMLExporter): + """an accessible reference implementation for computational notebooks implemented for ipynb files. - changes to the template and exporter machinery are foundational changes that take time. - post modifications make it possible to quick changes in manual testing scenarios or configure - def post_process_code_cell(self, cell): - pass - A/B testing with out requiring `nbconvert` or notebook knowleldge. + this template provides a flexible screen reader experience with settings to control and customize the reading experience. """ - template_file = Unicode("semantic-forms/table.html.j2").tag(config=True) - include_axe = Bool(False).tag(config=True) - axe_url = CUnicode("https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.8.2/axe.min.js").tag( + template_file = Unicode("a11y/table.html.j2").tag(config=True) + include_axe = Bool(False, help="include axe auditing tools in the rendered page.").tag( + config=True + ) + axe_url = CUnicode(AXE, help="the remote source for the axe resources.").tag(config=True) + include_sa11y = Bool(False, help="include sa11y accessibility authoring tool").tag(config=True) + include_settings = Bool(False, help="include configurable accessibility settings dialog.").tag( config=True ) - include_settings = Bool(True).tag(config=True) - include_help = Bool(True).tag(config=True) - include_toc = Bool(True).tag(config=True) - include_cell_index = Bool(True).tag(config=True) + include_help = Bool( + False, help="include help and supplementary descriptions about notebooks and cells" + ).tag(config=True) + include_toc = Bool( + True, help="collect a table of contents of the headings in the document" + ).tag(config=True) + wcag_priority = Enum( + ["AAA", "AA", "A"], "AA", help="the default inital wcag priority to start with" + ).tag(config=True) + accesskey_navigation = Bool( + True, help="use numeric accesskeys to access the first 10 cells" + ).tag(config=True) + include_cell_index = Bool( + True, help="show the ordinal cell index, typically this is ignored from notebooks." + ).tag(config=True) exclude_anchor_links = Bool(True).tag(config=True) + code_theme = Enum(list(THEMES), "gh-high", help="an accessible pygments dark/light theme").tag( + config=True + ) def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -117,17 +114,32 @@ def __init__(self, *args, **kwargs) -> None: datetime=datetime, ) + @property + def default_config(self): + c = super().default_config + c.merge( + { + "CSSHTMLHeaderPreprocessor": {"enabled": False}, + } + ) + return c + def from_notebook_node(self, nb, resources=None, **kw): resources = resources or {} - resources.setdefault("include_axe", self.include_axe) - resources.setdefault("include_settings", self.include_settings) - resources.setdefault("include_help", self.include_help) - resources.setdefault("axe_url", self.axe_url) - html, resources = super().from_notebook_node(nb, resources, **kw) - html = self.post_process_html(html) - return html, resources + resources["include_axe"] = self.include_axe + resources["include_settings"] = self.include_settings + resources["include_help"] = self.include_help + resources["include_toc"] = self.include_toc + resources["wcag_priority"] = self.wcag_priority + resources["accesskey_navigation"] = self.accesskey_navigation + resources["code_theme"] = THEMES[self.code_theme] + resources["axe_url"] = self.axe_url + resources["include_sa11y"] = self.include_sa11y + + return super().from_notebook_node(nb, resources, **kw) def post_process_html(self, body): + """A final pass at the exported html to add table of contents, heading links, and other a11y affordances.""" soup = soupify(body) describe_main(soup) heading_links(soup) @@ -140,8 +152,63 @@ def post_process_html(self, body): return soup.prettify(formatter="html5") -class A11yExporter(FormExporter): - template_file = Unicode("a11y/table.html.j2").tag(config=True) +class SectionExporter(A11yExporter): + template_file = Unicode("a11y/section.html.j2").tag(config=True) + + +class ListExporter(A11yExporter): + template_file = Unicode("a11y/list.html.j2").tag(config=True) + + +def strip_comments(tag): + for child in getattr(tag, "children", ()): + with suppress(AttributeError): + if isinstance(child, bs4.Comment): + child.extract() + strip_comments(child) + return tag + + +@lru_cache +def get_markdown_renderer(): + from markdown_it import MarkdownIt + from mdit_py_plugins.anchors import anchors_plugin + + md = MarkdownIt("gfm-like", options_update={"inline_definitions": True, "langPrefix": ""}) + md.use(anchors_plugin) + md.options.update(highlight=highlight) + return md + + +def get_markdown(md, **kwargs): + """Exporter markdown as html""" + return get_markdown_renderer().render("".join(md), **kwargs) + + +def highlight(code, lang="python", attrs=None, experimental=True): + """Highlight code blocks""" + import html + + import pygments + + if lang == "code": + lang = "python" + elif lang == "raw": + return "" + + lang = lang or pygments.lexers.get_lexer_by_name(lang or "python") + + formatter = pygments.formatters.get_formatter_by_name( + "html", debug_token_types=True, title=f"{lang} code", wrapcode=True + ) + try: + return pygments.highlight( + code, pygments.lexers.get_lexer_by_name(lang or "python"), formatter + ) + except BaseException as e: + print(code, e) + + return f"""
{html.escape(code)}
""" def soupify(body: str) -> BeautifulSoup: @@ -150,6 +217,7 @@ def soupify(body: str) -> BeautifulSoup: def mdtoc(html): + """Create a table of contents in markdown that will be converted to html""" import io toc = io.StringIO() @@ -169,10 +237,12 @@ def mdtoc(html): def toc(html): + """Create an html table of contents""" return get_markdown(mdtoc(html)) def heading_links(html): + """Convert headings into links""" for header in html.select(":is(h1,h2,h3,h4,h5,h6):not([role])"): id = header.attrs.get("id") if not id: @@ -191,19 +261,33 @@ def heading_links(html): # * navigate landmarks +def count_cell_loc(cell): + lines = 0 + for line in StringIO("".join(cell.source)): + if not line: + continue + if line.strip(): + lines += 1 + return lines + + def count_loc(nb): - return sum(map(len, (x.source.splitlines() for x in nb.cells))) + """Count total significant lines of code in the document""" + return sum(map(count_cell_loc, nb.cells)) def count_outputs(nb): + """Count total number of cell outputs""" return sum(map(len, (x.get("outputs", "") for x in nb.cells))) def count_code_cells(nb): + """Count total number of code cells""" return len([None for x in nb.cells if x["cell_type"] == "code"]) def describe_main(soup): + """Add REFIDs to aria-describedby""" x = soup.select_one("#toc > details > summary") if x: x.attrs["aria-describedby"] = soup.select_one("main").attrs[ @@ -212,9 +296,12 @@ def describe_main(soup): def ordered(nb) -> str: + """Measure if the notebook is ordered""" start = 0 for cell in nb.cells: if cell["cell_type"] == "code": + if any("".join(cell.source).strip()): + continue start += 1 if start != cell["execution_count"] and start: return "executed out of order" diff --git a/nbconvert_a11y/exporters.py b/nbconvert_a11y/exporters.py deleted file mode 100644 index d81a76c1..00000000 --- a/nbconvert_a11y/exporters.py +++ /dev/null @@ -1,286 +0,0 @@ -"""nbconvert exporters towards accessible notebook html.""" - -from pathlib import Path -from re import compile - -from bs4 import BeautifulSoup, Tag -from nbconvert.exporters.html import HTMLExporter -from traitlets import Bool, Callable, CUnicode, List, TraitType - -from ._selectors import CODE, MAIN, MD, OUT, PROMPT - -DIR = Path(__file__).parent -PROMPT_RE = compile(r"(In|Out)(\s| ){0,1}\[(?P[0-9]+)\]") - - -def soupify(body: str) -> BeautifulSoup: - """Convert a string of html to an beautiful soup object.""" - return BeautifulSoup(body, features="html.parser") - - -class PostProcessExporter(HTMLExporter): - """an embellished HTMLExporter that allows modifications of exporting and the exported. - - the `nbconvert` exporter has a lot machinery for converting notebook data into strings. - this class introduces a `post_process` trait that allows modifications after creating html content. - this method allows tools like `html.parser` and `bs4.BeautifulSoup` to make modifications at the end. - - changes to the template and exporter machinery are foundational changes that take time. - post modifications make it possible to quick changes in manual testing scenarios or configure - def post_process_code_cell(self, cell): - pass - A/B testing with out requiring `nbconvert` or notebook knowleldge. - """ - - enabled = True - extra_template_paths = List([(DIR / "templates").absolute().__str__()]) - post_processor = Callable(lambda x: x).tag(config=True) - - -class Html5Test(PostProcessExporter): - """the primary exporter produced by notebooks for all. - - this class has a lot of flags that we designed to test. - the naming occurred organically as the project progressed. - we try to limit the degrees of freedom of each trait - so that the configuration changes are minimal. - """ - - def from_notebook_node(self, nb, **kw): - result, meta = super().from_notebook_node(nb, **kw) - result = self.post_process_html(result) - return str(result), meta - - notebook_is_main = Bool(True, help="transform notebook div to main").tag(config=True) - notebook_code_cell_is_article = Bool(True, help="transform code cell div to article").tag( - config=True - ) - notebook_md_cell_is_article = Bool(True, help="transform mardown cell div to article").tag( - config=True - ) - cell_output_is_section = Bool(True, help="transform output div to section").tag(config=True) - tab_to_code_cell = Bool(False, help="add tabindex to code cells for navigation").tag( - config=True - ) - tab_to_md_cell = Bool(False, help="add tabindex to md cells for navigation").tag(config=True) - tab_to_code_cell_display = Bool(False, help="add tabindex to cell displays for navigation").tag( - config=True - ) - code_cell_label = Bool(False, help="add aria-label to code cells").tag(config=True) - md_cell_label = Bool(False, help="add aria-label to md cell").tag(config=True) - cell_display_label = Bool(False, help="add aria-label to cell").tag(config=True) - # contenteditable cells make a tag interactive. - cell_contenteditable = Bool(False, help="make cell code inputs contenteditable").tag( - config=True - ) - cell_contenteditable_label = Bool(False, help="aria-label on contenteditable cells").tag( - config=True - ) - prompt_is_label = Bool(False, help="add the cell input number to the aria label").tag( - config=True - ) - - cell_describedby_heading = Bool( - False, help="set aria-describedby when heading found in markdown cell" - ).tag(config=True) - - increase_prompt_visibility = Bool( - True, help="decrease prompt transparency for better color contrast" - ).tag(config=True) - - cell_focus_style = CUnicode( - """outline: 1px dashed;""", - help="the focus style to apply to tabble cells.", - allow_none=True, - ).tag(config=True) - - include_toc = Bool( - False, - help="include a top of contents in the page", # this will likely need styling. - ) - # add toc as as a markdown cell? can't cause there is no canonical plage for it. - # the natural place for a table of contents is based on the dom structure. - # if the headings are links then they can be tabbed to. - h_is_link = Bool(False, help="markdown headings h1..6 are links that get tabbed to.").tag( - config=True - ) - scroll_to_top = Bool(False, help="include a scroll to top link").tag(config=True) - - def post_process_head(self, soup): - """Post process the head of the document. - - add custom css based on flags - """ - script = soup.new_tag("style", type="text/css", rel="stylesheet") - script.string = "" - if self.increase_prompt_visibility: - script.string += """ -:root { - --jp-cell-prompt-not-active-opacity: 1; -} -.jp-InputArea, .jp-Editor, .CodeMirror { - overflow: auto; -} -.jp-MainAreaWidget > :focus { - outline: auto; -} -""" - if self.cell_focus_style: - css = ( - """.jp-Cell:focus { - %s -} -""" - % self.cell_focus_style - ) - if self.tab_to_code_cell: - script.string += css - if self.cell_contenteditable: - script.string += css.replace("Cell", "Editor") - - soup.select_one("head").append(script) - - def post_process_html(self, body): - soup = soupify(body) - if self.notebook_is_main: - soup.select_one(MAIN).name = "main" - - soup.select_one("html").attrs["lang"] = "en" - - self.post_process_head(soup) - - self.post_process_cells(soup) - if self.scroll_to_top: - footer = soup.select_one("main footer") - if not footer: - footer = Tag(name="footer") - soup.select_one("main").append(footer) - a = Tag(name="a", attrs={"href": "#top"}) - a.string = "Scroll to top" - b = Tag(name="span", attrs={"id": "top"}) - footer.append(a) - soup.select_one("main").insert(0, b) - return str(soup) - - def post_process_cells(self, soup): - for i, element in enumerate(soup.select(CODE)): - self.post_process_code_cell(element, i) - - for element in soup.select(MD): - self.post_process_markdown_cell(element) - - def post_process_code_cell(self, cell, i): - if self.notebook_code_cell_is_article: - cell.name = "article" - - if self.tab_to_code_cell: - cell.attrs["tabindex"] = 0 # when we do this we need add styling - - if self.code_cell_label: - # https://ericwbailey.website/published/aria-label-is-a-code-smell/ - cell.attrs["aria-label"] = "cell" - if self.prompt_is_label: - prompt = cell.select_one(PROMPT) - m = PROMPT_RE.match(prompt.text) - if m and self.prompt_is_label: - cell.attrs["aria-label"] += " {}".format(m.group("n")) - - if self.cell_contenteditable: - prompt = cell.select_one(PROMPT) - prompt.name = "label" - text = prompt.text - prompt.string = "" - start, lbracket, rest = text.partition("[") - number, rbracket, rest = rest.partition("]:") - prompt.append(start) - t = Tag(name="span", attrs={"aria-hidden": "true"}) - t.string = lbracket - prompt.append(t) - prompt.append(number) - t = Tag(name="span", attrs={"aria-hidden": "true"}) - t.string = rbracket - prompt.append(t) - prompt.attrs["for"] = f"code-cell-input-{i}" - prompt.attrs["id"] = f"code-cell-prompt-{i}" - prompt.attrs["aria-description"] = f"input {number}" - input = cell.select_one("code, .jp-Editor") - input.attrs["contenteditable"] = "false" - input.attrs["id"] = prompt.attrs["for"] - input.attrs["role"] = "textbox" - input.attrs["aria-multiline"] = "true" - input.attrs["aria-readonly"] = "true" - input.attrs["aria-labelledby"] = prompt.attrs["id"] - input.attrs["tabindex"] = "0" - - if self.tab_to_code_cell: - cell.attrs["tabindex"] = 0 # when we do this we need add styling - - self.post_process_displays(cell) - - def post_process_displays(self, cell): - for out in cell.select(OUT): - self.post_process_display(out) - - def post_process_display(self, display): - if self.cell_output_is_section: - display.name = "section" - - if self.cell_display_label: - display.attrs["aria-label"] = "display" - if self.prompt_is_label: - prompt = display.select_one(PROMPT) - if prompt: - m = PROMPT_RE.match(prompt.text) - if m: - display.attrs["aria-label"] += " output {}".format(m.group("n")) - if self.tab_to_code_cell_display: - display.attrs["tabindex"] = 0 # when we do this we need add styling - - def post_process_markdown_cell(self, cell): - if self.notebook_md_cell_is_article: - cell.name = "article" - - if self.md_cell_label: - # https://ericwbailey.website/published/aria-label-is-a-code-smell/ - cell.attrs["aria-label"] = "markdown" - - if self.cell_describedby_heading: - heading = cell.select_one("h1,h2,h3,h4,h5,h6") - if heading and "id" in heading.attrs: - cell.attrs["aria-describedby"] = heading.attrs["id"] - - if self.tab_to_md_cell: - cell.attrs["tabindex"] = 0 # when we do this we need add styling - - if self.h_is_link: - for e in cell.select("h1,h2,h3,h4,h5,h6"): - id = e.attrs.get("id") - if id: - a = Tag(name="a") - a.attrs["href"] = f"#{id}" - a.extend(list(e.children)) - e.clear() - e.append(a) - e.select_one(".anchor-link").decompose() - - @classmethod - def generate_config(cls): - s = """c.NbConvertApp.export_format = "html5" -c.CSSHTMLHeaderPreprocessor.style = "default" -""" - for k, v in vars(cls).items(): - if isinstance(v, TraitType): - val = v.default_value - if isinstance(val, str): - val = f'''"{val}"''' - s += f"c.{cls.__name__}.{k} = {val} # {v.help}\n" - return s - - @classmethod - def write_config(cls, dir=Path.cwd(), file="jupyter_nbconvert_config.py"): - target = Path(dir, file) - if target.exists(): - raise FileExistsError(target) - - print(f"writing config to {target}") - target.write_text(cls.generate_config()) diff --git a/nbconvert_a11y/pytest_axe.py b/nbconvert_a11y/pytest_axe.py index 84c65cd4..4b4c3873 100644 --- a/nbconvert_a11y/pytest_axe.py +++ b/nbconvert_a11y/pytest_axe.py @@ -1,7 +1,15 @@ +"""report axe violations in html content + +* an axe fixture to use in pytest +* a command line application for auditing files. + +""" + # requires node and axe # requires playwright import dataclasses -from functools import lru_cache +from collections import defaultdict +from functools import lru_cache, partial from json import dumps, loads from pathlib import Path from shlex import quote, split @@ -9,34 +17,83 @@ from typing import Any import exceptiongroup -from attr import dataclass from pytest import fixture, mark, param -nbconvert_a11y_DYNAMIC_TEST = "nbconvert_a11y_DYNAMIC_TEST" - -axe_config_aa = { - "runOnly": ["act", "best-practice", "experimental", "wcag21a", "wcag21aa", "wcag22aa"], - "allowedOrigins": [""], -} - -axe_config_aaa = { - "runOnly": [ - "act", - "best-practice", - "experimental", - "wcag21a", - "wcag21aa", - "wcag22aa", - "wcag2aaa", - ], - "allowedOrigins": [""], -} - +# selectors for regions of the notebook MATHJAX = "[id^=MathJax]" -tests_axe = {"exclude": [MATHJAX]} +JUPYTER_WIDGETS = ".jupyter-widgets" +OUTPUTS = ".jp-OutputArea-output" +NO_ALT = "img:not([alt])" +PYGMENTS = ".highlight" +SA11Y = "sa11y-control-panel" + +# axe test tags +# https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#axe-core-tags +TEST_TAGS = [ + "ACT", + "best-practice", + "experimental", + "wcag2a", + "wcag2aa", + "wcag2aaa", + "wcag21a", + "wcag21aa", + "wcag22aa", + "TTv5", +] + + +class Base: + """base class for exceptions and models""" + + def __init_subclass__(cls) -> None: + dataclasses.dataclass(cls) + + def dict(self): + return {k: v for k, v in dataclasses.asdict(self).items() if v is not None} + + def dump(self): + return dumps(self.dict()) + + +# https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure +class AxeConfigure(Base): + """axe configuration model""" + + branding: str = None + reporter: str = None + checks: list = None + rules: list = None + standards: list = None + disableOtherRules: bool = None + local: str = None + axeVersion: str = None + noHtml: bool = False + allowedOrigins: list = dataclasses.field(default_factory=[""].copy) + + +# https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter +class AxeOptions(Base): + """axe options model""" + + runOnly: list = dataclasses.field(default_factory=TEST_TAGS.copy) + rules: list = None + reporter: str = None + resultTypes: Any = None + selectors: bool = None + ancestry: bool = None + xpath: bool = None + absolutePaths: bool = None + iframes: bool = True + elementRef: bool = None + frameWaitTime: int = None + preload: bool = None + performanceTimer: bool = None + pingWaitTime: int = None def get_npm_directory(package, data=False): + """Get the path of an npm package in the environment""" try: info = loads(check_output(split(f"npm ls --long --depth 0 --json {quote(package)}"))) except CalledProcessError: @@ -46,14 +103,17 @@ def get_npm_directory(package, data=False): return Path(info.get("dependencies").get(package).get("path")) -@dataclass -class AxeResults: +class AxeResults(Base): data: Any - def raises(self): + def exception(self): if self.data["violations"]: - raise AxeException.from_violations(self.data) - return self + return Violation.from_violations(self.data) + + def raises(self): + exc = self.exception() + if exc: + raise exc def dump(self, file: Path): if file.is_dir(): @@ -63,36 +123,139 @@ def dump(self, file: Path): return self +class NotAllOf(Exception): + ... + + +class AllOf(Exception): + ... + + +class NoAllOfMember(Exception): + ... + + @dataclasses.dataclass -class AxeException(Exception): - message: str - target: list - data: dict = dataclasses.field(repr=False) +class Axe(Base): + """the Axe class is a fluent api for configuring and running accessibility tests.""" - types = {} + page: Any = None + url: str = None + results: Any = None - @classmethod - def new(cls, id, impact, message, data, target, **kwargs): - if id in cls.types: - cls = cls.types.get(id) - else: - cls = cls.types.setdefault( - id, - type( - f"{impact.capitalize()}{''.join(map(str.capitalize, id.split('-')))}Exception", - (cls,), - {}, - ), + def __post_init__(self): + self.page.goto(self.url) + self.page.evaluate(get_axe()) + + def configure(self, **config): + self.page.evaluate(f"window.axe.configure({AxeConfigure(**config).dump()})") + return self + + def reset(self): + self.page.evaluate("""window.axe.reset()""") + return self + + def __enter__(self): + self.reset() + + def __exit__(self, *e): + None + + def run(self, test=None, options=None): + self.results = AxeResults( + self.page.evaluate( + f"""window.axe.run({test and dumps(test) or "document"}, {AxeOptions(**options or {}).dump()})""" ) - return cls(message, target, data) + ) + return self + + def raises(self, allof=None): + if allof: + self.raises_allof(allof) + else: + self.results.raises() + + def raises_allof(self, *types, extra=False): + found = set() + allof = set() + exc = self.results.exception() + if exc: + for t in list(types): + for e in exc.exceptions: + allof.add(type(e)) + if isinstance(e, t): + found.add(t) + not_found = set(types).difference(found) + if not_found: + raise NotAllOf(f"""{",".join(map(str, not_found))} not raised""") + elif not extra: + excess = allof.difference(found) + if excess: + raise NoAllOfMember(f"""{",".join(map(str, excess))} """) + result = AllOf(f"""{",".join(map(str, allof))} exceptions raised""") + result.__cause__ = exc + return result + + +class Violation(Exception, Base): + id: str = dataclasses.field(repr=False) + impact: str | None = dataclasses.field(repr=False) + tags: list = dataclasses.field(default=None, repr=False) + description: str = "" + help: str = "" + helpUrl: str = "" + nodes: list = dataclasses.field(default=None, repr=False) + elements: dict = dataclasses.field(default_factory=partial(defaultdict, list)) + map = {} + + def __class_getitem__(cls, id): + if id in cls.map: + return cls.map[id] + return cls.map.setdefault(id, type(id, (Violation,), {})) + + def __new__(cls, **kwargs): + if cls is Violation: + target = cls.cast(kwargs) + return target(**kwargs) + self = super().__new__(cls, **kwargs) + self.__init__(**kwargs) + return self + + @classmethod + def cast(cls, data): + object = {"__doc__": f"""{data.get("help")} {data.get("helpUrl")}"""} + name = "-".join((data["impact"], data["id"])) + if name in cls.map: + return cls.map.get(name) + bases = () + # these generate types primitves + if data["impact"]: + bases += (Violation[data["impact"]],) + for tag in data["tags"]: + bases += (Violation[tag],) + return cls.map.setdefault(name, type(name, bases, object)) + + def get_elements(self, N=150): + for node in self.nodes: + key = node["html"] + if len(key) > N: + key = key[:N] + "..." + self.elements[key].extend(node["target"]) + + def __str__(self): + try: + self.get_elements() + return repr(self) + except BaseException as e: + print(e) + raise e @classmethod def from_violations(cls, data): out = [] for violation in (violations := data.get("violations")): - for node in violation["nodes"]: - for exc in node["any"]: - out.append(cls.new(**exc, target=node["target"])) + out.append(Violation(**violation)) + return exceptiongroup.ExceptionGroup(f"{len(violations)} accessibility violations", out) @@ -106,23 +269,11 @@ def get_axe(): return (get_npm_directory("axe-core") / "axe.js").read_text() -def inject_axe(page): - page.evaluate(get_axe()) - - -def run_axe_test(page, tests_config=None, axe_config=None): - return AxeResults( - page.evaluate( - f"window.axe.run({tests_config and dumps(tests_config) or 'document'}, {dumps(axe_config or {})})" - ) - ) - - @fixture() def axe(page): - def go(url, tests=tests_axe, axe_config=axe_config_aa): - page.goto(url) - inject_axe(page) - return run_axe_test(page, tests, axe_config) + def go(url, **axe_config): + axe = Axe(page=page, url=url) + axe.configure(**axe_config) + return axe return go diff --git a/nbconvert_a11y/pytest_github_step_summary.py b/nbconvert_a11y/pytest_github_step_summary.py new file mode 100644 index 00000000..c1765a23 --- /dev/null +++ b/nbconvert_a11y/pytest_github_step_summary.py @@ -0,0 +1,29 @@ +from dataclasses import dataclass, field +from io import StringIO +from os import environ +from pathlib import Path +from sys import modules + +TARGET = Path(environ.get("GITHUB_STEP_SUMMARY", "github-step-summary.md")) + +if "pytest" in modules: + from pytest import fixture + + @fixture(scope="session") + def github_summary(): + summary = Summary() + yield Summary() + summary.write() + + +@dataclass +class Summary: + buffer: StringIO = field(default_factory=StringIO) + + def append(self, body): + self.buffer.write(body) + + def write(self): + with TARGET.open("a") as file: + print(self.buffer.getvalue(), file=file) + self.buffer = StringIO() diff --git a/nbconvert_a11y/templates/a11y/README.md b/nbconvert_a11y/templates/a11y/README.md new file mode 100644 index 00000000..63a6da97 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/README.md @@ -0,0 +1,63 @@ +# `nbconvert-a11y` reference templates + +the primary intent is to provide reference implementations of accessible computational notebooks. the reference implementations are designed from aria first and second principles to develop semantic html5 representations of notebooks and their components. the templates try to use as much native styling as possible, their views are quite plain. they MAY be used as actual templates for accessible documentation, but style is not a priority and `nbconvert-a11y` relies strongly on native style. it is possible for template users to further customize their styling with custom css. + +## goals + +a popular resource for accessibility developers are the ARIA practicing guides that provide. these resources do have known issues, but that does not detract from the idea that a reference implementation is valuable for developing accessible applications. this work intends to provide a reference for computational notebooks. + +varied applications of notebooks make it difficult to choose a single reference implementation. this library provides multiple reference implementations that representation minimal, standards-compliant templates. + +* a well formed accessibility tree / object model +* aaa priority compliance that gracefully degrades into aa and a +* a _hull_ of multiple reference implementations +* test experiences on assistive technologies + + +### non-goals + +* extra css styling +* replace existing templates +* advanced interactive applications + +there are several accessible variants of notebooks that are implemented in this repository, and some more could exist like shadow down templates. + +## first principle templates + +
+If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so. +
+ +first principle reference templates use semantically meaningful dom tags. + +- [x] cells are `section` landmarks with implicit `role=region` +- [x] cells are `li` ordered list items +- [x] cells are `tr` rows in a `table` + +## second principle templates + +having multiple first principle reference templates indicates the variety of potential applications. each of the first order templates expose different navigation techniques to assistive technology users, and choosing a single technique may not be possible. to embrace the pluralism, we use the `table` as a flexible interface for providing different assistive technology navigation techniques by transforming tables into lists or tables. + +there is a single second principle implementation that projects the structured data into a table representation. learn more in that template. + + +## shadow dom templates + +it is important for these reference implementations to exist to guide decisions in other products. reference implementations and expected patterns can be valuable in guiding decisions in other products like JupyterLab. + +there have been, and will be, discussions about web components in computational notebook applications. there are plenty of reports about accessibility challenges with web components. + +* https://nolanlawson.com/2022/11/28/shadow-dom-and-accessibility-the-trouble-with-aria/ +* https://marcysutton.com/accessibility-and-the-shadow-dom + +having a reference implementation using the `template` and `slot` tags to inspect the shadow dom accessibility implications based on current reference implementations. we'd benefit to learn how the assistive experience may suffer if a first or second principle shadow do approaches were used. + + +## web content accessibility guidelines as a user interaction + +the intent of these templates is too begin with priority aaa compliant substrates for content in computational notebooks. "should we target priority aaa? couldn't we make an easier goal." is a common sentiment in retrofitting accessibility. our approach targets priority aaa compliance with the ability to remove constraints for those that would prefer less strict accessibility conditions. priority AAA will often benefit low vision and ambulatory conditions, but they may be too much for an abled user. + +some examples of progressive accessibility changes: + +* using native representation of source code to always satisfy AAA priority versus pygments highlighting when priority AA is preferred. +* target size has AAA and AA guidelines. target sizes are expected to be larger with AAA compliance and smaller with AA compliance. \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/activity-log.html.j2 b/nbconvert_a11y/templates/a11y/activity-log.html.j2 deleted file mode 100644 index 6542c27a..00000000 --- a/nbconvert_a11y/templates/a11y/activity-log.html.j2 +++ /dev/null @@ -1,11 +0,0 @@ -
- activity log -
- - - - - {# all forward behaviors should be reversible except code execution #} - {# #} - -
timemessageaction
\ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/base.html.j2 b/nbconvert_a11y/templates/a11y/base.html.j2 new file mode 100644 index 00000000..4e06990a --- /dev/null +++ b/nbconvert_a11y/templates/a11y/base.html.j2 @@ -0,0 +1,94 @@ +{# # a base template for accessible notebook representations. + +the base template defines notebook independent components. +an accessible base template provides a substrate to progressively enchance +the notebook experiennce from browse to edit/focus mode. +#} +{%- extends 'lab/index.html.j2' -%} +{% from "a11y/components/core.html.j2" import activity_log %} +{% from 'celltags.j2' import celltags %} +{% from 'mathjax.html.j2' import mathjax %} +{% from 'lab/mermaidjs.html.j2' import mermaid_js %} +{% from 'jupyter_widgets.html.j2' import jupyter_widgets %} +{% set title = nb.metadata.get('title', resources['metadata']['name']) | escape_html_keep_quotes %} + +{%- block html_head_js -%} + +{# sa11y needs to be loaded before requirejs #} +{% if resources.include_sa11y %}{% include "a11y/components/sa11y.html.j2"%}{% endif %} +{{super()}} +{%- endblock html_head_js -%} + +{% block notebook_css %} + +{% for theme in ["light", "dark"] %} + +{% endfor %} +{% endblock notebook_css %} + +{% block extra_css %}{% endblock %} + +{% block body_header %} + + +
+ skip to main content + {{site_navigation | default("")}} +
+
+ {%- if resources.include_toc -%}{% include "a11y/components/toc.html.j2" %}{%- endif -%} + {% endblock body_header %} + + {% block body_footer scoped %} + {# dialogs need to be outside the form because we cant nest forms #} + +
+ + {{nb.metadata| json_dumps | escape_html_keep_quotes }} +
+
+ {% include "a11y/components/nb-toolbar.html.j2" %} +
+ {# a notebook begins as a static document that can progressively + add features like run time computation. #} + {# skip to top is needed for long notebooks. + it is difficult to access for keyboard users. #} +
+
+ settings, help, & diagnostics +
+ {% if resources.include_settings %}{% include "a11y/components/settings.html.j2" %}{% endif %} + {% include "a11y/components/visibility.html.j2"%} + {% if resources.include_help %}{% include "a11y/components/help.html.j2" %}{% endif %} + {% if resources.include_axe %}{% include "a11y/components/audit.html.j2" %}{% endif %} + {# make the individual settings discoverable before all the settings + when using shift+tab #} + {% if resources.include_settings %} + + {% endif %} + + {% if resources.include_axe %}{% endif %} + {% if resources.include_help %}{% endif %} +
+ + +{% endblock body_footer %} + +{% block footer_js %} +{% if resources.include_settings %} +{% endif %} +{%- if resources.include_axe -%} + +{%- endif -%} +{%- endblock footer_js -%} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/audit.j2.html b/nbconvert_a11y/templates/a11y/components/audit.html.j2 similarity index 70% rename from nbconvert_a11y/templates/a11y/audit.j2.html rename to nbconvert_a11y/templates/a11y/components/audit.html.j2 index c3c19956..1bed1bbc 100644 --- a/nbconvert_a11y/templates/a11y/audit.j2.html +++ b/nbconvert_a11y/templates/a11y/components/audit.html.j2 @@ -1,5 +1,31 @@ +{# # in page auditing + +this template creates an in-page accessibility auditing dialog. +the eventual state of a notebook is an interactive editing state. +having the auditing system brings accessibility standards and knowledge +nearer to the developer. +it can be useful when authoring is enabled and when reporting issues +or feedback. + +the axe tests are configurable in the dialog. +when running axe tests, the dialog running the tests must be suppressed and +reactivated after the tests complete. +if the dialog remains open then axe will only test the dialog. + +#} + +{% from "a11y/components/core.html.j2" import dialog_close, multiselect %} +{% set RULES = "wcag2a wcag2aa wcag2aaa wcag21a wcag21aa wcag22aa best-practice ACT section508 TTv5 EN-301-549 +experimental".split() %}

AXE accessibility violations

+
+ {{dialog_close()}} +
+
+ {{multiselect("axe-rules", RULES)}} + +
@@ -26,20 +52,26 @@

AXE accessibility violations

\ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/components/cell.html.j2 b/nbconvert_a11y/templates/a11y/components/cell.html.j2 new file mode 100644 index 00000000..0d4809dc --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/cell.html.j2 @@ -0,0 +1,100 @@ +{% from "a11y/components/core.html.j2" import loc, hide %} +{% from "a11y/components/displays.html.j2" import cell_display_priority with context %} + +{% macro cell_anchor(i, cell_type, hidden=False)%} +{{i}} +{% endmacro %} + +{% macro cell_form(i, cell_type, hidden=True) %} +{# the cell form acts a formal reference for each cell. as a form, each cell can handle a submission process +that would include talking to the kernel. #} + +
+ actions + +
+ +{% endmacro %} + +{% macro cell_cell_type(i, cell_type, hidden=False) %} +{% set selected = ' selected id="cell-{}-cell_type"'.format(i) %} + +{% endmacro %} + +{% macro cell_execution_count(i, execution_count, hidden=False) %} +#{{execution_count}} +{% endmacro %} + + +{% macro cell_source(i, source, cell_type, execution_count, hidden=False) %} +{% set label -%} + + In{{execution_count}} + +{%- endset %} +
+ {{label}} + + {{highlight(source, cell_type)}} +
+{% endmacro %} + +{% macro cell_metadata(i, metadata, hidden=False) %} + + +
+ +

+        {{metadata}}
+        
+ +
+{% endmacro %} + +{%- macro cell_output(i, cell, source, outputs, cell_type, execution_count, hidden=False) -%} +{% set CODE = cell_type == "code" %} +{% set label %}{% if CODE and outputs %}Out{{execution_count}}{% else %}Cell {{i}}{% endif %}{% endset %} + +{% if CODE and outputs %} +{% if outputs %} +
+ {{label}} + {# the output description should mention the number of outputs + saying zero outputs should be an option. a cell without an output is probably a violation. #} + {{cell_display_priority(i, outputs, cell)}} +
+{% endif %} +{% elif cell_type=="markdown" %} +
+ + {{ markdown(source) | strip_files_prefix }} +
+{% endif %} +{%- endmacro -%} + + +{% macro cell_section(cell, loop, tag="section") %} +<{{tag}} class="cell {{cell.cell_type}}" aria-labelledby="cell-{{loop.index}}-cell_type {{loop.index}}" + data-loc="{{cell.source.splitlines().__len__()}}" {% if cell.cell_type=="code" %} + data-outputs="{{cell.outputs.__len__()}}" {% endif %}> + {{cell_anchor(loop.index, cell.cell_type)}} + {{cell_form(i, hidden=True)}} + {{cell_execution_count(loopindex, cell.execution_count, hidden=True)}} + {{cell_cell_type(loop.index, cell.cell_type, hidden=True)}} + {{cell_source(loop.index, cell.source, cell.cell_type, cell.execution_count, hidden=cell.cell_type != "code")}} + {{cell_output(loop.index, cell, cell.source, cell.outputs, cell.cell_type, cell.execution_count)}} + {{cell_metadata(loop.index, cell.metadata, hidden=True)}} + +{% endmacro%} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/components/core.html.j2 b/nbconvert_a11y/templates/a11y/components/core.html.j2 new file mode 100644 index 00000000..10bb000a --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/core.html.j2 @@ -0,0 +1,118 @@ +{# # core accessible components + +this document provides collections of macros that can be used to provide tested accessible experiences. +these core components use aria first principles to provide accessible components. +a to do is individual testing of each of the components, but writing those tests means +we've been success doing other things + +developers should be aware that this template should imported `with context`. for example, +`"a11y/components/audit.html.j2"` + +```html +{% from "a11y/components/core.html.j2" import dialog_close, multiselect with context%} +``` + +in this specific application that context really isn't necessary. +at some point effort should be put into separating context dependent and independent macros. + +#} + + +{% macro select(title, values={}, default=None, disabled=None, hide_label=False) %} +{% set name="-".join(map(str.lower, title.split()))%} +{% if default == None %} +{% set default = next(iter(values), None)%} +{% endif %} + +{% if isinstance(values, [].__class__) %} +{% set values = dict(zip(values, values))%} +{% endif %} +{% if isinstance(disabled, [].__class__) %} +{% set disabled = dict(zip(disabled, disabled))%} +{% endif %} + +{% endmacro %} + + +{% macro activity_log(id=False) %} +
+ activity log +
+
+ + + + {# all forward behaviors should be reversible except code execution #} + {# #} + +
messageaction
+{% endmacro %} + +{% macro input_number(title, value, min=None, max=None, step=None, extra_title="")%} +{% set name="-".join(map(str.lower, title.split()))%} + +{% endmacro%} + +{% macro checkbox(title, value)%} +{% set name="-".join(map(str.lower, title.split()))%} + +{% endmacro%} + +{% macro dialog_close() %} + +{% endmacro %} + +{% macro dialog_close_form() %} +
{{dialog_close()}}
+{% endmacro %} + + +{% macro h(level, title)%} +{% set name="-".join(map(str.lower, title.split()))%} +{{title}} +{% endmacro%} + +{% macro hide(object) %}{% if object %} hidden{% endif %}{% endmacro %} + +{% macro multiselect(title, values={}, default=None) %} +{% set name="-".join(map(str.lower, title.split()))%} +{% if default == None %} +{% set default = list(values) %} +{% endif %} +{% if isinstance(values, [].__class__) %} +{% set values = dict(zip(values, values))%} +{% endif %} + +{% endmacro %} + +{% macro loc(cell) %}{{cell.source.splitlines().__len__()}}{% endmacro%} + +{% macro time(t) %} +{% if t %}{% if t.endswith("Z") %}{% set t = t[:-1] + "+00:00" %}{% endif %}{% endif %} +{% endmacro %} + +{# + +[h25]: https://www.w3.org/WAI/WCAG21/Techniques/html/H25 +[2.4.2A]: https://www.w3.org/WAI/WCAG21/Understanding/page-titled + +#} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/displays.j2.html b/nbconvert_a11y/templates/a11y/components/displays.html.j2 similarity index 100% rename from nbconvert_a11y/templates/semantic-forms/displays.j2.html rename to nbconvert_a11y/templates/a11y/components/displays.html.j2 diff --git a/nbconvert_a11y/templates/a11y/help.html.j2 b/nbconvert_a11y/templates/a11y/components/help.html.j2 similarity index 100% rename from nbconvert_a11y/templates/a11y/help.html.j2 rename to nbconvert_a11y/templates/a11y/components/help.html.j2 diff --git a/nbconvert_a11y/templates/a11y/components/inputs.html.j2 b/nbconvert_a11y/templates/a11y/components/inputs.html.j2 new file mode 100644 index 00000000..79977f8c --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/inputs.html.j2 @@ -0,0 +1,12 @@ +{% macro group(type, title, values={}) %} +
+ color scheme + + + +
+{% endgroup %} + +{% macro radiogroup(title, values={}) %} +{{group("radio", title, values)}} +{% endgroup %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/components/nb-toolbar.html.j2 b/nbconvert_a11y/templates/a11y/components/nb-toolbar.html.j2 new file mode 100644 index 00000000..2181df84 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/nb-toolbar.html.j2 @@ -0,0 +1,5 @@ +
+ + + +
\ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/components/sa11y.html.j2 b/nbconvert_a11y/templates/a11y/components/sa11y.html.j2 new file mode 100644 index 00000000..6b78f456 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/sa11y.html.j2 @@ -0,0 +1,32 @@ +{# i discovered sa11y a fork called [editoria11y](https://github.com/itmaybejj/editoria11y). +turns out both of these projects began as tota11y. #} + + + + + + + + + + \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/components/settings.html.j2 b/nbconvert_a11y/templates/a11y/components/settings.html.j2 new file mode 100644 index 00000000..7232b2cc --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/settings.html.j2 @@ -0,0 +1,49 @@ +{# +settings provides multiple screen reader navigation techniques when the dialog is active. + +#} + +{% from "a11y/components/core.html.j2" import h, radiogroup, select, activity_log, input_number, checkbox, dialog_close %} + +
+ {{h(1, "accessibility settings")}} + {{dialog_close()}} +
    +
  • + {{h(2, "notebook layout")}} +
      +
    • {{select("color scheme", {"light mode": "light", "dark mode": "dark"})}}
    • +
    • {{input_number("margin", 5, min=0, max=40, step=5, extra_title="%")}}
    • +
    • {{input_number("line height", 1.5, min=0.5, max=3, step=0.1)}}
    • +
    • {{select("cell navigation", values="list table landmark presentation".split(), disabled="grid + treegrid + tree".split())}}
    • +
    • {{select("accessibility priority", "AAA AA A".split(), resources.wcag_priority)}}
    • +
    • {{checkbox("accesskey navigation", True)}}
    • +
    +
  • +
  • + {{h(2, "font settings")}} +
      +
    • {{select("font size", "xx-small x-small small medium large x-large xx-large".split(), + "medium")}}
    • +
    • + {{select("font family", "serif sans-serif".split())}} +
    • +
    +
  • +
  • + {{h(2, "sound settings")}} +
      +
    • + {{checkbox("synthetic speech", False)}} +
    • +
    • + {{checkbox("aria live", True)}} +
    • +
    +
  • +
+ {{activity_log(False)}} +
+
\ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/components/toc.html.j2 b/nbconvert_a11y/templates/a11y/components/toc.html.j2 new file mode 100644 index 00000000..0d43da00 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/toc.html.j2 @@ -0,0 +1,15 @@ +
+ {# a notebook will provide visual structural navigation for a document. + this is a feature of screen readers that is not common to sighted users. + the implementation here is very naive. users will need to know to collapse the heading + to skip the link tree. the best implementation is a tree that will consume a single tab stop + and allow arrow key navigation. #} +
+ {# if the label is on the summary then the bullet is announced as the label and it should not be + #} + table of contents + {# the table of contents is populated in python. #} + +
    +
    +
    \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/visibility.html.j2 b/nbconvert_a11y/templates/a11y/components/visibility.html.j2 similarity index 73% rename from nbconvert_a11y/templates/a11y/visibility.html.j2 rename to nbconvert_a11y/templates/a11y/components/visibility.html.j2 index f82be4e5..c522a91c 100644 --- a/nbconvert_a11y/templates/a11y/visibility.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/visibility.html.j2 @@ -1,3 +1,5 @@ +{% from "a11y/components/core.html.j2" import activity_log %} + {% macro header_row(names) %} @@ -39,5 +41,18 @@ - {% include "a11y/activity-log.html.j2" %} +
    + + + + {% set labels = "cell source outputs" %} + {{header_row(labels)}} + {{checkbox_row("code", labels)}} + {{checkbox_row("markdown", labels)}} + {{checkbox_row("raw", labels)}} + +
    +
    + {{activity_log()}} +
    \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/expanded.html.j2 b/nbconvert_a11y/templates/a11y/expanded.html.j2 deleted file mode 100644 index c1086778..00000000 --- a/nbconvert_a11y/templates/a11y/expanded.html.j2 +++ /dev/null @@ -1,17 +0,0 @@ -{% from "a11y/visibility.html.j2" import header_row, checkbox_row%} - -
    - - - - {% set labels = "cell source outputs" %} - {{header_row(labels)}} - {{checkbox_row("code", labels)}} - {{checkbox_row("markdown", labels)}} - {{checkbox_row("raw", labels)}} - -
    -
    - {% include "a11y/activity-log.html.j2" %} - -
    \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/list.html.j2 b/nbconvert_a11y/templates/a11y/list.html.j2 new file mode 100644 index 00000000..d3b3ffd1 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/list.html.j2 @@ -0,0 +1,22 @@ +{# # notebooks as an ordered list + +there are ordered and unordered applications of cells in computational notebooks. +observable notebooks use a topological ordered while jupyter notebooks use an ordered +execution model. the ordered and unordered lists are appropriate form capturing these semantics. + +the list elements can become trees with collapsible user interactions. + +#} +{%- extends 'a11y/base.html.j2' -%} +{% from "a11y/components/cell.html.j2" import cell_section with context%} + +{% block body_loop %} +{# the most consistent implementation would connect the input visibility to a form #} +
      + {{super()}} +
    +{% endblock body_loop %} + +{% block any_cell scoped %} +{{cell_section(cell, loop, "li")}} +{% endblock any_cell %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/section.html.j2 b/nbconvert_a11y/templates/a11y/section.html.j2 new file mode 100644 index 00000000..b16a7f62 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/section.html.j2 @@ -0,0 +1,6 @@ +{%- extends 'a11y/base.html.j2' -%} +{% from "a11y/components/cell.html.j2" import cell_section with context%} + +{% block any_cell scoped %} +{{cell_section(cell, loop)}} +{% endblock any_cell %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/settings.html.j2 b/nbconvert_a11y/templates/a11y/settings.html.j2 deleted file mode 100644 index beb07860..00000000 --- a/nbconvert_a11y/templates/a11y/settings.html.j2 +++ /dev/null @@ -1,56 +0,0 @@ - - -
    -

    Settings

    - -
    - color scheme - - - -
    - -
    - cell navigation - - modify the screen reader's cell navigation preference, - there is no visual effect. - - - - - - - -
    -
    - font settings - - -
    - serif - - -
    - -
    - -
    - sound settings - -
    - {% include "a11y/activity-log.html.j2" %} -
    -
    \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/settings.js b/nbconvert_a11y/templates/a11y/static/settings.js similarity index 78% rename from nbconvert_a11y/templates/a11y/settings.js rename to nbconvert_a11y/templates/a11y/static/settings.js index 4146a566..3e2e1f63 100644 --- a/nbconvert_a11y/templates/a11y/settings.js +++ b/nbconvert_a11y/templates/a11y/static/settings.js @@ -19,7 +19,7 @@ const BODY = document.querySelector("body"), SELECTORS = { "cell": "cell", }, "landmark": { "table": "presentation", - "body": "group", + "body": "presentation", "row": "region", "header": "none", "cell": "none", @@ -35,13 +35,13 @@ const BODY = document.querySelector("body"), SELECTORS = { function toggleColorScheme() { let value = document.forms.settings.elements["color-scheme"].value; let opposite = value == "dark" ? "light" : "dark"; - document.getElementById(`nb-${value}-theme`).removeAttribute("disabled"); - document.getElementById(`nb-${opposite}-theme`).setAttribute("disabled", null); + document.getElementById(`nb-${value}-theme`).removeAttribute("media", "screen"); + document.getElementById(`nb-${opposite}-theme`).setAttribute("media", "not screen"); document.querySelector(`head > meta[name="color-scheme"]`).setAttribute("content", value); activityLog(`${value} mode activated`) } function toggleRole() { - let value = document.forms.settings["table-role"].value; + let value = document.forms.settings["cell-navigation"].value; for (const [k, selector] of Object.entries(SELECTORS)) { document.querySelectorAll(selector).forEach( (x) => { @@ -55,22 +55,20 @@ function toggleRole() { } activityLog(`notebook cell navigation set to ${event.target.value}.`); } -document.forms.settings.elements["table-role"].forEach( - (x) => { x.addEventListener("change", toggleRole) } -) function flattenCss(x) { return Object.entries(x).map(x => x.join(": ")).join("; "); } function getStyle() { return { "--nb-font-size": document.forms.settings["font-size"].value, - "font-family": document.forms.settings["font-family"].value, + "--nb-font-family": document.forms.settings["font-family"].value, "--nb-margin": `${document.forms.settings.elements.margin.value}%`, - "line-height": `${document.forms.settings.elements["line-height"].value}`, + "--nb-line-height": `${document.forms.settings.elements["line-height"].value}`, } } function setStyle(msg) { BODY.setAttribute("style", flattenCss(getStyle())); + setWCAG(); toggleColorScheme(); toggleColorScheme(); activityLog(msg); } function changeFont() { @@ -90,12 +88,12 @@ function activityLog(msg, silent = false, first = false) { td = document.createElement("td"), out = document.createElement("output"), now = Date.now(); - time.setAttribute("datetime", now), time.setAttribute("aria-hidden", "true"); + time.setAttribute("datetime", now), th.setAttribute("aria-live", "off"), th.setAttribute("hidden", null); time.textContent = now; body.append(tr), th.append(time), tr.append(th), tr.append(td), td.append(out); silent ? out.setAttribute("aria-live", "off") : null; out.textContent = msg; - if (!i && document.forms.settings.elements.speech.checked) { + if (!i && document.forms.settings.elements["synthetic-speech"].checked) { // a non-screen reader solution for audible activity. speechSynthesis.speak(new SpeechSynthesisUtterance(msg)); } @@ -125,16 +123,14 @@ document.querySelectorAll("table[role=grid]").forEach( }) } ); -document.forms.settings.elements["color-scheme"].forEach( - (x) => { x.addEventListener("change", toggleColorScheme) } -); +document.forms.settings.elements["cell-navigation"].addEventListener("change", toggleRole) + +document.forms.settings.elements["color-scheme"].addEventListener("change", toggleColorScheme); document.forms.settings.elements["font-size"].addEventListener("change", (x) => { setStyle("change font size"); }); -document.forms.settings.elements["font-family"].forEach( - (x) => { x.addEventListener("change", changeFontFamily) } -); -document.forms.settings.elements.speech.addEventListener("change", (x) => { +document.forms.settings.elements["font-family"].addEventListener("change", changeFontFamily); +document.forms.settings.elements["synthetic-speech"].addEventListener("change", (x) => { activityLog("speech on") }); document.forms.settings.elements.margin.addEventListener("change", (x) => { @@ -143,7 +139,19 @@ document.forms.settings.elements.margin.addEventListener("change", (x) => { document.forms.settings.elements["line-height"].addEventListener("change", (x) => { setStyle("line height changed"); }); - +function setWCAG() { + var priority = document.forms.settings["accessibility-priority"].value.toLowerCase(); + ["a", "aa", "aaa"].forEach( + (x) => { + if (x == priority) { + BODY.classList.add(`wcag-${x}`) + } else { + BODY.classList.remove(`wcag-${x}`) + } + } + ); +} +document.forms.settings.elements["accessibility-priority"].addEventListener("change", setWCAG); function toggleActive() { if (document.forms.notebook.elements.edit.checked) { document.querySelectorAll("tr.cell>td>details>summary[inert]").forEach( @@ -169,8 +177,23 @@ function toggleActive() { activityLog("entering reading mode"); } } + + document.forms.notebook.elements.edit.addEventListener("change", () => toggleActive()) +function openDialogs() { + let trigger = document.querySelector("#nb-dialogs > details"); + Array.from( + document.querySelectorAll("#nb-dialogs dialog:not(.log)") + ).reverse().forEach( + x => { + trigger.getAttribute("open") === null ? x.show() : x.close(); + } + ); + event.target.focus(); +} + +setStyle("initialize saved settings.") // async function runSource(target) { // { // let pyodide = await loadPyodide(); diff --git a/nbconvert_a11y/templates/a11y/settings.jsonschema b/nbconvert_a11y/templates/a11y/static/settings.jsonschema similarity index 100% rename from nbconvert_a11y/templates/a11y/settings.jsonschema rename to nbconvert_a11y/templates/a11y/static/settings.jsonschema diff --git a/nbconvert_a11y/templates/a11y/static/style.css b/nbconvert_a11y/templates/a11y/static/style.css new file mode 100644 index 00000000..8b1e3b79 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/style.css @@ -0,0 +1,124 @@ +/* style variables */ +:root { + --nb-focus-width: 3px; + --nb-accent-color: auto; + --nb-background-color-dark: #2b2a33; + --nb-background-color-light: #FFFFFF; + --nb-margin: 5%; + --nb-font-size: 16px; + --nb-font-family: serif; + --nb-line-height: 1.5; +} + +body { + font-size: var(--nb-font-size); + font-family: var(--nb-font-family); + accent-color: var(--nb-accent-color); + margin-left: var(--nb-margin); + margin-right: var(--nb-margin); + line-height: var(--nb-line-height); + width: calc(100% - 2*var(--nb-margin)); +} + +/* align checkboxes with buttons */ +input[type="checkbox"] { + vertical-align: middle; +} + + +#cells .cell, +#cells tbody { + display: flex; + flex-direction: column; +} + +/* on firefox, the input and output become interactive when there is overflow.*/ +textarea[name=source], +.cell>td>details { + overflow: auto; + min-width: 0; +} + + +#nb-settings li::marker, +summary[inert]::marker { + content: ""; +} + +input, +select, +button { + font-family: inherit; + font-size: inherit; +} + +textarea { + font-family: monospace; + font-size: inherit; + line-height: inherit; + overflow: auto; + color: unset; +} + +textarea[name=source] { + box-sizing: border-box; + width: 100%; + resize: vertical; +} + +.cell:focus-within, +:focus-visible { + outline: max(var(--nb-focus-width), 1px) solid; + box-shadow: 0 0 0 calc(2 * max(var(--nb-focus-width), 1px)); +} + +#nb-dialogs details[open]~dialog { + position: relative; +} + + +#nb-dialogs details:not([open])~dialog:not([open]):not(:focus-within):not(:active), +legend:not(:focus-within):not(:active), +details.log:not([open])+table, +.visually-hidden:not(:focus-within):not(:active) { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +dialog form>* { + display: block; +} + +/* satisfy AA 2.5.8 minimum target requirement */ +.wcag-aa button, +.wcag-aa input[type=checkbox] { + min-height: 24px; + min-width: 24px; +} + +.wcag-aa a { + font-size: max(24px, var(--nb-font-size)); +} + +/* satisfy AAA 2.5.5 */ +.wcag-aaa button, +.wcag-aaa input[type=checkbox] { + min-height: 44px; + min-width: 44px; +} + +.wcag-aaa a { + font-size: max(44px, var(--nb-font-size)); +} + +.wcag-a details>summary[inert]~textarea[name=source], +.wcag-aa details>summary[inert]~textarea[name=source], +.wcag-aaa details>summary[inert]~textarea[name=source]~*, +details>summary:not([inert])~textarea[name=source]~* { + display: none; +} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/__main__.py b/nbconvert_a11y/templates/a11y/static/theme/__main__.py new file mode 100644 index 00000000..55b2e316 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/__main__.py @@ -0,0 +1,37 @@ +"""generate accessible pygments themes for testing""" + +from pathlib import Path + +HERE = Path(__file__).parent + +themes = """a11y-dark +a11y-light +a11y-high-contrast-dark +a11y-high-contrast-light +github-light +github-dark +github-light-colorblind +github-dark-colorblind +github-light-high-contrast +github-dark-high-contrast +gotthard-dark +gotthard-light +blinds-light +blinds-dark""".split() + + +# exclude greative, pitaya-smoothie because we are using light/dark theme pairs + + +def get_pygments(target, theme): + import pygments.formatters + + target.write_text( + f"""/** accessible pygments {theme} generated by {__name__}**/\n""" + + pygments.formatters.get_formatter_by_name("html", style=theme).get_style_defs() + ) + + +for theme in themes: + get_pygments(target := HERE / f"{theme}.css", theme) + print(f"wrote {theme} theme to {target.relative_to(HERE.parent.parent.parent)}") diff --git a/nbconvert_a11y/templates/a11y/static/theme/a11y-dark.css b/nbconvert_a11y/templates/a11y/static/theme/a11y-dark.css new file mode 100644 index 00000000..f877a676 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/a11y-dark.css @@ -0,0 +1,76 @@ +/** accessible pygments a11y-dark generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #ffd9002e } +.c { color: #d4d0ab } /* Comment */ +.err { color: #ffa07a } /* Error */ +.k { color: #dcc6e0 } /* Keyword */ +.l { color: #f5ab35 } /* Literal */ +.n { color: #f8f8f2 } /* Name */ +.o { color: #abe338 } /* Operator */ +.p { color: #f8f8f2 } /* Punctuation */ +.ch { color: #d4d0ab } /* Comment.Hashbang */ +.cm { color: #d4d0ab } /* Comment.Multiline */ +.cp { color: #d4d0ab } /* Comment.Preproc */ +.cpf { color: #d4d0ab } /* Comment.PreprocFile */ +.c1 { color: #d4d0ab } /* Comment.Single */ +.cs { color: #d4d0ab } /* Comment.Special */ +.gd { color: #00e0e0 } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gh { color: #00e0e0 } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #00e0e0 } /* Generic.Subheading */ +.kc { color: #dcc6e0 } /* Keyword.Constant */ +.kd { color: #dcc6e0 } /* Keyword.Declaration */ +.kn { color: #dcc6e0 } /* Keyword.Namespace */ +.kp { color: #dcc6e0 } /* Keyword.Pseudo */ +.kr { color: #dcc6e0 } /* Keyword.Reserved */ +.kt { color: #f5ab35 } /* Keyword.Type */ +.ld { color: #f5ab35 } /* Literal.Date */ +.m { color: #f5ab35 } /* Literal.Number */ +.s { color: #abe338 } /* Literal.String */ +.na { color: #ffd700 } /* Name.Attribute */ +.nb { color: #f5ab35 } /* Name.Builtin */ +.nc { color: #00e0e0 } /* Name.Class */ +.no { color: #00e0e0 } /* Name.Constant */ +.nd { color: #f5ab35 } /* Name.Decorator */ +.ni { color: #abe338 } /* Name.Entity */ +.ne { color: #dcc6e0 } /* Name.Exception */ +.nf { color: #00e0e0 } /* Name.Function */ +.nl { color: #f5ab35 } /* Name.Label */ +.nn { color: #f8f8f2 } /* Name.Namespace */ +.nx { color: #f8f8f2 } /* Name.Other */ +.py { color: #00e0e0 } /* Name.Property */ +.nt { color: #00e0e0 } /* Name.Tag */ +.nv { color: #ffa07a } /* Name.Variable */ +.ow { color: #dcc6e0 } /* Operator.Word */ +.pm { color: #f8f8f2 } /* Punctuation.Marker */ +.w { color: #f8f8f2 } /* Text.Whitespace */ +.mb { color: #f5ab35 } /* Literal.Number.Bin */ +.mf { color: #f5ab35 } /* Literal.Number.Float */ +.mh { color: #f5ab35 } /* Literal.Number.Hex */ +.mi { color: #f5ab35 } /* Literal.Number.Integer */ +.mo { color: #f5ab35 } /* Literal.Number.Oct */ +.sa { color: #abe338 } /* Literal.String.Affix */ +.sb { color: #abe338 } /* Literal.String.Backtick */ +.sc { color: #abe338 } /* Literal.String.Char */ +.dl { color: #abe338 } /* Literal.String.Delimiter */ +.sd { color: #abe338 } /* Literal.String.Doc */ +.s2 { color: #abe338 } /* Literal.String.Double */ +.se { color: #abe338 } /* Literal.String.Escape */ +.sh { color: #abe338 } /* Literal.String.Heredoc */ +.si { color: #abe338 } /* Literal.String.Interpol */ +.sx { color: #abe338 } /* Literal.String.Other */ +.sr { color: #ffa07a } /* Literal.String.Regex */ +.s1 { color: #abe338 } /* Literal.String.Single */ +.ss { color: #00e0e0 } /* Literal.String.Symbol */ +.bp { color: #f5ab35 } /* Name.Builtin.Pseudo */ +.fm { color: #00e0e0 } /* Name.Function.Magic */ +.vc { color: #ffa07a } /* Name.Variable.Class */ +.vg { color: #ffa07a } /* Name.Variable.Global */ +.vi { color: #ffa07a } /* Name.Variable.Instance */ +.vm { color: #f5ab35 } /* Name.Variable.Magic */ +.il { color: #f5ab35 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/a11y-high-contrast-dark.css b/nbconvert_a11y/templates/a11y/static/theme/a11y-high-contrast-dark.css new file mode 100644 index 00000000..d3501b14 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/a11y-high-contrast-dark.css @@ -0,0 +1,76 @@ +/** accessible pygments a11y-high-contrast-dark generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #ffd9002e } +.c { color: #ffd900 } /* Comment */ +.err { color: #ffa07a } /* Error */ +.k { color: #dcc6e0 } /* Keyword */ +.l { color: #ffd900 } /* Literal */ +.n { color: #f8f8f2 } /* Name */ +.o { color: #abe338 } /* Operator */ +.p { color: #f8f8f2 } /* Punctuation */ +.ch { color: #ffd900 } /* Comment.Hashbang */ +.cm { color: #ffd900 } /* Comment.Multiline */ +.cp { color: #ffd900 } /* Comment.Preproc */ +.cpf { color: #ffd900 } /* Comment.PreprocFile */ +.c1 { color: #ffd900 } /* Comment.Single */ +.cs { color: #ffd900 } /* Comment.Special */ +.gd { color: #00e0e0 } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gh { color: #00e0e0 } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #00e0e0 } /* Generic.Subheading */ +.kc { color: #dcc6e0 } /* Keyword.Constant */ +.kd { color: #dcc6e0 } /* Keyword.Declaration */ +.kn { color: #dcc6e0 } /* Keyword.Namespace */ +.kp { color: #dcc6e0 } /* Keyword.Pseudo */ +.kr { color: #dcc6e0 } /* Keyword.Reserved */ +.kt { color: #ffd900 } /* Keyword.Type */ +.ld { color: #ffd900 } /* Literal.Date */ +.m { color: #ffd900 } /* Literal.Number */ +.s { color: #abe338 } /* Literal.String */ +.na { color: #ffd900 } /* Name.Attribute */ +.nb { color: #ffd900 } /* Name.Builtin */ +.nc { color: #00e0e0 } /* Name.Class */ +.no { color: #00e0e0 } /* Name.Constant */ +.nd { color: #ffd900 } /* Name.Decorator */ +.ni { color: #abe338 } /* Name.Entity */ +.ne { color: #dcc6e0 } /* Name.Exception */ +.nf { color: #00e0e0 } /* Name.Function */ +.nl { color: #ffd900 } /* Name.Label */ +.nn { color: #f8f8f2 } /* Name.Namespace */ +.nx { color: #f8f8f2 } /* Name.Other */ +.py { color: #00e0e0 } /* Name.Property */ +.nt { color: #00e0e0 } /* Name.Tag */ +.nv { color: #ffa07a } /* Name.Variable */ +.ow { color: #dcc6e0 } /* Operator.Word */ +.pm { color: #f8f8f2 } /* Punctuation.Marker */ +.w { color: #f8f8f2 } /* Text.Whitespace */ +.mb { color: #ffd900 } /* Literal.Number.Bin */ +.mf { color: #ffd900 } /* Literal.Number.Float */ +.mh { color: #ffd900 } /* Literal.Number.Hex */ +.mi { color: #ffd900 } /* Literal.Number.Integer */ +.mo { color: #ffd900 } /* Literal.Number.Oct */ +.sa { color: #abe338 } /* Literal.String.Affix */ +.sb { color: #abe338 } /* Literal.String.Backtick */ +.sc { color: #abe338 } /* Literal.String.Char */ +.dl { color: #abe338 } /* Literal.String.Delimiter */ +.sd { color: #abe338 } /* Literal.String.Doc */ +.s2 { color: #abe338 } /* Literal.String.Double */ +.se { color: #abe338 } /* Literal.String.Escape */ +.sh { color: #abe338 } /* Literal.String.Heredoc */ +.si { color: #abe338 } /* Literal.String.Interpol */ +.sx { color: #abe338 } /* Literal.String.Other */ +.sr { color: #ffa07a } /* Literal.String.Regex */ +.s1 { color: #abe338 } /* Literal.String.Single */ +.ss { color: #00e0e0 } /* Literal.String.Symbol */ +.bp { color: #ffd900 } /* Name.Builtin.Pseudo */ +.fm { color: #00e0e0 } /* Name.Function.Magic */ +.vc { color: #ffa07a } /* Name.Variable.Class */ +.vg { color: #ffa07a } /* Name.Variable.Global */ +.vi { color: #ffa07a } /* Name.Variable.Instance */ +.vm { color: #ffd900 } /* Name.Variable.Magic */ +.il { color: #ffd900 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/a11y-high-contrast-light.css b/nbconvert_a11y/templates/a11y/static/theme/a11y-high-contrast-light.css new file mode 100644 index 00000000..2343e484 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/a11y-high-contrast-light.css @@ -0,0 +1,76 @@ +/** accessible pygments a11y-high-contrast-light generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #7971292e } +.c { color: #797129 } /* Comment */ +.err { color: #d91e18 } /* Error */ +.k { color: #7928a1 } /* Keyword */ +.l { color: #797129 } /* Literal */ +.n { color: #545454 } /* Name */ +.o { color: #008000 } /* Operator */ +.p { color: #545454 } /* Punctuation */ +.ch { color: #797129 } /* Comment.Hashbang */ +.cm { color: #797129 } /* Comment.Multiline */ +.cp { color: #797129 } /* Comment.Preproc */ +.cpf { color: #797129 } /* Comment.PreprocFile */ +.c1 { color: #797129 } /* Comment.Single */ +.cs { color: #797129 } /* Comment.Special */ +.gd { color: #007faa } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gh { color: #007faa } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #007faa } /* Generic.Subheading */ +.kc { color: #7928a1 } /* Keyword.Constant */ +.kd { color: #7928a1 } /* Keyword.Declaration */ +.kn { color: #7928a1 } /* Keyword.Namespace */ +.kp { color: #7928a1 } /* Keyword.Pseudo */ +.kr { color: #7928a1 } /* Keyword.Reserved */ +.kt { color: #797129 } /* Keyword.Type */ +.ld { color: #797129 } /* Literal.Date */ +.m { color: #797129 } /* Literal.Number */ +.s { color: #008000 } /* Literal.String */ +.na { color: #797129 } /* Name.Attribute */ +.nb { color: #797129 } /* Name.Builtin */ +.nc { color: #007faa } /* Name.Class */ +.no { color: #007faa } /* Name.Constant */ +.nd { color: #797129 } /* Name.Decorator */ +.ni { color: #008000 } /* Name.Entity */ +.ne { color: #7928a1 } /* Name.Exception */ +.nf { color: #007faa } /* Name.Function */ +.nl { color: #797129 } /* Name.Label */ +.nn { color: #545454 } /* Name.Namespace */ +.nx { color: #545454 } /* Name.Other */ +.py { color: #007faa } /* Name.Property */ +.nt { color: #007faa } /* Name.Tag */ +.nv { color: #d91e18 } /* Name.Variable */ +.ow { color: #7928a1 } /* Operator.Word */ +.pm { color: #545454 } /* Punctuation.Marker */ +.w { color: #545454 } /* Text.Whitespace */ +.mb { color: #797129 } /* Literal.Number.Bin */ +.mf { color: #797129 } /* Literal.Number.Float */ +.mh { color: #797129 } /* Literal.Number.Hex */ +.mi { color: #797129 } /* Literal.Number.Integer */ +.mo { color: #797129 } /* Literal.Number.Oct */ +.sa { color: #008000 } /* Literal.String.Affix */ +.sb { color: #008000 } /* Literal.String.Backtick */ +.sc { color: #008000 } /* Literal.String.Char */ +.dl { color: #008000 } /* Literal.String.Delimiter */ +.sd { color: #008000 } /* Literal.String.Doc */ +.s2 { color: #008000 } /* Literal.String.Double */ +.se { color: #008000 } /* Literal.String.Escape */ +.sh { color: #008000 } /* Literal.String.Heredoc */ +.si { color: #008000 } /* Literal.String.Interpol */ +.sx { color: #008000 } /* Literal.String.Other */ +.sr { color: #d91e18 } /* Literal.String.Regex */ +.s1 { color: #008000 } /* Literal.String.Single */ +.ss { color: #007faa } /* Literal.String.Symbol */ +.bp { color: #797129 } /* Name.Builtin.Pseudo */ +.fm { color: #007faa } /* Name.Function.Magic */ +.vc { color: #d91e18 } /* Name.Variable.Class */ +.vg { color: #d91e18 } /* Name.Variable.Global */ +.vi { color: #d91e18 } /* Name.Variable.Instance */ +.vm { color: #797129 } /* Name.Variable.Magic */ +.il { color: #797129 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/a11y-light.css b/nbconvert_a11y/templates/a11y/static/theme/a11y-light.css new file mode 100644 index 00000000..16ac95f9 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/a11y-light.css @@ -0,0 +1,76 @@ +/** accessible pygments a11y-light generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #7971292e } +.c { color: #696969 } /* Comment */ +.err { color: #d91e18 } /* Error */ +.k { color: #7928a1 } /* Keyword */ +.l { color: #aa5d00 } /* Literal */ +.n { color: #545454 } /* Name */ +.o { color: #008000 } /* Operator */ +.p { color: #545454 } /* Punctuation */ +.ch { color: #696969 } /* Comment.Hashbang */ +.cm { color: #696969 } /* Comment.Multiline */ +.cp { color: #696969 } /* Comment.Preproc */ +.cpf { color: #696969 } /* Comment.PreprocFile */ +.c1 { color: #696969 } /* Comment.Single */ +.cs { color: #696969 } /* Comment.Special */ +.gd { color: #007faa } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gh { color: #007faa } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #007faa } /* Generic.Subheading */ +.kc { color: #7928a1 } /* Keyword.Constant */ +.kd { color: #7928a1 } /* Keyword.Declaration */ +.kn { color: #7928a1 } /* Keyword.Namespace */ +.kp { color: #7928a1 } /* Keyword.Pseudo */ +.kr { color: #7928a1 } /* Keyword.Reserved */ +.kt { color: #aa5d00 } /* Keyword.Type */ +.ld { color: #aa5d00 } /* Literal.Date */ +.m { color: #aa5d00 } /* Literal.Number */ +.s { color: #008000 } /* Literal.String */ +.na { color: #aa5d00 } /* Name.Attribute */ +.nb { color: #aa5d00 } /* Name.Builtin */ +.nc { color: #007faa } /* Name.Class */ +.no { color: #007faa } /* Name.Constant */ +.nd { color: #aa5d00 } /* Name.Decorator */ +.ni { color: #008000 } /* Name.Entity */ +.ne { color: #7928a1 } /* Name.Exception */ +.nf { color: #007faa } /* Name.Function */ +.nl { color: #aa5d00 } /* Name.Label */ +.nn { color: #545454 } /* Name.Namespace */ +.nx { color: #545454 } /* Name.Other */ +.py { color: #007faa } /* Name.Property */ +.nt { color: #007faa } /* Name.Tag */ +.nv { color: #d91e18 } /* Name.Variable */ +.ow { color: #7928a1 } /* Operator.Word */ +.pm { color: #545454 } /* Punctuation.Marker */ +.w { color: #545454 } /* Text.Whitespace */ +.mb { color: #aa5d00 } /* Literal.Number.Bin */ +.mf { color: #aa5d00 } /* Literal.Number.Float */ +.mh { color: #aa5d00 } /* Literal.Number.Hex */ +.mi { color: #aa5d00 } /* Literal.Number.Integer */ +.mo { color: #aa5d00 } /* Literal.Number.Oct */ +.sa { color: #008000 } /* Literal.String.Affix */ +.sb { color: #008000 } /* Literal.String.Backtick */ +.sc { color: #008000 } /* Literal.String.Char */ +.dl { color: #008000 } /* Literal.String.Delimiter */ +.sd { color: #008000 } /* Literal.String.Doc */ +.s2 { color: #008000 } /* Literal.String.Double */ +.se { color: #008000 } /* Literal.String.Escape */ +.sh { color: #008000 } /* Literal.String.Heredoc */ +.si { color: #008000 } /* Literal.String.Interpol */ +.sx { color: #008000 } /* Literal.String.Other */ +.sr { color: #d91e18 } /* Literal.String.Regex */ +.s1 { color: #008000 } /* Literal.String.Single */ +.ss { color: #007faa } /* Literal.String.Symbol */ +.bp { color: #aa5d00 } /* Name.Builtin.Pseudo */ +.fm { color: #007faa } /* Name.Function.Magic */ +.vc { color: #d91e18 } /* Name.Variable.Class */ +.vg { color: #d91e18 } /* Name.Variable.Global */ +.vi { color: #d91e18 } /* Name.Variable.Instance */ +.vm { color: #aa5d00 } /* Name.Variable.Magic */ +.il { color: #aa5d00 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/blinds-dark.css b/nbconvert_a11y/templates/a11y/static/theme/blinds-dark.css new file mode 100644 index 00000000..6899fd3a --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/blinds-dark.css @@ -0,0 +1,76 @@ +/** accessible pygments blinds-dark generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #66666691 } +.c { color: #8C8C8C } /* Comment */ +.err { color: #5391CF } /* Error */ +.k { color: #D166A3 } /* Keyword */ +.l { color: #5391CF } /* Literal */ +.n { color: #5391CF } /* Name */ +.o { color: #ee6677 } /* Operator */ +.p { color: #bbbbbb } /* Punctuation */ +.ch { color: #8C8C8C } /* Comment.Hashbang */ +.cm { color: #8C8C8C } /* Comment.Multiline */ +.cp { color: #8C8C8C } /* Comment.Preproc */ +.cpf { color: #8C8C8C } /* Comment.PreprocFile */ +.c1 { color: #8C8C8C } /* Comment.Single */ +.cs { color: #8C8C8C } /* Comment.Special */ +.gd { color: #5391CF } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gh { color: #5391CF } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #5391CF } /* Generic.Subheading */ +.kc { color: #D166A3 } /* Keyword.Constant */ +.kd { color: #D166A3 } /* Keyword.Declaration */ +.kn { color: #D166A3 } /* Keyword.Namespace */ +.kp { color: #D166A3 } /* Keyword.Pseudo */ +.kr { color: #D166A3 } /* Keyword.Reserved */ +.kt { color: #66ccee } /* Keyword.Type */ +.ld { color: #5391CF } /* Literal.Date */ +.m { color: #bbbbbb } /* Literal.Number */ +.s { color: #D166A3 } /* Literal.String */ +.na { color: #D166A3 } /* Name.Attribute */ +.nb { color: #66ccee } /* Name.Builtin */ +.nc { color: #ee6677 } /* Name.Class */ +.no { color: #ee6677 } /* Name.Constant */ +.nd { color: #ccbb44 } /* Name.Decorator */ +.ni { color: #ccbb44 } /* Name.Entity */ +.ne { color: #5391CF } /* Name.Exception */ +.nf { color: #66ccee } /* Name.Function */ +.nl { color: #ee6677 } /* Name.Label */ +.nn { color: #66ccee } /* Name.Namespace */ +.nx { color: #5391CF } /* Name.Other */ +.py { color: #5391CF } /* Name.Property */ +.nt { color: #66ccee } /* Name.Tag */ +.nv { color: #5391CF } /* Name.Variable */ +.ow { color: #D166A3 } /* Operator.Word */ +.pm { color: #bbbbbb } /* Punctuation.Marker */ +.w { color: #bbbbbb } /* Text.Whitespace */ +.mb { color: #bbbbbb } /* Literal.Number.Bin */ +.mf { color: #bbbbbb } /* Literal.Number.Float */ +.mh { color: #bbbbbb } /* Literal.Number.Hex */ +.mi { color: #bbbbbb } /* Literal.Number.Integer */ +.mo { color: #bbbbbb } /* Literal.Number.Oct */ +.sa { color: #D166A3 } /* Literal.String.Affix */ +.sb { color: #D166A3 } /* Literal.String.Backtick */ +.sc { color: #D166A3 } /* Literal.String.Char */ +.dl { color: #D166A3 } /* Literal.String.Delimiter */ +.sd { color: #D166A3 } /* Literal.String.Doc */ +.s2 { color: #D166A3 } /* Literal.String.Double */ +.se { color: #D166A3 } /* Literal.String.Escape */ +.sh { color: #D166A3 } /* Literal.String.Heredoc */ +.si { color: #D166A3 } /* Literal.String.Interpol */ +.sx { color: #D166A3 } /* Literal.String.Other */ +.sr { color: #D166A3 } /* Literal.String.Regex */ +.s1 { color: #D166A3 } /* Literal.String.Single */ +.ss { color: #ee6677 } /* Literal.String.Symbol */ +.bp { color: #66ccee } /* Name.Builtin.Pseudo */ +.fm { color: #66ccee } /* Name.Function.Magic */ +.vc { color: #5391CF } /* Name.Variable.Class */ +.vg { color: #5391CF } /* Name.Variable.Global */ +.vi { color: #5391CF } /* Name.Variable.Instance */ +.vm { color: #ee6677 } /* Name.Variable.Magic */ +.il { color: #bbbbbb } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/blinds-light.css b/nbconvert_a11y/templates/a11y/static/theme/blinds-light.css new file mode 100644 index 00000000..d34fa1bf --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/blinds-light.css @@ -0,0 +1,76 @@ +/** accessible pygments blinds-light generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #add6ff } +.c { color: #737373 } /* Comment */ +.err { color: #0072b2 } /* Error */ +.k { color: #CC398B } /* Keyword */ +.l { color: #0072b2 } /* Literal */ +.n { color: #0072b2 } /* Name */ +.o { color: #BF5400 } /* Operator */ +.p { color: #000000 } /* Punctuation */ +.ch { color: #737373 } /* Comment.Hashbang */ +.cm { color: #737373 } /* Comment.Multiline */ +.cp { color: #737373 } /* Comment.Preproc */ +.cpf { color: #737373 } /* Comment.PreprocFile */ +.c1 { color: #737373 } /* Comment.Single */ +.cs { color: #737373 } /* Comment.Special */ +.gd { color: #0072b2 } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gh { color: #0072b2 } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #0072b2 } /* Generic.Subheading */ +.kc { color: #CC398B } /* Keyword.Constant */ +.kd { color: #CC398B } /* Keyword.Declaration */ +.kn { color: #CC398B } /* Keyword.Namespace */ +.kp { color: #CC398B } /* Keyword.Pseudo */ +.kr { color: #CC398B } /* Keyword.Reserved */ +.kt { color: #008561 } /* Keyword.Type */ +.ld { color: #0072b2 } /* Literal.Date */ +.m { color: #000000 } /* Literal.Number */ +.s { color: #CC398B } /* Literal.String */ +.na { color: #CC398B } /* Name.Attribute */ +.nb { color: #008561 } /* Name.Builtin */ +.nc { color: #BF5400 } /* Name.Class */ +.no { color: #BF5400 } /* Name.Constant */ +.nd { color: #996B00 } /* Name.Decorator */ +.ni { color: #0072b2 } /* Name.Entity */ +.ne { color: #0072b2 } /* Name.Exception */ +.nf { color: #008561 } /* Name.Function */ +.nl { color: #BF5400 } /* Name.Label */ +.nn { color: #008561 } /* Name.Namespace */ +.nx { color: #0072b2 } /* Name.Other */ +.py { color: #0072b2 } /* Name.Property */ +.nt { color: #008561 } /* Name.Tag */ +.nv { color: #0072b2 } /* Name.Variable */ +.ow { color: #CC398B } /* Operator.Word */ +.pm { color: #000000 } /* Punctuation.Marker */ +.w { color: #000000 } /* Text.Whitespace */ +.mb { color: #000000 } /* Literal.Number.Bin */ +.mf { color: #000000 } /* Literal.Number.Float */ +.mh { color: #000000 } /* Literal.Number.Hex */ +.mi { color: #000000 } /* Literal.Number.Integer */ +.mo { color: #000000 } /* Literal.Number.Oct */ +.sa { color: #CC398B } /* Literal.String.Affix */ +.sb { color: #CC398B } /* Literal.String.Backtick */ +.sc { color: #CC398B } /* Literal.String.Char */ +.dl { color: #CC398B } /* Literal.String.Delimiter */ +.sd { color: #CC398B } /* Literal.String.Doc */ +.s2 { color: #CC398B } /* Literal.String.Double */ +.se { color: #CC398B } /* Literal.String.Escape */ +.sh { color: #CC398B } /* Literal.String.Heredoc */ +.si { color: #CC398B } /* Literal.String.Interpol */ +.sx { color: #CC398B } /* Literal.String.Other */ +.sr { color: #CC398B } /* Literal.String.Regex */ +.s1 { color: #CC398B } /* Literal.String.Single */ +.ss { color: #BF5400 } /* Literal.String.Symbol */ +.bp { color: #008561 } /* Name.Builtin.Pseudo */ +.fm { color: #008561 } /* Name.Function.Magic */ +.vc { color: #0072b2 } /* Name.Variable.Class */ +.vg { color: #0072b2 } /* Name.Variable.Global */ +.vi { color: #0072b2 } /* Name.Variable.Instance */ +.vm { color: #BF5400 } /* Name.Variable.Magic */ +.il { color: #000000 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/github-dark-colorblind.css b/nbconvert_a11y/templates/a11y/static/theme/github-dark-colorblind.css new file mode 100644 index 00000000..700e4765 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/github-dark-colorblind.css @@ -0,0 +1,77 @@ +/** accessible pygments github-dark-colorblind generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #58a6ff70 } +.c { color: #b1bac4 } /* Comment */ +.err { color: #ec8e2c } /* Error */ +.k { color: #ec8e2c } /* Keyword */ +.l { color: #fdac54 } /* Literal */ +.n { color: #d2a8ff } /* Name */ +.o { color: #a5d6ff } /* Operator */ +.p { color: #C9D1D9 } /* Punctuation */ +.ch { color: #b1bac4 } /* Comment.Hashbang */ +.cm { color: #b1bac4 } /* Comment.Multiline */ +.cp { color: #b1bac4 } /* Comment.Preproc */ +.cpf { color: #b1bac4 } /* Comment.PreprocFile */ +.c1 { color: #b1bac4 } /* Comment.Single */ +.cs { color: #b1bac4 } /* Comment.Special */ +.gd { color: #79c0ff } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #ec8e2c } /* Generic.Error */ +.gh { color: #79c0ff } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #79c0ff } /* Generic.Subheading */ +.kc { color: #79c0ff } /* Keyword.Constant */ +.kd { color: #ec8e2c } /* Keyword.Declaration */ +.kn { color: #ec8e2c } /* Keyword.Namespace */ +.kp { color: #ec8e2c } /* Keyword.Pseudo */ +.kr { color: #ec8e2c } /* Keyword.Reserved */ +.kt { color: #ec8e2c } /* Keyword.Type */ +.ld { color: #fdac54 } /* Literal.Date */ +.m { color: #fdac54 } /* Literal.Number */ +.s { color: #79c0ff } /* Literal.String */ +.na { color: #fdac54 } /* Name.Attribute */ +.nb { color: #fdac54 } /* Name.Builtin */ +.nc { color: #79c0ff } /* Name.Class */ +.no { color: #79c0ff } /* Name.Constant */ +.nd { color: #fdac54 } /* Name.Decorator */ +.ni { color: #a5d6ff } /* Name.Entity */ +.ne { color: #d2a8ff } /* Name.Exception */ +.nf { color: #79c0ff } /* Name.Function */ +.nl { color: #fdac54 } /* Name.Label */ +.nn { color: #C9D1D9 } /* Name.Namespace */ +.nx { color: #d2a8ff } /* Name.Other */ +.py { color: #79c0ff } /* Name.Property */ +.nt { color: #a5d6ff } /* Name.Tag */ +.nv { color: #fdac54 } /* Name.Variable */ +.ow { color: #d2a8ff } /* Operator.Word */ +.pm { color: #C9D1D9 } /* Punctuation.Marker */ +.w { color: #C9D1D9 } /* Text.Whitespace */ +.mb { color: #fdac54 } /* Literal.Number.Bin */ +.mf { color: #fdac54 } /* Literal.Number.Float */ +.mh { color: #fdac54 } /* Literal.Number.Hex */ +.mi { color: #fdac54 } /* Literal.Number.Integer */ +.mo { color: #fdac54 } /* Literal.Number.Oct */ +.sa { color: #79c0ff } /* Literal.String.Affix */ +.sb { color: #79c0ff } /* Literal.String.Backtick */ +.sc { color: #79c0ff } /* Literal.String.Char */ +.dl { color: #79c0ff } /* Literal.String.Delimiter */ +.sd { color: #79c0ff } /* Literal.String.Doc */ +.s2 { color: #79c0ff } /* Literal.String.Double */ +.se { color: #79c0ff } /* Literal.String.Escape */ +.sh { color: #79c0ff } /* Literal.String.Heredoc */ +.si { color: #79c0ff } /* Literal.String.Interpol */ +.sx { color: #79c0ff } /* Literal.String.Other */ +.sr { color: #79c0ff } /* Literal.String.Regex */ +.s1 { color: #79c0ff } /* Literal.String.Single */ +.ss { color: #79c0ff } /* Literal.String.Symbol */ +.bp { color: #fdac54 } /* Name.Builtin.Pseudo */ +.fm { color: #79c0ff } /* Name.Function.Magic */ +.vc { color: #fdac54 } /* Name.Variable.Class */ +.vg { color: #fdac54 } /* Name.Variable.Global */ +.vi { color: #fdac54 } /* Name.Variable.Instance */ +.vm { color: #fdac54 } /* Name.Variable.Magic */ +.il { color: #fdac54 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/github-dark-high-contrast.css b/nbconvert_a11y/templates/a11y/static/theme/github-dark-high-contrast.css new file mode 100644 index 00000000..f89d03b1 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/github-dark-high-contrast.css @@ -0,0 +1,77 @@ +/** accessible pygments github-dark-high-contrast generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #58a6ff70 } +.c { color: #d9dee3 } /* Comment */ +.err { color: #ff9492 } /* Error */ +.k { color: #ff9492 } /* Keyword */ +.l { color: #ffb757 } /* Literal */ +.n { color: #dbb7ff } /* Name */ +.o { color: #72f088 } /* Operator */ +.p { color: #C9D1D9 } /* Punctuation */ +.ch { color: #d9dee3 } /* Comment.Hashbang */ +.cm { color: #d9dee3 } /* Comment.Multiline */ +.cp { color: #d9dee3 } /* Comment.Preproc */ +.cpf { color: #d9dee3 } /* Comment.PreprocFile */ +.c1 { color: #d9dee3 } /* Comment.Single */ +.cs { color: #d9dee3 } /* Comment.Special */ +.gd { color: #91cbff } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #ff9492 } /* Generic.Error */ +.gh { color: #91cbff } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #91cbff } /* Generic.Subheading */ +.kc { color: #91cbff } /* Keyword.Constant */ +.kd { color: #ff9492 } /* Keyword.Declaration */ +.kn { color: #ff9492 } /* Keyword.Namespace */ +.kp { color: #ff9492 } /* Keyword.Pseudo */ +.kr { color: #ff9492 } /* Keyword.Reserved */ +.kt { color: #ff9492 } /* Keyword.Type */ +.ld { color: #ffb757 } /* Literal.Date */ +.m { color: #ffb757 } /* Literal.Number */ +.s { color: #91cbff } /* Literal.String */ +.na { color: #ffb757 } /* Name.Attribute */ +.nb { color: #ffb757 } /* Name.Builtin */ +.nc { color: #91cbff } /* Name.Class */ +.no { color: #91cbff } /* Name.Constant */ +.nd { color: #ffb757 } /* Name.Decorator */ +.ni { color: #72f088 } /* Name.Entity */ +.ne { color: #dbb7ff } /* Name.Exception */ +.nf { color: #91cbff } /* Name.Function */ +.nl { color: #ffb757 } /* Name.Label */ +.nn { color: #C9D1D9 } /* Name.Namespace */ +.nx { color: #dbb7ff } /* Name.Other */ +.py { color: #91cbff } /* Name.Property */ +.nt { color: #72f088 } /* Name.Tag */ +.nv { color: #ffb757 } /* Name.Variable */ +.ow { color: #dbb7ff } /* Operator.Word */ +.pm { color: #C9D1D9 } /* Punctuation.Marker */ +.w { color: #C9D1D9 } /* Text.Whitespace */ +.mb { color: #ffb757 } /* Literal.Number.Bin */ +.mf { color: #ffb757 } /* Literal.Number.Float */ +.mh { color: #ffb757 } /* Literal.Number.Hex */ +.mi { color: #ffb757 } /* Literal.Number.Integer */ +.mo { color: #ffb757 } /* Literal.Number.Oct */ +.sa { color: #91cbff } /* Literal.String.Affix */ +.sb { color: #91cbff } /* Literal.String.Backtick */ +.sc { color: #91cbff } /* Literal.String.Char */ +.dl { color: #91cbff } /* Literal.String.Delimiter */ +.sd { color: #91cbff } /* Literal.String.Doc */ +.s2 { color: #91cbff } /* Literal.String.Double */ +.se { color: #91cbff } /* Literal.String.Escape */ +.sh { color: #91cbff } /* Literal.String.Heredoc */ +.si { color: #91cbff } /* Literal.String.Interpol */ +.sx { color: #91cbff } /* Literal.String.Other */ +.sr { color: #91cbff } /* Literal.String.Regex */ +.s1 { color: #91cbff } /* Literal.String.Single */ +.ss { color: #91cbff } /* Literal.String.Symbol */ +.bp { color: #ffb757 } /* Name.Builtin.Pseudo */ +.fm { color: #91cbff } /* Name.Function.Magic */ +.vc { color: #ffb757 } /* Name.Variable.Class */ +.vg { color: #ffb757 } /* Name.Variable.Global */ +.vi { color: #ffb757 } /* Name.Variable.Instance */ +.vm { color: #ffb757 } /* Name.Variable.Magic */ +.il { color: #ffb757 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/github-dark.css b/nbconvert_a11y/templates/a11y/static/theme/github-dark.css new file mode 100644 index 00000000..2e0d0f60 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/github-dark.css @@ -0,0 +1,86 @@ +/** accessible pygments github-dark generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: #6e7681; background-color: #0d1117; padding-left: 5px; padding-right: 5px; } +span.linenos { color: #6e7681; background-color: #0d1117; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #e6edf3; background-color: #6e7681; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #e6edf3; background-color: #6e7681; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #6e7681 } +.c { color: #8b949e; font-style: italic } /* Comment */ +.err { color: #f85149 } /* Error */ +.esc { color: #e6edf3 } /* Escape */ +.g { color: #e6edf3 } /* Generic */ +.k { color: #ff7b72 } /* Keyword */ +.l { color: #a5d6ff } /* Literal */ +.n { color: #e6edf3 } /* Name */ +.o { color: #ff7b72; font-weight: bold } /* Operator */ +.x { color: #e6edf3 } /* Other */ +.p { color: #e6edf3 } /* Punctuation */ +.ch { color: #8b949e; font-style: italic } /* Comment.Hashbang */ +.cm { color: #8b949e; font-style: italic } /* Comment.Multiline */ +.cp { color: #8b949e; font-weight: bold; font-style: italic } /* Comment.Preproc */ +.cpf { color: #8b949e; font-style: italic } /* Comment.PreprocFile */ +.c1 { color: #8b949e; font-style: italic } /* Comment.Single */ +.cs { color: #8b949e; font-weight: bold; font-style: italic } /* Comment.Special */ +.gd { color: #ffa198; background-color: #490202 } /* Generic.Deleted */ +.ge { color: #e6edf3; font-style: italic } /* Generic.Emph */ +.ges { color: #e6edf3; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.gr { color: #ffa198 } /* Generic.Error */ +.gh { color: #79c0ff; font-weight: bold } /* Generic.Heading */ +.gi { color: #56d364; background-color: #0f5323 } /* Generic.Inserted */ +.go { color: #8b949e } /* Generic.Output */ +.gp { color: #8b949e } /* Generic.Prompt */ +.gs { color: #e6edf3; font-weight: bold } /* Generic.Strong */ +.gu { color: #79c0ff } /* Generic.Subheading */ +.gt { color: #ff7b72 } /* Generic.Traceback */ +.g-Underline { color: #e6edf3; text-decoration: underline } /* Generic.Underline */ +.kc { color: #79c0ff } /* Keyword.Constant */ +.kd { color: #ff7b72 } /* Keyword.Declaration */ +.kn { color: #ff7b72 } /* Keyword.Namespace */ +.kp { color: #79c0ff } /* Keyword.Pseudo */ +.kr { color: #ff7b72 } /* Keyword.Reserved */ +.kt { color: #ff7b72 } /* Keyword.Type */ +.ld { color: #79c0ff } /* Literal.Date */ +.m { color: #a5d6ff } /* Literal.Number */ +.s { color: #a5d6ff } /* Literal.String */ +.na { color: #e6edf3 } /* Name.Attribute */ +.nb { color: #e6edf3 } /* Name.Builtin */ +.nc { color: #f0883e; font-weight: bold } /* Name.Class */ +.no { color: #79c0ff; font-weight: bold } /* Name.Constant */ +.nd { color: #d2a8ff; font-weight: bold } /* Name.Decorator */ +.ni { color: #ffa657 } /* Name.Entity */ +.ne { color: #f0883e; font-weight: bold } /* Name.Exception */ +.nf { color: #d2a8ff; font-weight: bold } /* Name.Function */ +.nl { color: #79c0ff; font-weight: bold } /* Name.Label */ +.nn { color: #ff7b72 } /* Name.Namespace */ +.nx { color: #e6edf3 } /* Name.Other */ +.py { color: #79c0ff } /* Name.Property */ +.nt { color: #7ee787 } /* Name.Tag */ +.nv { color: #79c0ff } /* Name.Variable */ +.ow { color: #ff7b72; font-weight: bold } /* Operator.Word */ +.pm { color: #e6edf3 } /* Punctuation.Marker */ +.w { color: #6e7681 } /* Text.Whitespace */ +.mb { color: #a5d6ff } /* Literal.Number.Bin */ +.mf { color: #a5d6ff } /* Literal.Number.Float */ +.mh { color: #a5d6ff } /* Literal.Number.Hex */ +.mi { color: #a5d6ff } /* Literal.Number.Integer */ +.mo { color: #a5d6ff } /* Literal.Number.Oct */ +.sa { color: #79c0ff } /* Literal.String.Affix */ +.sb { color: #a5d6ff } /* Literal.String.Backtick */ +.sc { color: #a5d6ff } /* Literal.String.Char */ +.dl { color: #79c0ff } /* Literal.String.Delimiter */ +.sd { color: #a5d6ff } /* Literal.String.Doc */ +.s2 { color: #a5d6ff } /* Literal.String.Double */ +.se { color: #79c0ff } /* Literal.String.Escape */ +.sh { color: #79c0ff } /* Literal.String.Heredoc */ +.si { color: #a5d6ff } /* Literal.String.Interpol */ +.sx { color: #a5d6ff } /* Literal.String.Other */ +.sr { color: #79c0ff } /* Literal.String.Regex */ +.s1 { color: #a5d6ff } /* Literal.String.Single */ +.ss { color: #a5d6ff } /* Literal.String.Symbol */ +.bp { color: #e6edf3 } /* Name.Builtin.Pseudo */ +.fm { color: #d2a8ff; font-weight: bold } /* Name.Function.Magic */ +.vc { color: #79c0ff } /* Name.Variable.Class */ +.vg { color: #79c0ff } /* Name.Variable.Global */ +.vi { color: #79c0ff } /* Name.Variable.Instance */ +.vm { color: #79c0ff } /* Name.Variable.Magic */ +.il { color: #a5d6ff } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/github-light-colorblind.css b/nbconvert_a11y/templates/a11y/static/theme/github-light-colorblind.css new file mode 100644 index 00000000..8ac90a9c --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/github-light-colorblind.css @@ -0,0 +1,77 @@ +/** accessible pygments github-light-colorblind generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #0969da4a } +.c { color: #6e7781 } /* Comment */ +.err { color: #b35900 } /* Error */ +.k { color: #b35900 } /* Keyword */ +.l { color: #8a4600 } /* Literal */ +.n { color: #8250df } /* Name */ +.o { color: #0550ae } /* Operator */ +.p { color: #24292f } /* Punctuation */ +.ch { color: #6e7781 } /* Comment.Hashbang */ +.cm { color: #6e7781 } /* Comment.Multiline */ +.cp { color: #6e7781 } /* Comment.Preproc */ +.cpf { color: #6e7781 } /* Comment.PreprocFile */ +.c1 { color: #6e7781 } /* Comment.Single */ +.cs { color: #6e7781 } /* Comment.Special */ +.gd { color: #0550ae } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #b35900 } /* Generic.Error */ +.gh { color: #0550ae } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #0550ae } /* Generic.Subheading */ +.kc { color: #0550ae } /* Keyword.Constant */ +.kd { color: #b35900 } /* Keyword.Declaration */ +.kn { color: #b35900 } /* Keyword.Namespace */ +.kp { color: #b35900 } /* Keyword.Pseudo */ +.kr { color: #b35900 } /* Keyword.Reserved */ +.kt { color: #b35900 } /* Keyword.Type */ +.ld { color: #8a4600 } /* Literal.Date */ +.m { color: #8a4600 } /* Literal.Number */ +.s { color: #0550ae } /* Literal.String */ +.na { color: #8a4600 } /* Name.Attribute */ +.nb { color: #8a4600 } /* Name.Builtin */ +.nc { color: #0550ae } /* Name.Class */ +.no { color: #0550ae } /* Name.Constant */ +.nd { color: #8a4600 } /* Name.Decorator */ +.ni { color: #0550ae } /* Name.Entity */ +.ne { color: #8250df } /* Name.Exception */ +.nf { color: #0550ae } /* Name.Function */ +.nl { color: #8a4600 } /* Name.Label */ +.nn { color: #24292f } /* Name.Namespace */ +.nx { color: #8250df } /* Name.Other */ +.py { color: #0550ae } /* Name.Property */ +.nt { color: #0550ae } /* Name.Tag */ +.nv { color: #8a4600 } /* Name.Variable */ +.ow { color: #8250df } /* Operator.Word */ +.pm { color: #24292f } /* Punctuation.Marker */ +.w { color: #24292f } /* Text.Whitespace */ +.mb { color: #8a4600 } /* Literal.Number.Bin */ +.mf { color: #8a4600 } /* Literal.Number.Float */ +.mh { color: #8a4600 } /* Literal.Number.Hex */ +.mi { color: #8a4600 } /* Literal.Number.Integer */ +.mo { color: #8a4600 } /* Literal.Number.Oct */ +.sa { color: #0550ae } /* Literal.String.Affix */ +.sb { color: #0550ae } /* Literal.String.Backtick */ +.sc { color: #0550ae } /* Literal.String.Char */ +.dl { color: #0550ae } /* Literal.String.Delimiter */ +.sd { color: #0550ae } /* Literal.String.Doc */ +.s2 { color: #0550ae } /* Literal.String.Double */ +.se { color: #0550ae } /* Literal.String.Escape */ +.sh { color: #0550ae } /* Literal.String.Heredoc */ +.si { color: #0550ae } /* Literal.String.Interpol */ +.sx { color: #0550ae } /* Literal.String.Other */ +.sr { color: #0550ae } /* Literal.String.Regex */ +.s1 { color: #0550ae } /* Literal.String.Single */ +.ss { color: #0550ae } /* Literal.String.Symbol */ +.bp { color: #8a4600 } /* Name.Builtin.Pseudo */ +.fm { color: #0550ae } /* Name.Function.Magic */ +.vc { color: #8a4600 } /* Name.Variable.Class */ +.vg { color: #8a4600 } /* Name.Variable.Global */ +.vi { color: #8a4600 } /* Name.Variable.Instance */ +.vm { color: #8a4600 } /* Name.Variable.Magic */ +.il { color: #8a4600 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/github-light-high-contrast.css b/nbconvert_a11y/templates/a11y/static/theme/github-light-high-contrast.css new file mode 100644 index 00000000..9e5da25a --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/github-light-high-contrast.css @@ -0,0 +1,77 @@ +/** accessible pygments github-light-high-contrast generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #0969da4a } +.c { color: #66707b } /* Comment */ +.err { color: #a0111f } /* Error */ +.k { color: #a0111f } /* Keyword */ +.l { color: #702c00 } /* Literal */ +.n { color: #622cbc } /* Name */ +.o { color: #024c1a } /* Operator */ +.p { color: #24292f } /* Punctuation */ +.ch { color: #66707b } /* Comment.Hashbang */ +.cm { color: #66707b } /* Comment.Multiline */ +.cp { color: #66707b } /* Comment.Preproc */ +.cpf { color: #66707b } /* Comment.PreprocFile */ +.c1 { color: #66707b } /* Comment.Single */ +.cs { color: #66707b } /* Comment.Special */ +.gd { color: #023b95 } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #a0111f } /* Generic.Error */ +.gh { color: #023b95 } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #023b95 } /* Generic.Subheading */ +.kc { color: #023b95 } /* Keyword.Constant */ +.kd { color: #a0111f } /* Keyword.Declaration */ +.kn { color: #a0111f } /* Keyword.Namespace */ +.kp { color: #a0111f } /* Keyword.Pseudo */ +.kr { color: #a0111f } /* Keyword.Reserved */ +.kt { color: #a0111f } /* Keyword.Type */ +.ld { color: #702c00 } /* Literal.Date */ +.m { color: #702c00 } /* Literal.Number */ +.s { color: #023b95 } /* Literal.String */ +.na { color: #702c00 } /* Name.Attribute */ +.nb { color: #702c00 } /* Name.Builtin */ +.nc { color: #023b95 } /* Name.Class */ +.no { color: #023b95 } /* Name.Constant */ +.nd { color: #702c00 } /* Name.Decorator */ +.ni { color: #024c1a } /* Name.Entity */ +.ne { color: #622cbc } /* Name.Exception */ +.nf { color: #023b95 } /* Name.Function */ +.nl { color: #702c00 } /* Name.Label */ +.nn { color: #24292f } /* Name.Namespace */ +.nx { color: #622cbc } /* Name.Other */ +.py { color: #023b95 } /* Name.Property */ +.nt { color: #024c1a } /* Name.Tag */ +.nv { color: #702c00 } /* Name.Variable */ +.ow { color: #622cbc } /* Operator.Word */ +.pm { color: #24292f } /* Punctuation.Marker */ +.w { color: #24292f } /* Text.Whitespace */ +.mb { color: #702c00 } /* Literal.Number.Bin */ +.mf { color: #702c00 } /* Literal.Number.Float */ +.mh { color: #702c00 } /* Literal.Number.Hex */ +.mi { color: #702c00 } /* Literal.Number.Integer */ +.mo { color: #702c00 } /* Literal.Number.Oct */ +.sa { color: #023b95 } /* Literal.String.Affix */ +.sb { color: #023b95 } /* Literal.String.Backtick */ +.sc { color: #023b95 } /* Literal.String.Char */ +.dl { color: #023b95 } /* Literal.String.Delimiter */ +.sd { color: #023b95 } /* Literal.String.Doc */ +.s2 { color: #023b95 } /* Literal.String.Double */ +.se { color: #023b95 } /* Literal.String.Escape */ +.sh { color: #023b95 } /* Literal.String.Heredoc */ +.si { color: #023b95 } /* Literal.String.Interpol */ +.sx { color: #023b95 } /* Literal.String.Other */ +.sr { color: #023b95 } /* Literal.String.Regex */ +.s1 { color: #023b95 } /* Literal.String.Single */ +.ss { color: #023b95 } /* Literal.String.Symbol */ +.bp { color: #702c00 } /* Name.Builtin.Pseudo */ +.fm { color: #023b95 } /* Name.Function.Magic */ +.vc { color: #702c00 } /* Name.Variable.Class */ +.vg { color: #702c00 } /* Name.Variable.Global */ +.vi { color: #702c00 } /* Name.Variable.Instance */ +.vm { color: #702c00 } /* Name.Variable.Magic */ +.il { color: #702c00 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/github-light.css b/nbconvert_a11y/templates/a11y/static/theme/github-light.css new file mode 100644 index 00000000..c991c2f0 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/github-light.css @@ -0,0 +1,77 @@ +/** accessible pygments github-light generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #0969da4a } +.c { color: #6e7781 } /* Comment */ +.err { color: #cf222e } /* Error */ +.k { color: #cf222e } /* Keyword */ +.l { color: #953800 } /* Literal */ +.n { color: #8250df } /* Name */ +.o { color: #116329 } /* Operator */ +.p { color: #24292f } /* Punctuation */ +.ch { color: #6e7781 } /* Comment.Hashbang */ +.cm { color: #6e7781 } /* Comment.Multiline */ +.cp { color: #6e7781 } /* Comment.Preproc */ +.cpf { color: #6e7781 } /* Comment.PreprocFile */ +.c1 { color: #6e7781 } /* Comment.Single */ +.cs { color: #6e7781 } /* Comment.Special */ +.gd { color: #0550ae } /* Generic.Deleted */ +.ge { font-style: italic } /* Generic.Emph */ +.gr { color: #cf222e } /* Generic.Error */ +.gh { color: #0550ae } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #0550ae } /* Generic.Subheading */ +.kc { color: #0550ae } /* Keyword.Constant */ +.kd { color: #cf222e } /* Keyword.Declaration */ +.kn { color: #cf222e } /* Keyword.Namespace */ +.kp { color: #cf222e } /* Keyword.Pseudo */ +.kr { color: #cf222e } /* Keyword.Reserved */ +.kt { color: #cf222e } /* Keyword.Type */ +.ld { color: #953800 } /* Literal.Date */ +.m { color: #953800 } /* Literal.Number */ +.s { color: #0550ae } /* Literal.String */ +.na { color: #953800 } /* Name.Attribute */ +.nb { color: #953800 } /* Name.Builtin */ +.nc { color: #0550ae } /* Name.Class */ +.no { color: #0550ae } /* Name.Constant */ +.nd { color: #953800 } /* Name.Decorator */ +.ni { color: #116329 } /* Name.Entity */ +.ne { color: #8250df } /* Name.Exception */ +.nf { color: #0550ae } /* Name.Function */ +.nl { color: #953800 } /* Name.Label */ +.nn { color: #24292f } /* Name.Namespace */ +.nx { color: #8250df } /* Name.Other */ +.py { color: #0550ae } /* Name.Property */ +.nt { color: #116329 } /* Name.Tag */ +.nv { color: #953800 } /* Name.Variable */ +.ow { color: #8250df } /* Operator.Word */ +.pm { color: #24292f } /* Punctuation.Marker */ +.w { color: #24292f } /* Text.Whitespace */ +.mb { color: #953800 } /* Literal.Number.Bin */ +.mf { color: #953800 } /* Literal.Number.Float */ +.mh { color: #953800 } /* Literal.Number.Hex */ +.mi { color: #953800 } /* Literal.Number.Integer */ +.mo { color: #953800 } /* Literal.Number.Oct */ +.sa { color: #0550ae } /* Literal.String.Affix */ +.sb { color: #0550ae } /* Literal.String.Backtick */ +.sc { color: #0550ae } /* Literal.String.Char */ +.dl { color: #0550ae } /* Literal.String.Delimiter */ +.sd { color: #0550ae } /* Literal.String.Doc */ +.s2 { color: #0550ae } /* Literal.String.Double */ +.se { color: #0550ae } /* Literal.String.Escape */ +.sh { color: #0550ae } /* Literal.String.Heredoc */ +.si { color: #0550ae } /* Literal.String.Interpol */ +.sx { color: #0550ae } /* Literal.String.Other */ +.sr { color: #0550ae } /* Literal.String.Regex */ +.s1 { color: #0550ae } /* Literal.String.Single */ +.ss { color: #0550ae } /* Literal.String.Symbol */ +.bp { color: #953800 } /* Name.Builtin.Pseudo */ +.fm { color: #0550ae } /* Name.Function.Magic */ +.vc { color: #953800 } /* Name.Variable.Class */ +.vg { color: #953800 } /* Name.Variable.Global */ +.vi { color: #953800 } /* Name.Variable.Instance */ +.vm { color: #953800 } /* Name.Variable.Magic */ +.il { color: #953800 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/gotthard-dark.css b/nbconvert_a11y/templates/a11y/static/theme/gotthard-dark.css new file mode 100644 index 00000000..b9c30b3c --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/gotthard-dark.css @@ -0,0 +1,75 @@ +/** accessible pygments gotthard-dark generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #4c4b4be8 } +.c { color: #b19db4 } /* Comment */ +.err { color: #AB6369 } /* Error */ +.k { color: #b19db4 } /* Keyword */ +.l { color: #b19db4 } /* Literal */ +.n { color: #F5F5F5 } /* Name */ +.o { color: #6F98B3 } /* Operator */ +.p { color: #F5F5F5 } /* Punctuation */ +.ch { color: #b19db4 } /* Comment.Hashbang */ +.cm { color: #b19db4 } /* Comment.Multiline */ +.cp { color: #b19db4 } /* Comment.Preproc */ +.cpf { color: #b19db4 } /* Comment.PreprocFile */ +.c1 { color: #b19db4 } /* Comment.Single */ +.cs { color: #b19db4 } /* Comment.Special */ +.gd { color: #AB6369 } /* Generic.Deleted */ +.gh { color: #81B19B } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #81B19B } /* Generic.Subheading */ +.kc { color: #AB6369 } /* Keyword.Constant */ +.kd { color: #b19db4 } /* Keyword.Declaration */ +.kn { color: #b19db4 } /* Keyword.Namespace */ +.kp { color: #b19db4 } /* Keyword.Pseudo */ +.kr { color: #b19db4 } /* Keyword.Reserved */ +.kt { color: #81B19B } /* Keyword.Type */ +.ld { color: #b19db4 } /* Literal.Date */ +.m { color: #AB6369 } /* Literal.Number */ +.s { color: #81B19B } /* Literal.String */ +.na { color: #b19db4 } /* Name.Attribute */ +.nb { color: #81B19B } /* Name.Builtin */ +.nc { color: #CAAB6D } /* Name.Class */ +.no { color: #AB6369 } /* Name.Constant */ +.nd { color: #81B19B } /* Name.Decorator */ +.ni { color: #81B19B } /* Name.Entity */ +.ne { color: #AB6369 } /* Name.Exception */ +.nf { color: #b19db4 } /* Name.Function */ +.nl { color: #81B19B } /* Name.Label */ +.nn { color: #CAAB6D } /* Name.Namespace */ +.nx { color: #F5F5F5 } /* Name.Other */ +.py { color: #b19db4 } /* Name.Property */ +.nt { color: #AB6369 } /* Name.Tag */ +.nv { color: #F5F5F5 } /* Name.Variable */ +.ow { color: #b19db4 } /* Operator.Word */ +.pm { color: #F5F5F5 } /* Punctuation.Marker */ +.w { color: #F5F5F5 } /* Text.Whitespace */ +.mb { color: #AB6369 } /* Literal.Number.Bin */ +.mf { color: #AB6369 } /* Literal.Number.Float */ +.mh { color: #AB6369 } /* Literal.Number.Hex */ +.mi { color: #AB6369 } /* Literal.Number.Integer */ +.mo { color: #AB6369 } /* Literal.Number.Oct */ +.sa { color: #81B19B } /* Literal.String.Affix */ +.sb { color: #CAAB6D } /* Literal.String.Backtick */ +.sc { color: #81B19B } /* Literal.String.Char */ +.dl { color: #81B19B } /* Literal.String.Delimiter */ +.sd { color: #81B19B } /* Literal.String.Doc */ +.s2 { color: #81B19B } /* Literal.String.Double */ +.se { color: #6F98B3 } /* Literal.String.Escape */ +.sh { color: #81B19B } /* Literal.String.Heredoc */ +.si { color: #81B19B } /* Literal.String.Interpol */ +.sx { color: #81B19B } /* Literal.String.Other */ +.sr { color: #6F98B3 } /* Literal.String.Regex */ +.s1 { color: #81B19B } /* Literal.String.Single */ +.ss { color: #81B19B } /* Literal.String.Symbol */ +.bp { color: #81B19B } /* Name.Builtin.Pseudo */ +.fm { color: #b19db4 } /* Name.Function.Magic */ +.vc { color: #F5F5F5 } /* Name.Variable.Class */ +.vg { color: #F5F5F5 } /* Name.Variable.Global */ +.vi { color: #F5F5F5 } /* Name.Variable.Instance */ +.vm { color: #F5F5F5 } /* Name.Variable.Magic */ +.il { color: #AB6369 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/theme/gotthard-light.css b/nbconvert_a11y/templates/a11y/static/theme/gotthard-light.css new file mode 100644 index 00000000..3196b32a --- /dev/null +++ b/nbconvert_a11y/templates/a11y/static/theme/gotthard-light.css @@ -0,0 +1,75 @@ +/** accessible pygments gotthard-light generated by __main__**/ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.hll { background-color: #E1E1E1 } +.c { color: #974EB7 } /* Comment */ +.err { color: #9F4E55 } /* Error */ +.k { color: #974EB7 } /* Keyword */ +.l { color: #974EB7 } /* Literal */ +.n { color: #141414 } /* Name */ +.o { color: #3D73A9 } /* Operator */ +.p { color: #141414 } /* Punctuation */ +.ch { color: #974EB7 } /* Comment.Hashbang */ +.cm { color: #974EB7 } /* Comment.Multiline */ +.cp { color: #974EB7 } /* Comment.Preproc */ +.cpf { color: #974EB7 } /* Comment.PreprocFile */ +.c1 { color: #974EB7 } /* Comment.Single */ +.cs { color: #974EB7 } /* Comment.Special */ +.gd { color: #9F4E55 } /* Generic.Deleted */ +.gh { color: #437A6B } /* Generic.Heading */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #437A6B } /* Generic.Subheading */ +.kc { color: #9F4E55 } /* Keyword.Constant */ +.kd { color: #974EB7 } /* Keyword.Declaration */ +.kn { color: #974EB7 } /* Keyword.Namespace */ +.kp { color: #974EB7 } /* Keyword.Pseudo */ +.kr { color: #974EB7 } /* Keyword.Reserved */ +.kt { color: #437A6B } /* Keyword.Type */ +.ld { color: #974EB7 } /* Literal.Date */ +.m { color: #9F4E55 } /* Literal.Number */ +.s { color: #437A6B } /* Literal.String */ +.na { color: #974EB7 } /* Name.Attribute */ +.nb { color: #437A6B } /* Name.Builtin */ +.nc { color: #98661B } /* Name.Class */ +.no { color: #9F4E55 } /* Name.Constant */ +.nd { color: #437A6B } /* Name.Decorator */ +.ni { color: #437A6B } /* Name.Entity */ +.ne { color: #9F4E55 } /* Name.Exception */ +.nf { color: #974EB7 } /* Name.Function */ +.nl { color: #437A6B } /* Name.Label */ +.nn { color: #98661B } /* Name.Namespace */ +.nx { color: #141414 } /* Name.Other */ +.py { color: #974EB7 } /* Name.Property */ +.nt { color: #9F4E55 } /* Name.Tag */ +.nv { color: #141414 } /* Name.Variable */ +.ow { color: #974EB7 } /* Operator.Word */ +.pm { color: #141414 } /* Punctuation.Marker */ +.w { color: #141414 } /* Text.Whitespace */ +.mb { color: #9F4E55 } /* Literal.Number.Bin */ +.mf { color: #9F4E55 } /* Literal.Number.Float */ +.mh { color: #9F4E55 } /* Literal.Number.Hex */ +.mi { color: #9F4E55 } /* Literal.Number.Integer */ +.mo { color: #9F4E55 } /* Literal.Number.Oct */ +.sa { color: #437A6B } /* Literal.String.Affix */ +.sb { color: #98661B } /* Literal.String.Backtick */ +.sc { color: #437A6B } /* Literal.String.Char */ +.dl { color: #437A6B } /* Literal.String.Delimiter */ +.sd { color: #437A6B } /* Literal.String.Doc */ +.s2 { color: #437A6B } /* Literal.String.Double */ +.se { color: #3D73A9 } /* Literal.String.Escape */ +.sh { color: #437A6B } /* Literal.String.Heredoc */ +.si { color: #437A6B } /* Literal.String.Interpol */ +.sx { color: #437A6B } /* Literal.String.Other */ +.sr { color: #3D73A9 } /* Literal.String.Regex */ +.s1 { color: #437A6B } /* Literal.String.Single */ +.ss { color: #437A6B } /* Literal.String.Symbol */ +.bp { color: #437A6B } /* Name.Builtin.Pseudo */ +.fm { color: #974EB7 } /* Name.Function.Magic */ +.vc { color: #141414 } /* Name.Variable.Class */ +.vg { color: #141414 } /* Name.Variable.Global */ +.vi { color: #141414 } /* Name.Variable.Instance */ +.vm { color: #141414 } /* Name.Variable.Magic */ +.il { color: #9F4E55 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/style.css b/nbconvert_a11y/templates/a11y/style.css deleted file mode 100644 index 0f11c597..00000000 --- a/nbconvert_a11y/templates/a11y/style.css +++ /dev/null @@ -1,172 +0,0 @@ -/* style variables */ -:root { - --nb-focus-width: 3px; - --nb-accent-color: auto; - --nb-background-color-dark: #2b2a33; - --nb-background-color-light: #FFFFFF; - --nb-margin: 5%; - --nb-font-size: medium; -} - -body { - font-size: var(--nb-font-size); - overflow-x: hidden; - accent-color: var(--nb-accent-color); - margin-left: var(--nb-margin); - margin-right: var(--nb-margin); -} - -/* satisfy AA 2.5.8 minimum target requirement */ -body.wcag-aa button, -body.wcag-aa input[type=checkbox] { - min-height: 24px; - min-width: 24px; -} - -body.wcag-aa a { - font-size: max(24px, var(--nb-font-size)); -} - -/* satisfy AAA 2.5.5 */ -body.wcag-aaa button, -body.wcag-aaa input[type=checkbox] { - min-height: 44px; - min-width: 44px; -} - -body.wcag-aaa a { - font-size: max(44px, var(--nb-font-size)); -} - -/* align checkboxes with buttons */ -input[type="checkbox"] { - vertical-align: middle; -} - -table#cells, -table#cells>.cell>td { - display: block; -} - -table#cells .cell, -table#cells>tbody { - display: flex; - flex-direction: column; -} - - -/* the input and output become interactive when there is overflow.*/ -textarea[name=source], -.cell>td>details { - overflow: auto; - min-width: 0; -} - - -summary[inert]::marker { - content: ""; -} - -textarea { - font-family: inherit; - font-size: inherit; - line-height: inherit; -} - -textarea[name=source] { - box-sizing: border-box; - /* https://davidwalsh.name/textarea-width */ - width: 100%; -} - - - -table#cells+table, -table#cells>tbody>tr:first-child, -#cells .cell>.nb-loc, -#cells .cell.markdown>.nb-source, -#cells .nb-cell_type, -#cells .nb-metadata, -#cells .nb-execution_count, -#cells .nb-start, -#cells .nb-end, -#cells details>summary[inert]~textarea[name=source], -#cells details>summary:not([inert])~textarea[name=source]~* { - display: none; -} - -header:focus-within, -.cell:focus-within, -:focus-visible { - outline: max(var(--nb-focus-width), 1px) solid; - box-shadow: 0 0 0 calc(2 * max(var(--nb-focus-width), 1px)); -} - -/* we visually hide the logs so that the screen reader announces them. hiding them hides them from AT. */ -details.log:not([open])+table, -details.log:not([open])+ul[aria-live], -.visually-hidden:not(:focus-within):not(:active) { - clip: rect(0 0 0 0); - clip-path: inset(50%); - height: 1px; - overflow: hidden; - position: absolute; - white-space: nowrap; - width: 1px; -} - -/* default tag settings. */ -textarea { - overflow: auto; -} - -textarea: { - color: unset; -} - - -/* hide legends and use labels instead. they increase the hit area. -https://adrianroselli.com/2022/07/use-legend-and-fieldset.html */ -legend:not(:focus):not(:active) { - position: absolute; - overflow: hidden; - clip: rect(0 0 0 0); - clip-path: inset(50%); - width: 1px; - height: 1px; - white-space: nowrap; -} - -[role=log] li::marker { - display: none; -} - -#skip-link { - position: fixed; - transform: translateY(-200%); - display: inline-flex; - justify-content: space-evenly; - align-items: center; - width: 100vw; - background-color: inherit; -} - -#skip-link:focus-within { - transform: translateY(0); -} - -a[href="#notebook"] { - position: sticky; - float: right; - bottom: 0; -} - -/* small devices */ -@media (max-width: 576px) {} - -/* medium devices */ -@media (max-width: 768px) { - :root { - --nb-margin: 0px; - } -} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/table.html.j2 b/nbconvert_a11y/templates/a11y/table.html.j2 index 507614ef..da110e15 100644 --- a/nbconvert_a11y/templates/a11y/table.html.j2 +++ b/nbconvert_a11y/templates/a11y/table.html.j2 @@ -1,31 +1,52 @@ -{%- extends 'semantic-forms/base.html.j2' -%} +{%- extends 'a11y/base.html.j2' -%} +{% from "a11y/components/core.html.j2" import hide, time, loc %} +{% from "a11y/components/cell.html.j2" import cell_anchor, cell_execution_count, cell_cell_type, +cell_form, cell_source, cell_metadata, cell_output with context%} {% set COLUMNS = ["index", "execution_count", "cell_type", "toolbar", "started_at", "completed_at", "source", "loc", "metadata", "outputs"] %} -{% block body_header %} -{{super()}} +{% block body_loop %} {# the most consistent implementation would connect the input visibility to a form #} - + {% for col in COLUMNS %} {% endfor %} - {# the table caption is removed because testing on VoiceOver iOS encountered - https://developer.apple.com/forums/thread/679841 - - the best course of action is to remove the caption and provide label support with aria. - we wont use captions on any tables for this reason. #} - {# #} - {% endblock body_header %} - - {% block any_cell %}{{cell_row(cell, loop)}}{% endblock any_cell %} - - {% block body_footer %} + {%- for cell in nb.cells -%} + {% block any_cell scoped %} + + + + + + + + + + {# it was noted in a video that lines of code were helpful in assistive descriptions. + lines of code are part of the gestalt of code forms. #} + + + + {% endblock any_cell %} + {%- endfor -%} - {# needs a header row #} @@ -64,38 +85,4 @@ -{{super()}} -{% endblock body_footer %} - -{% macro loc(cell) %}{{cell.source.splitlines().__len__()}}{% endmacro%} - -{% macro time(t) %} -{% if t %}{% if t.endswith("Z") %}{% set t = t[:-1] + "+00:00" %}{% endif %}{% endif %} -{% endmacro %} - -{% macro cell_row(cell, loop) %} - - {{cell_anchor(loop.index, cell.cell_type)}} - {{cell_execution_count(loop.index, cell.execution_count)}} - {{cell_cell_type(loop.index, cell.cell_type)}} - {{cell_form(loop.index)}} - - {% set t0 = cell.metadata.get("execution", {}).get("iopub.execute_input", "") %} - {{time(t0)}} - - - {% set t1 = cell.metadata.get("execution", {}).get("shell.execute_reply", "") %} - {{time(t1)}} - - {{cell_source(loop.index, cell.source, cell.execution_count)}} - {{cell_metadata(loop.index, cell.metadata)}} - {# it was noted in a video that lines of code were helpful in assistive descriptions. - lines of code are part of the gestalt of code forms. #} - {{loc(cell)}} - {{cell_output(loop.index, cell, cell.source, cell.outputs, cell.cell_type, - cell.execution_count)}} - -{% endmacro %} \ No newline at end of file +{% endblock body_loop %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/README.md b/nbconvert_a11y/templates/semantic-forms/README.md deleted file mode 100644 index 47cb8e7a..00000000 --- a/nbconvert_a11y/templates/semantic-forms/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# the `html5` template - -an `nbconvert` template designed for an accessible experience when rendering notebooks as html webpages. more generally, it could serve as an accessible substrate to build computational literature with like documentation, research papers, or blog posts. - -`jupyter nbconvert --to html5` features: - -- [x] semantic html tags, roles, and aria for the notebook and its cells -- [x] efficient tab navigation including: - - [x] skips links - - [x] heading links with large hit areas - - [x] cell source as `readonly` forms that take tab focus - - [x] any other rich interactive content in the `output` -- [x] uses Atkinson Hyperlegible which is specifically to increase legibility for readers with low vision, and to improve comprehension. -- [x] uses the `github-light-colorblind` `pygments code theme from the [accessible-pygments](https://github.com/Quansight-Labs/accessible-pygments) project based on [`a11y-syntax-highlighting`](https://github.com/ericwbailey/a11y-syntax-highlighting) -- [x] screen reader landmarks, headings (markdown & outputs), forms (cell inputs), and table navigation -- [x] operable when zoomed in -- [x] table of contents for code and narrative navigation -- [ ] configurable accessibility settings - - [ ] persistent settings across sessions -- [ ] best practice auditting during conversion -- [ ] automated remediations - - [ ] fix rendered pandas tables - -### template scope - -the template defines the majority of the web page from the `html` tag to the cell outputs. every element is defined using a meaningful tag or aria role. the cell outputs come from user land and our template can't control their content. if author's abide some [best practices]() then they can ensure an accessible experience when their notebook is exported to html. - -### POUR-CAF principles - -notebooks often harness data visualizations. their mission co-develops with accessible visualizations. this project goals beyond the standard [WCAG] POUR principles and adds [Chartability]'s [CAF] principles and heuristics to the design. - -## a table of cells - -this template represents a notebook as a html table where each notebook cell is a row in the html. the table pattern is a natural html pattern and adds a new dimension to screen readers navigating notebook documents. - -## table of contents navigation - -notebook documents can be long and navigating them need to be easier. - -* Esc - minimizes the table of contents -* Ctrl + Esc - toggles the table of contents - -## conclusion - -the html version of notebooks is not the same interactive state as the editting experience, but it is still a highly interactive experience. overall, focusing on an accessible substrate to build sites from has improved the experience from abled and disabled people. - -[Chartability]: https://chartability.fizz.studio/ "heuristics and principles for accessible data systems" -[WCAG]: https://en.wikipedia.org/wiki/Web_Content_Accessibility_Guidelines -[CAF]: https://github.com/Chartability/POUR-CAF \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/aside-dl.html.j2 b/nbconvert_a11y/templates/semantic-forms/aside-dl.html.j2 deleted file mode 100644 index e69de29b..00000000 diff --git a/nbconvert_a11y/templates/semantic-forms/base.html.j2 b/nbconvert_a11y/templates/semantic-forms/base.html.j2 deleted file mode 100644 index 860a8a37..00000000 --- a/nbconvert_a11y/templates/semantic-forms/base.html.j2 +++ /dev/null @@ -1,238 +0,0 @@ -{# # a base template for accessible notebook representations. - -the base template defines notebook independent components. -an accessible base template provides a substrate to progressively enchance -the notebook experiennce from browse to edit/focus mode. -#} -{%- extends 'semantic-forms/displays.j2.html' -%} -{% from 'celltags.j2' import celltags %} -{% from 'mathjax.html.j2' import mathjax %} -{% from 'lab/mermaidjs.html.j2' import mermaid_js %} -{% from 'jupyter_widgets.html.j2' import jupyter_widgets %} -{% set title = nb.metadata.get('title', resources['metadata']['name']) | escape_html_keep_quotes %} - -{%- block header -%} - - - - - {%- block head -%} - - - {# use technique [h25] to provide a title to satisfy [2.4.2A]. #} - {{title}} - {# color scheme signals that notebooks can viewed in light and dark mode. - the html representation is used instead of the css representation so it is immediately avaiable. - https://css-tricks.com/almanac/properties/c/color-scheme/ #} - - - {# non-html resources, css and javascript, are served as external resources to optimize load times. #} - - - - - - {%- block html_head_js -%} - {%- block html_head_js_requirejs -%} - - {%- endblock html_head_js_requirejs -%} - {%- endblock html_head_js -%} - - {% block jupyter_widgets %} - {%- if "widgets" in nb.metadata -%} - {{ jupyter_widgets(resources.jupyter_widgets_base_url, resources.html_manager_semver_range, - resources.widget_renderer_url) }} - {%- endif -%} - {% endblock jupyter_widgets %} - - - {%- block html_head_js_mathjax -%} - {{ mathjax(resources.mathjax_url) }} - {%- endblock html_head_js_mathjax -%} - - {%- endblock head -%} - -{%- endblock header -%} - - -{% block body_header %} - -
    - - {# the skip link is the first tab stop in the site to skip repetitive information - and directly access the content. #} - {# site authors with include their site specific headers in this region. #} - {# a subsequent tab stop will indicate to keyboard and AT users that there are - accessibility settings that can be toggled. #} - {% if resources.include_settings %}{% include "a11y/settings.html.j2" %}{% endif %} - {% if resources.include_axe %}{% include "a11y/audit.j2.html" %}{% endif %} -
    -
    -
    - {% if resources.include_toc %} -
    - {# a notebook will provide visual structural navigation for a document. - this is a feature of screen readers that is not common to sighted users. - the implementation here is very naive. users will need to know to collapse the heading - to skip the link tree. the best implementation is a tree that will consume a single tab stop - and allow arrow key navigation. #} -
    - {# if the label is on the summary then the bullet is announced as the label and it should not be - #} - table of contents - {# the table of contents is populated in python. #} - -
      -
      -
      - {% endif %} - - - {% if resources.include_axe %}{% endif %} - - - {% if resources.include_help %}{% endif %} - -
      - {% endblock body_header %} - - {% block any_cell %}{{cell_section(cell, loop)}}{% endblock any_cell %} - - {% block body_footer %} - {# dialogs need to be outside the form because we cant nest forms #} - {% include "a11y/expanded.html.j2"%} - {% include "a11y/visibility.html.j2"%} - -
      - - {{nb.metadata| json_dumps | escape_html_keep_quotes }} -
      -
      - {% if resources.include_help %}{% include "a11y/help.html.j2" %}{% endif %} -
      - {# a notebook begins as a static document that can progressively - add features like run time computation. #} - {# skip to top is needed for long notebooks. - it is difficult to access for keyboard users. #} -
      - {% set LOGID = True %} - {% include "a11y/activity-log.html.j2" %} - skip to top -
      - {% if resources.include_settings %}{% endif %} - {% if resources.include_axe %}{% endif %} - {% set mimetype = 'application/vnd.jupyter.widget-state+json'%} - {% if mimetype in nb.metadata.get("widgets",{})%} - - {% endif %} - {# - '' #} - - - -{% endblock body_footer %} - - -{% macro cell_section(cell, loop) %} -
      - {{cell_anchor(loop.index, cell.cell_type)}} - {{cell_form(i)}} - {{cell_execution_count(loop.index, cell.execution_count)}} - {{cell_cell_type(loop.index, cell.cell_type)}} - {{cell_source(loop.index, cell.source, cell.execution_count)}} - {{cell_output(loop.index, cell, cell.source, cell.outputs, cell.cell_type, cell.execution_count)}} - {{cell_metadata(loop.index, cell.metadata)}} -
      -{% endmacro%} -{% macro cell_anchor(i, cell_type)%} -{{i}} -{% endmacro %} - -{% macro cell_form(i, cell_type) %} -{# the cell form acts a formal reference for each cell. as a form, each cell can handle a submission process -that would include talking to the kernel. #} - -{% endmacro %} - -{% macro cell_cell_type(i, cell_type) %} - -{{cell_type}} -{% endmacro %} - -{% macro cell_execution_count(i, execution_count) %} -#{{execution_count}} -{% endmacro %} - - -{% macro cell_source(i, source, execution_count) %} -{% set label -%} -In{{execution_count}} -{%- endset %} -
      - {{label}} - - {{highlight(source)}} -
      -{% endmacro %} - -{% macro cell_metadata(i, metadata) %} - - -
      - -
      
      -        {{metadata}}
      -        
      -
      -
      -{% endmacro %} - -{%- macro cell_output(i, cell, source, outputs, cell_type, execution_count) -%} -{% set CODE = cell_type == "code" %} -{% set label %}{% if CODE and outputs %}Out{{execution_count}}{% else %}Cell {{i}}{% endif %}{% endset %} - -{% if CODE and outputs %} -{% if outputs %} -
      - {{label}} - {# the output description should mention the number of outputs - saying zero outputs should be an option. a cell without an output is probably a violation. #} - {{cell_display_priority(i, outputs, cell)}} -
      -{% endif %} -{% elif cell_type=="markdown" %} -
      - - {{ markdown(source) | strip_files_prefix }} -
      -{% endif %} -{%- endmacro -%} - -{# - -[h25]: https://www.w3.org/WAI/WCAG21/Techniques/html/H25 -[2.4.2A]: https://www.w3.org/WAI/WCAG21/Understanding/page-titled - -#} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/cell.html.j2 b/nbconvert_a11y/templates/semantic-forms/cell.html.j2 deleted file mode 100644 index 51d5ceef..00000000 --- a/nbconvert_a11y/templates/semantic-forms/cell.html.j2 +++ /dev/null @@ -1,105 +0,0 @@ -{%- extends 'semantic-forms/index.html.j2' -%} - -{# cell is a tough analogy, it is an ambiguous signifier dependent on context. -the analogy to a biologic cell asks "what is the nucleus?" or "what encodes the objective?". -the source input feels like it is the irreducible element of our computational cells. -lines of text evolve over time. like dna they replicate, mutate, evolve, and cooperate. - -allied alphabets dancing on carefully organized sand emitting waves of sound, light, and hope. -the lines in the source, the nucleus, freeze a sliver of a movement in hypermedia. - -#} -{%- macro cell_textarea(cell, nb) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} - -{%- endmacro -%} - - -{%- macro cell_forms(cell, nb, body) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -
      - {{body}} -
      -{%- endmacro -%} - - -{%- macro cell_form_element(cell, nb) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -
      -{%- endmacro -%} - -{%- macro cell_submit(cell, nb) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{# https://www.w3.org/TR/WCAG20-TECHS/H32.html #} - -{%- endmacro -%} - -{%- macro cell_toolbars(cell, nb, body) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -
        {%- for part in body -%}
      • {{part}}
      • {%- endfor -%}
      -{%- endmacro -%} - -{%- macro cell_type(cell, nb) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{%- set cell_type = cell.cell_type -%} - -{%- endmacro -%} - -{# the execution count has been one of the most confusing aspects of this journey. -it's meaning wasn't revealed until the very end of this rigorous study. -the result discovered labelable, interactive elements that describe the components of the cell. -assistive technology requires labels for interactive objects and the semantic representation allows the re-use -of the execution count label on different elements with different roles. -#} -{%- macro cell_execution_count_out(cell, nb, body) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{% if cell.cell_type == "code" %} - -{% else %} - -{% endif %} -{%- endmacro -%} - -{%- macro cell_execution_count_in(cell, nb, body) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{% if cell.cell_type == "code" %} - -{% else %} - -{% endif %} -{%- endmacro -%} - -{%- macro cell_outputs(cell, nb, parts) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} - -{%- endmacro -%} - -{%- macro cell_render(cell, nb, parts) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{% set CODE = cell.cell_type=="code" %} - - {%- if CODE -%}{{ cell.source | highlight_code(metadata=cell.metadata) }}{% else %}{{markdown(cell.source) | - strip_files_prefix}}{%- endif -%} - -{%- endmacro -%} - -{%- macro cell_display_priority(cell, ID) -%} -{%- for i, output in enumerate(cell.outputs) -%} -{%- block output scoped -%}{%- block output_prompt -%}{%- endblock-%}{{super()}}{%- endblock -%} -{%- endfor -%} -{%- endmacro -%} - - -{%- macro highlight(body, type) -%} -{{markdown("```{{type}}\n" + json.dumps(nb.metadata, indent=2) + "\n```\n")}} -{%- endmacro -%} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/conf.json b/nbconvert_a11y/templates/semantic-forms/conf.json deleted file mode 100644 index 094e7003..00000000 --- a/nbconvert_a11y/templates/semantic-forms/conf.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "base_template": "null", - "mimetypes": { - "text/html": true - }, - "preprocessors": { - } -} diff --git a/nbconvert_a11y/templates/semantic-forms/feed.html.j2 b/nbconvert_a11y/templates/semantic-forms/feed.html.j2 deleted file mode 100644 index d124febd..00000000 --- a/nbconvert_a11y/templates/semantic-forms/feed.html.j2 +++ /dev/null @@ -1,16 +0,0 @@ -{%- extends 'semantic-forms/main.html.j2' -%} -{% from 'mathjax.html.j2' import mathjax %} -{% from 'jupyter_widgets.html.j2' import jupyter_widgets %} - -{% set title = nb.metadata.get('title', resources['metadata']['name']) | escape_html_keep_quotes %} - -{% block body_header %} -{{super()}} -
      - {{super()}} - {% endblock body_header %} - - {% block body_footer %} -
      -{{super()}} -{% endblock body_footer %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/forms.html.j2 b/nbconvert_a11y/templates/semantic-forms/forms.html.j2 deleted file mode 100644 index 33670a8d..00000000 --- a/nbconvert_a11y/templates/semantic-forms/forms.html.j2 +++ /dev/null @@ -1,61 +0,0 @@ -{%- extends 'semantic-forms/index.html.j2' -%} -{% from 'mathjax.html.j2' import mathjax %} -{% from 'jupyter_widgets.html.j2' import jupyter_widgets %} - - -{% block any_cell %} -{{cell_section(cell, nb, resources)}} -{% endblock any_cell %} - -{% macro highlight(body, type) %} -{{markdown("```{{type}}\n" + json.dumps(nb.metadata, indent=2) + "\n```\n")}} -{% endmacro %} - - -{% macro cell_section(cell, nb, resources={}) %} -{% set count = nb.cells.index(cell) %} -{% set ID = "/cells/" + str(cell.id or count) %} -
      - {{cell_type_cell(cell)}} - -
      -{{cell_toolbar(cell, ID)}} - - {% if cell.cell_type=="markdown" %}{{markdown(cell.source) | strip_files_prefix}}{% endif %} - {% if cell.cell_type=="code" %}{{ cell.source | highlight_code(metadata=cell.metadata) }}{% endif %} - -{{cell.execution_count or ""}} -{{outputs_cell(cell, ID)}} -{% endmacro %} - - -{% macro cell_type_cell(cell) %} -{% set cell_type = cell.cell_type %} - -{% endmacro %} - -{% macro outputs_cell(cell, ID) %} -{% for i, output in enumerate(cell.outputs) %} -{% block output scoped %}{% block output_prompt %}{% endblock %}{{super()}}{% endblock %} -{% endfor %} -{% endmacro %} - - -{% macro cell_toolbar(cell, ID) %} - - - - -{% endmacro %} - -{% macro hide(x) %}{% if not x %}hidden{% endif %}{% endmacro %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/index.html.j2 b/nbconvert_a11y/templates/semantic-forms/index.html.j2 deleted file mode 100644 index 67a951af..00000000 --- a/nbconvert_a11y/templates/semantic-forms/index.html.j2 +++ /dev/null @@ -1,98 +0,0 @@ -{%- extends 'semantic-forms/base.html.j2' -%} -{% from 'mathjax.html.j2' import mathjax %} -{% from 'jupyter_widgets.html.j2' import jupyter_widgets %} - -{%- block header -%} - - - -{%- block html_head -%} - - -{% set nb_title = nb.metadata.get('title', resources['metadata']['name']) | escape_html_keep_quotes %} -{{nb_title}} -{% block css %}{% endblock css %} -{%- endblock html_head -%} - -{%- block html_head_js_mathjax -%} -{{ mathjax(resources.mathjax_url) }} -{%- endblock html_head_js_mathjax -%} - - -{%- endblock -%} - - -{% block body_header %} - -
      - {% block skip_links %}Skip to content{% endblock skip_links %} -
      -
      -{% endblock body_header %} - - -{% block body_footer %} -
      - - -{% endblock body_footer %} - -{% block footer %} -{% block footer_js %} -{% endblock footer_js %} -{{ super() }} - -{% endblock footer %} - - - {% macro cell_form(cell, nb) %} - {% set count = nb["cells"].index(cell) +1%} - {% set ID = "/cells/" + str(cell.id or count) %} -
      - {{cell_type_cell(cell)}} - -
      - {{cell_toolbar(cell, ID)}} - - {% if cell.cell_type=="markdown" %}{{markdown(cell.source) | strip_files_prefix}}{% endif %} - {% if cell.cell_type=="code" %}{{ cell.source | highlight_code(metadata=cell.metadata) }}{% endif %} - - {{cell.execution_count or ""}} - {{outputs_cell(cell, ID)}} - {% endmacro%} - - {% macro cell_type_cell(cell) %} - {% set cell_type = cell.cell_type %} - - {% endmacro %} - - {% macro outputs_cell(cell, ID) %} - {% for i, output in enumerate(cell.outputs) %} - {% block output scoped %}{% block output_prompt %}{% endblock %}{{super()}}{% endblock %} - {% endfor %} - {% endmacro %} - - - {% macro cell_toolbar(cell, ID) %} - - {% endmacro %} - - {% macro hide(x) %}{% if not x %}hidden{% endif %}{% endmacro %} - - - {% macro highlight(body, type) %} - {{markdown("```{{type}}\n" + json.dumps(nb.metadata, indent=2) + "\n```\n")}} - {% endmacro %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/main.html.j2 b/nbconvert_a11y/templates/semantic-forms/main.html.j2 deleted file mode 100644 index 0d0c7023..00000000 --- a/nbconvert_a11y/templates/semantic-forms/main.html.j2 +++ /dev/null @@ -1,14 +0,0 @@ -{%- extends 'semantic-forms/base.html.j2' -%} -{% set title = nb.metadata.get('title', resources['metadata']['name']) | escape_html_keep_quotes %} - - -{% block main_header scoped %} -{{super()}} -
      - cells - {% endblock main_header %} - - {% block body_footer %} -
      -{{super()}} -{% endblock body_footer %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/ol.html.j2 b/nbconvert_a11y/templates/semantic-forms/ol.html.j2 deleted file mode 100644 index 3c05ccbf..00000000 --- a/nbconvert_a11y/templates/semantic-forms/ol.html.j2 +++ /dev/null @@ -1,68 +0,0 @@ -{%- extends 'semantic-forms/base.html.j2' -%} -{% set title = nb.metadata.get('title', resources['metadata']['name']) | escape_html_keep_quotes %} - -{% macro cell_section(cell, loop) %} -
      - {{cell_anchor(loop.index)}} - {{cell_form(i)}} - {{cell_execution_count(loop.index, cell.execution_count)}} - {{cell_cell_type(loop.index, cell.cell_type)}} - {{cell_source(loop.index, cell.source, cell.execution_count)}} - {{cell_output(loop.index, cell, cell.source, cell.outputs, cell.cell_type, cell.execution_count)}} - {{cell_metadata(loop.index, cell.metadata)}} -
      -{% endmacro%} - -{%- block header -%} - - - - - {%- block head -%} - - - {{title}} - - - - {%- endblock head -%} - {%- block html_head_js_mathjax -%}{%- endblock html_head_js_mathjax -%} - - -{%- endblock -%} - - -{% block body_loop %} -
        {{super() }}
      {% endblock body_loop %} - -{% block any_cell %} -
    1. - {{cell_section(cell, loop)}} -
    2. -{% endblock any_cell %} - -{% block body_header %} - - -
      - {% block skip_links %} - Skip to content - {% endblock skip_links %} -
      -
      -
      -
      - {{title}} -
      - {% endblock body_header %} - - {% block body_footer %} -
      - - - -{% endblock body_footer %} - - diff --git a/nbconvert_a11y/templates/semantic-forms/schema-dl.html.j2 b/nbconvert_a11y/templates/semantic-forms/schema-dl.html.j2 deleted file mode 100644 index a7a95dc8..00000000 --- a/nbconvert_a11y/templates/semantic-forms/schema-dl.html.j2 +++ /dev/null @@ -1,59 +0,0 @@ - -

      notebook help

      -
      - -

      definitions

      -
      -
      notebook
      -
      a collections of cells
      -
      -
      -
      metadata
      -
      {{schema.properties.metadata.description}}
      -
      cells
      -
      {{schema.properties.cells.description}}
      -
      -
      -
      cell
      -
      -
      -
      index
      -
      the ordinal location of the cell in the notebook, counting begins from 1. -
      -
      source
      -
      {{schema.definitions.misc.source.description}}
      -
      metadata
      -
      {{schema.definitions.raw_cell.properties.metadata.description}}
      -
      cell type
      -
      the are three kinds of cells -
      -
      code
      -
      {{schema.definitions.code_cell.description}}
      -
      markdown
      -
      {{schema.definitions.markdown_cell.description}}
      -
      raw
      -
      {{schema.definitions.raw_cell.description}}
      -
      -
      -
      {{schema.definitions.raw_cell.properties.cell_type.description}}
      -
      execution count
      -
      - {{schema.definitions.code_cell.properties.execution_count.description}} -
      - -
      outputs
      -
      - {{schema.definitions.code_cell.properties.outputs.description}} -
      -
      attachments
      -
      {{schema.definitions.misc.attachments.description}}
      -
      lines of code
      -
      lines of code in the cell, including whitespace.
      - {# this probably should be significant lines of code #} -
      -
      -
      toolbar
      -
      composite widgets that allow for arrow key navigation
      -
      -
      -
      \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/sections.html.j2 b/nbconvert_a11y/templates/semantic-forms/sections.html.j2 deleted file mode 100644 index ca2068bf..00000000 --- a/nbconvert_a11y/templates/semantic-forms/sections.html.j2 +++ /dev/null @@ -1,86 +0,0 @@ -{%- extends 'semantic-forms/cell.html.j2' -%} -{% from 'mathjax.html.j2' import mathjax %} -{% from 'jupyter_widgets.html.j2' import jupyter_widgets %} - -{% block css %} - -{% endblock css %} - -{% macro input_group(cell, nb) %} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{{ cell_form_element(cell, nb)}} -
      - {{ cell_execution_count_in(cell, nb) }} - {{ cell_textarea(cell, nb) }} - {{ cell_render(cell, nb) }} -
      -{% endmacro %} - -{% block any_cell scoped %} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{%- set CODE = cell.cell_type == "code" -%} -
      - {{ input_group(cell, nb) }} - {{ cell_toolbars(cell, nb, [cell_submit(cell, nb), cell_type(cell, nb)]) }} - {{ cell_outputs(cell, nb) }} -
      -{% endblock any_cell %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/semantic-forms/smol.html.j2 b/nbconvert_a11y/templates/semantic-forms/smol.html.j2 deleted file mode 100644 index 4ef7f5de..00000000 --- a/nbconvert_a11y/templates/semantic-forms/smol.html.j2 +++ /dev/null @@ -1,184 +0,0 @@ -{%- extends 'semantic-forms/index.html.j2' -%} - -{% from 'mathjax.html.j2' import mathjax %} -{% from 'jupyter_widgets.html.j2' import jupyter_widgets %} - -{% block css %} - - -{% endblock css %} -{% block any_cell scoped %} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{%- set CODE = cell.cell_type == "code" -%} -{%- set tags = celltags(cell) -%} -{%- set ct = namespace(sloc=0, loc=0)%} -{% for line in "".join(cell.source).splitlines() %}{% set ct.loc = ct.loc + 1 %}{% if line.strip() %}{% set ct.sloc = ct.sloc + 1 %}{% endif %}{% endfor %} -{% if cell.cell_type == "raw" %}{{cell.source}}{% endif %} -
      - - -
      - {# the form doubles as an anchor #} -
      -
      -
      - In {{cell.execution_count or ""}} - - -
      - {# things need to follow this input in dom order so that we can use css selectors off fieldset:disabled #} - {{ cell_toolbars(cell, nb, [cell_submit(cell, nb), cell_type(cell, nb)]) }} - {{ cell_output(cell, nb) }} -
      -
      -{% endblock any_cell %} - - -{%- macro cell_submit(cell, nb) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -{# https://www.w3.org/TR/WCAG20-TECHS/H32.html #} - -{%- endmacro -%} - -{%- macro cell_toolbars(cell, nb, body) -%} -{%- set count = nb["cells"].index(cell)-%}{%- set ID = "/cells/" + str(count) -%} -