diff --git a/test-environment.yml b/.github/test-environment.yml similarity index 100% rename from test-environment.yml rename to .github/test-environment.yml diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index 0234e39f..1a84b0dc 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 }}}.io/nbconvert-a11y/branch/${{ github.event.pull_request.head.ref }}/exports/html diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fbff16ef..6adffc44 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,10 +49,10 @@ jobs: with: path: ~/conda_pkgs_dir key: ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ - hashFiles('test-environment.yml') }} + hashFiles('.github/test-environment.yml') }} - uses: mamba-org/setup-micromamba@v1 with: - environment-file: test-environment.yml + environment-file: .github/test-environment.yml cache-environment: true - name: init plawright run: | 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/form_exporter.py b/nbconvert_a11y/a11y_exporter.py similarity index 99% rename from nbconvert_a11y/form_exporter.py rename to nbconvert_a11y/a11y_exporter.py index 0bac147e..177f40be 100644 --- a/nbconvert_a11y/form_exporter.py +++ b/nbconvert_a11y/a11y_exporter.py @@ -122,6 +122,7 @@ def from_notebook_node(self, nb, resources=None, **kw): resources.setdefault("include_axe", self.include_axe) resources.setdefault("include_settings", self.include_settings) resources.setdefault("include_help", self.include_help) + resources.setdefault("include_toc", self.include_toc) resources.setdefault("axe_url", self.axe_url) html, resources = super().from_notebook_node(nb, resources, **kw) html = self.post_process_html(html) 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/templates/semantic-forms/base.html.j2 b/nbconvert_a11y/templates/a11y/base.html.j2 similarity index 72% rename from nbconvert_a11y/templates/semantic-forms/base.html.j2 rename to nbconvert_a11y/templates/a11y/base.html.j2 index 860a8a37..08e8e392 100644 --- a/nbconvert_a11y/templates/semantic-forms/base.html.j2 +++ b/nbconvert_a11y/templates/a11y/base.html.j2 @@ -4,7 +4,7 @@ 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' -%} +{%- extends 'a11y/components/displays.html.j2' -%} {% from 'celltags.j2' import celltags %} {% from 'mathjax.html.j2' import mathjax %} {% from 'lab/mermaidjs.html.j2' import mermaid_js %} @@ -16,9 +16,9 @@ the notebook experiennce from browse to edit/focus mode. - {%- block head -%} + {# add page description #} {# 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. @@ -45,16 +45,13 @@ the notebook experiennce from browse to edit/focus mode. {%- 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 %}
@@ -66,53 +63,26 @@ the notebook experiennce from browse to edit/focus mode. {# 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_settings %}{% include "a11y/components/settings.html.j2" %}{% endif %} + {% if resources.include_axe %}{% include "a11y/components/audit.html.j2" %}{% 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 %} - -
    + {% include "a11y/components/nb-toolbar.html.j2" %} {% 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"%} + {% include "a11y/components/expanded.html.j2"%} + {% include "a11y/components/visibility.html.j2"%}
    {{nb.metadata| json_dumps | escape_html_keep_quotes }}
    - {% if resources.include_help %}{% include "a11y/help.html.j2" %}{% endif %} + {% if resources.include_help %}{% include "a11y/components/help.html.j2" %}{% endif %}
    {# a notebook begins as a static document that can progressively add features like run time computation. #} @@ -120,21 +90,15 @@ the notebook experiennce from browse to edit/focus mode. it is difficult to access for keyboard users. #} - {% 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 %} - {# - '' #} + {% if resources.include_settings %} + {% endif %} + {%- if resources.include_axe -%} + + {%- endif -%} - {% endblock body_footer %} diff --git a/nbconvert_a11y/templates/a11y/activity-log.html.j2 b/nbconvert_a11y/templates/a11y/components/activity-log.html.j2 similarity index 100% rename from nbconvert_a11y/templates/a11y/activity-log.html.j2 rename to nbconvert_a11y/templates/a11y/components/activity-log.html.j2 diff --git a/nbconvert_a11y/templates/a11y/audit.j2.html b/nbconvert_a11y/templates/a11y/components/audit.html.j2 similarity index 100% rename from nbconvert_a11y/templates/a11y/audit.j2.html rename to nbconvert_a11y/templates/a11y/components/audit.html.j2 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/expanded.html.j2 b/nbconvert_a11y/templates/a11y/components/expanded.html.j2 similarity index 77% rename from nbconvert_a11y/templates/a11y/expanded.html.j2 rename to nbconvert_a11y/templates/a11y/components/expanded.html.j2 index c1086778..0358ddd4 100644 --- a/nbconvert_a11y/templates/a11y/expanded.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/expanded.html.j2 @@ -1,4 +1,4 @@ -{% from "a11y/visibility.html.j2" import header_row, checkbox_row%} +{% from "a11y/components/visibility.html.j2" import header_row, checkbox_row%}
    @@ -12,6 +12,6 @@
    - {% include "a11y/activity-log.html.j2" %} + {% include "a11y/components/activity-log.html.j2" %}
    \ No newline at end of file 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/nb-toolbar.html.j2 b/nbconvert_a11y/templates/a11y/components/nb-toolbar.html.j2 new file mode 100644 index 00000000..228c41dd --- /dev/null +++ b/nbconvert_a11y/templates/a11y/components/nb-toolbar.html.j2 @@ -0,0 +1,14 @@ +
    + {%- if resources.include_toc -%}{% include "a11y/components/toc.html.j2" %}{%- endif -%} + + + {% if resources.include_axe %}{% endif %} + + + {% if resources.include_help %}{% + endif %} + +
    \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/settings.html.j2 b/nbconvert_a11y/templates/a11y/components/settings.html.j2 similarity index 98% rename from nbconvert_a11y/templates/a11y/settings.html.j2 rename to nbconvert_a11y/templates/a11y/components/settings.html.j2 index beb07860..6511c643 100644 --- a/nbconvert_a11y/templates/a11y/settings.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/settings.html.j2 @@ -51,6 +51,6 @@ sound settings - {% include "a11y/activity-log.html.j2" %} + {% include "a11y/components/activity-log.html.j2" %} \ 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 96% rename from nbconvert_a11y/templates/a11y/visibility.html.j2 rename to nbconvert_a11y/templates/a11y/components/visibility.html.j2 index f82be4e5..87f29c58 100644 --- a/nbconvert_a11y/templates/a11y/visibility.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/visibility.html.j2 @@ -39,5 +39,5 @@ - {% include "a11y/activity-log.html.j2" %} + {% include "a11y/components/activity-log.html.j2" %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/index.html.j2 b/nbconvert_a11y/templates/a11y/index.html.j2 new file mode 100644 index 00000000..c177a3af --- /dev/null +++ b/nbconvert_a11y/templates/a11y/index.html.j2 @@ -0,0 +1,85 @@ +{% from 'mathjax.html.j2' import mathjax %} +{% from 'mermaidjs.html.j2' import mermaid_js %} +{% 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 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 extra_css %} + {% endblock extra_css %} + + {% for css in resources.inlining.css -%} + + {% endfor %} + + {% block notebook_css %} + {# the accessible solution will reduce load times by having external style sheets. #} + {{ resources.include_css("static/style.css") }} + {% if resources.theme == 'dark' %} + {{ resources.include_css("static/theme-dark.css") }} + {% elif resources.theme == 'light' %} + {{ resources.include_css("static/theme-light.css") }} + {% else %} + {{ resources.include_lab_theme(resources.theme) }} + {% endif %} + {% endblock notebook_css %} + + {%- block html_head_js_mathjax -%} + {{ mathjax(resources.mathjax_url) }} + {%- endblock html_head_js_mathjax -%} + + {%- block html_head_js_mermaidjs -%} + {{ mermaid_js(resources.mermaid_js_url) }} + {%- endblock html_head_js_mermaidjs -%} + + {%- block html_head_css -%} + {%- endblock html_head_css -%} + + {%- endblock html_head -%} + +{%- endblock header -%} + +{%- block body_header -%} + +
      + {%- endblock body_header -%} + + {% block body_footer %} +
      + +{% endblock body_footer %} + +{% block footer %} +{% block footer_js %} +{% endblock footer_js %} +{{ super() }} + + +{% endblock footer %} \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/index.j2.html b/nbconvert_a11y/templates/a11y/index.j2.html new file mode 100644 index 00000000..291577a1 --- /dev/null +++ b/nbconvert_a11y/templates/a11y/index.j2.html @@ -0,0 +1,140 @@ +{% from 'mathjax.html.j2' import mathjax %} +{% from 'mermaidjs.html.j2' import mermaid_js %} +{% 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 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 extra_css %} +{% endblock extra_css %} + +{% for css in resources.inlining.css -%} + +{% endfor %} + +{% block notebook_css %} +{{ resources.include_css("static/index.css") }} +{% if resources.theme == 'dark' %} + {{ resources.include_css("static/theme-dark.css") }} +{% elif resources.theme == 'light' %} + {{ resources.include_css("static/theme-light.css") }} +{% else %} + {{ resources.include_lab_theme(resources.theme) }} +{% endif %} + + +{% endblock notebook_css %} + +{%- block html_head_js_mathjax -%} +{{ mathjax(resources.mathjax_url) }} +{%- endblock html_head_js_mathjax -%} + +{%- block html_head_js_mermaidjs -%} +{{ mermaid_js(resources.mermaid_js_url) }} +{%- endblock html_head_js_mermaidjs -%} + +{%- block html_head_css -%} +{%- endblock html_head_css -%} + +{%- endblock html_head -%} + +{%- endblock header -%} + +{%- block body_header -%} +{% if resources.theme == 'dark' %} + +{% else %} + +{% endif %} +
      +{%- endblock body_header -%} + +{% block body_footer %} +
      + +{% endblock body_footer %} + +{% block footer %} +{% block footer_js %} +{% endblock footer_js %} +{{ super() }} + +{% endblock footer %} diff --git a/nbconvert_a11y/templates/a11y/settings.js b/nbconvert_a11y/templates/a11y/static/settings.js similarity index 100% rename from nbconvert_a11y/templates/a11y/settings.js rename to nbconvert_a11y/templates/a11y/static/settings.js 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/style.css b/nbconvert_a11y/templates/a11y/static/style.css similarity index 100% rename from nbconvert_a11y/templates/a11y/style.css rename to nbconvert_a11y/templates/a11y/static/style.css diff --git a/nbconvert_a11y/templates/a11y/table.html.j2 b/nbconvert_a11y/templates/a11y/table.html.j2 index 507614ef..2171de39 100644 --- a/nbconvert_a11y/templates/a11y/table.html.j2 +++ b/nbconvert_a11y/templates/a11y/table.html.j2 @@ -1,8 +1,7 @@ -{%- extends 'semantic-forms/base.html.j2' -%} +{%- extends 'a11y/base.html.j2' -%} {% 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 #} @@ -11,21 +10,12 @@ {% 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 %}{{cell_row(cell, loop)}}{% endblock any_cell %} + {%- endfor -%} - {# needs a header row #} @@ -64,13 +54,13 @@ -{{super()}} -{% endblock body_footer %} +{% endblock body_loop %} {% 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 %} +{% if t %}{% if t.endswith("Z") %}{% set t = t[:-1] + "+00:00" %}{% endif %}{% endif %} {% endmacro %} {% macro cell_row(cell, loop) %} 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/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) -%} - -{%- 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) -%} -