diff --git a/nbconvert_a11y/exporter.py b/nbconvert_a11y/exporter.py index c333f074..9f5da633 100644 --- a/nbconvert_a11y/exporter.py +++ b/nbconvert_a11y/exporter.py @@ -112,9 +112,7 @@ class A11yExporter(PostProcess, HTMLExporter): include_toc = Bool( True, help="collect a table of contents of the headings in the document" ).tag(config=True) - include_summary = Bool( - True, help="collect notebook properties into a summary" - ).tag(config=True) + include_summary = Bool(True, help="collect notebook properties into a summary").tag(config=True) wcag_priority = Enum( ["AAA", "AA", "A"], "AA", help="the default inital wcag priority to start with" ).tag(config=True) @@ -124,11 +122,11 @@ class A11yExporter(PostProcess, HTMLExporter): include_cell_index = Bool( True, help="show the ordinal cell index, typically this is ignored from notebooks." ).tag(config=True) - include_visibility = Bool(False, help="include visibility toggle").tag(config=True) + include_visibility = Bool(True, help="include visibility toggle").tag(config=True) include_upload = Bool(False, help="include template for uploading new content").tag(config=True) allow_run_mode = Bool(False, help="enable buttons for a run mode").tag(config=True) hide_anchor_links = Bool(False).tag(config=True) - exclude_anchor_links = Bool(True).tag(config=True) + exclude_anchor_links = Bool(False).tag(config=True) code_theme = Enum(list(THEMES), "gh-high", help="an accessible pygments dark/light theme").tag( config=True ) @@ -187,7 +185,7 @@ def init_resources(self, resources=None): resources["include_help"] = self.include_help resources["include_toc"] = self.include_toc resources["include_summary"] = self.include_summary - resources["include_visibility"] = self.include_upload + resources["include_visibility"] = self.include_visibility resources["include_upload"] = self.include_upload resources["wcag_priority"] = self.wcag_priority resources["accesskey_navigation"] = self.accesskey_navigation @@ -212,14 +210,12 @@ def from_notebook_node(self, nb, resources=None, **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) - details = soup.select_one("""[aria-labelledby="nb-toc"] details""") - if details: - details.extend(soupify(toc(soup)).body.children) - for x in details.select("ul"): - x.name = "ol" - details.select_one("ol").attrs["aria-labelledby"] = "nb-toc" + if self.include_toc: + details = soup.select_one("""[aria-labelledby="nb-toc"] details""") + if details: + if not details.select_one("nav"): + details.append(toc(soup)) return soup.prettify(formatter="html5") @@ -270,7 +266,7 @@ def highlight(code, lang="python", attrs=None, experimental=True): 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 + "html", debug_token_types=False, title=f"{lang} code", wrapcode=True ) try: return pygments.highlight( @@ -287,31 +283,39 @@ def soupify(body: str) -> BeautifulSoup: return BeautifulSoup(body, features="html5lib") -def mdtoc(html): +def toc(html): """Create a table of contents in markdown that will be converted to html""" - import io - toc = io.StringIO() + toc = BeautifulSoup(features="html.parser") + toc.append(nav := toc.new_tag("nav")) + nav.append(ol := toc.new_tag("ol")) + last_level = 1 + headers = set() for header in html.select(".cell :is(h1,h2,h3,h4,h5,h6)"): + if header in headers: + continue + headers.add(header) id = header.attrs.get("id") if not id: - from slugify import slugify - - if header.string: - id = slugify(header.string) - else: - continue - + continue # there is missing logistics for managely role=heading # adding code group semantics will motivate this addition level = int(header.name[-1]) - toc.write(" " * (level - 1) + f"* [{header.string}](#{id})\n") - return toc.getvalue() - - -def toc(html): - """Create an html table of contents""" - return get_markdown(mdtoc(html)) + if last_level > level: + for l in range(level, last_level): + last_level -= 1 + ol = ol.parent.parent + elif last_level < level: + for l in range(last_level, level): + last_level += 1 + ol.append(li := toc.new_tag("li")) + li.append(ol := toc.new_tag("ol")) + ol.append(li := toc.new_tag("li")) + li.append(a := toc.new_tag("a")) + a.append(header.text) + a.attrs.update(href=f"#{id}") + + return toc def heading_links(html): @@ -321,12 +325,12 @@ def heading_links(html): if not id: from slugify import slugify - if header.string: - id = slugify(header.string) + if header.text: + id = slugify(header.text) else: continue - link = soupify(f"""{header.string}""").body.a + link = soupify(f"""{header.text}""").body.a header.clear() header.append(link) @@ -362,15 +366,6 @@ def count_code_cells(nb): 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["aria-describedby"] = ( - "nb-cells-count-label nb-cells-label nb-code-cells nb-code-cells-label nb-ordered nb-loc nb-loc-label" - ) - - def is_ordered(nb) -> str: """Measure if the notebook is ordered""" start = 0 diff --git a/nbconvert_a11y/templates/a11y/base.html.j2 b/nbconvert_a11y/templates/a11y/base.html.j2 index 5c57534a..c7ac7280 100644 --- a/nbconvert_a11y/templates/a11y/base.html.j2 +++ b/nbconvert_a11y/templates/a11y/base.html.j2 @@ -13,6 +13,8 @@ the notebook experiennce from browse to edit/focus mode. {% set title = nb.metadata.get('title', resources['metadata']['name']) | escape_html_keep_quotes %} {%- block html_head_js -%} +{% if nb.metadata.description %} +{% endif %} {# sa11y needs to be loaded before requirejs #} {% if resources.include_sa11y %}{% include "a11y/components/sa11y.html.j2"%}{% endif %} @@ -24,7 +26,7 @@ the notebook experiennce from browse to edit/focus mode. {% include "a11y/static/style.css" %} {% for theme in ["light", "dark"] %} - {% endfor %} @@ -33,12 +35,21 @@ the notebook experiennce from browse to edit/focus mode. {% block extra_css %}{% endblock %} {% block body_header %} +{%- set title = nb.metadata.get("title") -%} +{%- set description = nb.metadata.get("description") -%} +{% set describedby = "nb-ct-total nb-cells-label nb-ct-code nb-state nb-ct-loc nb-loc-label +nb-ct-outputs nb-outputs-label"%}
- skip to main content - {{site_navigation | default("")}} - {%- if resources.include_summary -%}{% include "a11y/components/nb-summary.html.j2"%}{%- endif -%} + skip to main content
+ {% if resources.include_settings %} + + {% endif %} + + + {{resources.header | default("")}} + {%- if resources.include_summary -%}{% include "a11y/components/nb-summary.html.j2" %}{%- endif -%} {%- if resources.include_toc -%}{% include "a11y/components/toc.html.j2" %}{%- endif -%}
@@ -58,12 +69,10 @@ the notebook experiennce from browse to edit/focus mode. {# skip to top is needed for long notebooks. it is difficult to access for keyboard users. #}
+ {{resources.footer}} {% include "a11y/components/nb-toolbar.html.j2" %} {# make the individual settings discoverable before all the settings when using shift+tab #} - {% if resources.include_settings %} - - {% endif %} {% if resources.include_visibility %} {% endif %} @@ -75,8 +84,7 @@ the notebook experiennce from browse to edit/focus mode. {% if resources.include_visibility %}{% include "a11y/components/visibility.html.j2"%}{% endif %} {% if resources.include_help %}{% include "a11y/components/help.html.j2" %}{% endif %} {% if resources.include_axe %}{% include "a11y/components/audit.html.j2" %}{% endif %} - {{footer}} - {{activity_log()}} + {{activity_log(id=True)}} skip to top
{% if resources.include_upload %}{% block template_element %}{% endblock %}{% endif %} diff --git a/nbconvert_a11y/templates/a11y/components/cell.html.j2 b/nbconvert_a11y/templates/a11y/components/cell.html.j2 index 2315f650..4e3ffd88 100644 --- a/nbconvert_a11y/templates/a11y/components/cell.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/cell.html.j2 @@ -2,19 +2,17 @@ {% from "a11y/components/displays.html.j2" import cell_display_priority with context %} {% macro cell_anchor(i, cell_type, execution_count=None, outputs=None, hidden=False)%} -{% if not resources.exclude_anchors_links %} {{i}} -{% endif %} + cell-{{i}}-outputs-len{% endif %}" {{hide(hidden or resources.exclude_anchor_links)}}>{{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 @@ -38,10 +36,10 @@ that would include talking to the kernel. #} {% endmacro %} {% macro cell_execution_count(i, execution_count, hidden=False) %} -
{% endmacro %} -{%- macro cell_output(i, cell, source, outputs, cell_type, execution_count, hidden=False) -%} +{%- macro cell_output(i, cell, source, outputs, cell_type, metadata, execution_count, hidden=False) -%} {% set CODE = cell_type == "code" %} {% if CODE %} {% if execution_count %} @@ -77,7 +76,7 @@ that would include talking to the kernel. #} {% endif %} {# the following span should get its own column in the table #}
@@ -92,9 +91,16 @@ that would include talking to the kernel. #} {{cell_display_priority(i, outputs, cell)}} {% endif %}
-{% else %} +{%- else %} +{%- set html = metadata.get("data", {}).get("text/html") %} +{%- if html -%} +{# if there is a data bundle in the metadata then we can use that +instead of computing the markup #} +{{html}} +{%- else -%} {{ markdown(source) | strip_files_prefix }} -{% endif %} +{%- endif -%} +{%- endif -%} {%- endmacro -%} {% macro cell_loc(i, cell, tag="span") %} @@ -112,7 +118,7 @@ that would include talking to the kernel. #} {{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_loc(loop.index, cell)}} - {{cell_output(loop.index, cell, cell.source, cell.outputs, cell.cell_type, cell.execution_count)}} + {{cell_output(loop.index, cell, cell.source, cell.outputs, cell.cell_type, cell.metadata, 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 index d4c0e077..533b921f 100644 --- a/nbconvert_a11y/templates/a11y/components/core.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/core.html.j2 @@ -47,7 +47,7 @@ at some point effort should be put into separating context dependent and indepen
activity log
- +
diff --git a/nbconvert_a11y/templates/a11y/components/nb-summary.html.j2 b/nbconvert_a11y/templates/a11y/components/nb-summary.html.j2 index 79e1602a..ad87cebb 100644 --- a/nbconvert_a11y/templates/a11y/components/nb-summary.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/nb-summary.html.j2 @@ -1,7 +1,3 @@ -{%- set title = nb.metadata.get("title") -%} -{%- set description = nb.metadata.get("description") -%} -{% set describedby = "nb-ct-total nb-cells-label nb-ct-code nb-state nb-ct-loc nb-loc-label -nb-ct-outputs nb-outputs-label"%}
notebook summary diff --git a/nbconvert_a11y/templates/a11y/components/settings.html.j2 b/nbconvert_a11y/templates/a11y/components/settings.html.j2 index 88dc10db..f69f705c 100644 --- a/nbconvert_a11y/templates/a11y/components/settings.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/settings.html.j2 @@ -5,6 +5,17 @@ settings provides multiple screen reader navigation techniques when the dialog i {% from "a11y/components/core.html.j2" import h, radiogroup, select, activity_log, input_number, checkbox, dialog_close %} +{% set font_sizes = {"xx-small": "xx-small", +"x-small": "x-small", +"small": "small", +"medium": "medium", +"large": "120%", +"x-large": "150%", +"xx-large": "200%", +"xxx-large": "300%", +"xxxx-large": "400%", +"xxxxx-large": "500%"} +%} {{h(1, "accessibility settings")}} @@ -13,22 +24,26 @@ settings provides multiple screen reader navigation techniques when the dialog i
  • {{h(2, "notebook layout")}}
      + {# the best implementation would make these configurable for folks who change them often. #}
    • {{select("color scheme", {"light mode": "light", "dark mode": "dark"})}}
    • -
    • {{input_number("margin", 5, min=0, max=40, step=5, extra_title="%")}}
    • +
    • {{checkbox("horizontal scrolling", False)}}
    • {{input_number("line height", 1.5, min=0.5, max=3, step=0.1)}}
    • + {# screen overflow exists for digital magnifier users to follow long horizontal lines of text. #}
    • {{select("cell navigation", default=resources.table_pattern.__name__.lower(), values="list table region presentation".split(), disabled="grid treegrid tree".split())}}
    • {% set priority = {"Triple A": "AAA","Double A": "AA", "Single A": "A"}%}
    • {{select("accessibility priority", priority, resources.wcag_priority)}}
    • +
    • {{input_number("margin", 5, min=0, max=40, step=5, extra_title="%")}}
    • {{checkbox("accesskey navigation", True)}}
  • + {{h(2, "font settings")}}
      -
    • {{select("font size", "xx-small x-small small medium large x-large xx-large".split(), +
    • {{select("font size", font_sizes, "medium")}}
    • {{select("font family", "serif sans-serif".split())}} diff --git a/nbconvert_a11y/templates/a11y/components/toc.html.j2 b/nbconvert_a11y/templates/a11y/components/toc.html.j2 index 0d43da00..9bce528a 100644 --- a/nbconvert_a11y/templates/a11y/components/toc.html.j2 +++ b/nbconvert_a11y/templates/a11y/components/toc.html.j2 @@ -10,6 +10,6 @@ table of contents {# the table of contents is populated in python. #} -
        + {{resources.toc|default("")}}
    • \ No newline at end of file diff --git a/nbconvert_a11y/templates/a11y/static/settings.js b/nbconvert_a11y/templates/a11y/static/settings.js index 3c5bb56c..60fa4c4a 100644 --- a/nbconvert_a11y/templates/a11y/static/settings.js +++ b/nbconvert_a11y/templates/a11y/static/settings.js @@ -32,12 +32,14 @@ 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("media", "screen"); - document.getElementById(`nb-${opposite}-theme`).setAttribute("media", "not screen"); +function toggleColorScheme(value = null) { + value = value === null ? document.forms.settings.elements["color-scheme"].value : value; + let DARK = value == "dark"; + let opposite = DARK ? "light" : "dark"; + document.getElementById(`nb-${value}-highlight`).removeAttribute("media", "screen"); + document.getElementById(`nb-${opposite}-highlight`).setAttribute("media", "not screen"); document.querySelector(`head > meta[name="color-scheme"]`).setAttribute("content", value); + BODY.classList.toggle("dark", DARK); activityLog(`${value} mode activated`) } function toggleRole() { @@ -152,7 +154,7 @@ function setWCAG() { ); } document.forms.settings.elements["accessibility-priority"].addEventListener("change", setWCAG); - function toggleActive() { +function toggleActive() { if (document.forms.notebook.elements.edit.checked) { document.querySelectorAll("tr.cell>td>details>summary[inert]").forEach( x => x.removeAttribute("inert") @@ -192,12 +194,84 @@ function openDialogs() { ); event.target.focus(); } + document.forms.visibility['visually-hide'].addEventListener("change", (x) => { document.querySelector("main").classList.toggle("visually-hide"); activityLog(`${event.target.checked ? "hiding" : "showing"} main content`); }); +document.forms.settings['horizontal-scrolling'].addEventListener("change", + (x) => { + BODY.classList.toggle("horiz-overflow", event.target.checked); + if (!event.target.checked) { + document.querySelectorAll("textarea").forEach( + (x) => { + x.style.width = ""; + x.style.height = ""; + } + ) + }; + // activityLog(`${event.target.checked ? "overflow scrol" : "showing"} main content`); + }); + + +function fullScreen() { + if (!document.fullscreenElement) { + document.querySelector("main").requestFullscreen(); + } else { + document.exitFullscreen(); + } +} + +// resizing hits the width before the height. +// width resizes trigger height resizes and height resizes need to be computed from scratch. +// we handle that logic with boolean flags when sets the width/height. +function setTextareaWidth(entry, set = null) { + if (set === null) { return } + if (set) { + entry.target.style.width = ""; + let props = getComputedStyle(entry.target); + let width = entry.target.scrollWidth, + left = Number(props.borderLeftWidth.slice(0, -2)), + right = Number(props.borderRightWidth.slice(0, -2)); + entry.target.style.width = `${Math.ceil(width) + Math.ceil(left) + Math.ceil(right) + 1}px` + setTextareaHeight(entry, true); + } +} + +function setTextareaHeight(entry, reset = null) { + if (reset === null) { return } + let props = getComputedStyle(entry.target); + if (reset) { + entry.target.style.height = "" + return setTextareaHeight(entry, false) + } else { + let height = entry.target.scrollHeight; + let top = Number(props.borderTopWidth.slice(0, -2)); + let bottom = Number(props.borderBottomWidth.slice(0, -2)); + entry.target.style.height = `${Math.ceil(height) + Math.ceil(top) + Math.ceil(bottom) + 1}px`; + } + +} +let observer = new ResizeObserver( + (entries) => { + entries.forEach((entry) => { + (BODY.matches(".horiz-overflow") ? setTextareaWidth : setTextareaHeight)(entry, true); + }); + } +); + +document.querySelectorAll("textarea").forEach( + (x) => { + observer.observe(x); + } +); + +if (!document.fullscreenEnabled) { + document.getElementById("nb-fullscreen").setAttribute("hidden", ""); +} + setStyle("initialize saved settings.") // async function runSource(target) { // { diff --git a/nbconvert_a11y/templates/a11y/static/style.css b/nbconvert_a11y/templates/a11y/static/style.css index 8268d702..61deac0a 100644 --- a/nbconvert_a11y/templates/a11y/static/style.css +++ b/nbconvert_a11y/templates/a11y/static/style.css @@ -7,19 +7,19 @@ the variables specify critical degrees of freedom in the notebook style. ## notebook interface specific css 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; --nb-scrolled-height: 600px; + --color-light: #FFFFFF; + --color-dark: #2b2a33; } + /* map css variables to css properties on the `body` elements. this mapping turns out css variables into user interfaces. */ @@ -31,6 +31,22 @@ body { margin-right: var(--nb-margin); line-height: var(--nb-line-height); width: calc(100% - 2*var(--nb-margin)); + color: var(--color-dark); + background-color: var(--color-light); +} + +body.dark { + color: var(--color-light); + background-color: var(--color-dark); +} + +main:fullscreen { + overflow: auto; + background: var(--color-light); +} + +.dark main:fullscreen { + background: var(--color-dark); } /* ## notebook settings @@ -86,8 +102,14 @@ WCAG 2.1 defines a huge stinking AAA hit size for buttons. its beautiful. font-size: max(24px, var(--nb-font-size)); } +/* remove the color styling for AAA */ +.wcag-aaa .highlight pre code span[class] { + color: unset; +} + /* ### native textareas for AAA priority. + our AAA priority choices often ensure that third party libraries can not compromise the accessibility of notebook. the design decisions are focused native elements so that the assistive technology users have consistent expectations and outcomes. @@ -102,6 +124,18 @@ the design decisions are focused native elements so that the assistive technolog display: none; } +/* line height cant change pre elements based on browser presets */ +pre, +textarea { + line-height: unset; + white-space: pre-wrap; +} + +.horiz-overflow pre, +.horiz-overflow textarea { + white-space: pre; +} + /* this is only needed for the lsit template variant */ ol#cells { margin: unset; @@ -116,7 +150,7 @@ li.cell { td[role="none"]:not([hidden]), th[role="none"]:not([hidden]) { - display: unset; + display: block; text-align: unset; } @@ -124,9 +158,9 @@ table { border-spacing: unset; } -main>.cell, -#cells:not([role=table]) .cell, -#cells:not([role=table])>tbody { +#cells, +#cells .cell, +#cells>tbody { display: flex; flex-direction: column; } @@ -162,12 +196,33 @@ we start with her [universal focus recommendation](https://www.sarasoueidan.com/ /* on firefox, the input and output become interactive when there is overflow. chrome fixed this recently find reference.*/ -textarea[name=source], -.cell>td>details { - overflow: auto; +textarea[name=source] { min-width: 0; } +td.nb-outputs, +td.nb-source { + overflow-x: auto; +} + +.horiz-overflow td.nb-outputs, +.horiz-overflow header, +.horiz-overflow footer { + width: max-content; +} + +.horiz-overflow td.nb-source { + overflow-x: visible; +} + +.horiz-overflow td.nb-outputs { + min-width: 100%; +} + +.horiz-overflow .cell { + width: fit-content; +} + #nb-settings li::marker, summary[inert]::marker { content: ""; @@ -193,7 +248,7 @@ textarea { textarea[name=source] { box-sizing: border-box; width: 100%; - resize: vertical; + resize: none; } @@ -220,7 +275,7 @@ details.log:not([open])+table, .nb-outputs.nb-scrolled { max-height: var(--nb-scrolled-height); - overflow: auto; + overflow-y: auto; } .nb-outputs.nb-collapsed { diff --git a/nbconvert_a11y/templates/a11y/table.html.j2 b/nbconvert_a11y/templates/a11y/table.html.j2 index a05c0919..60acf50c 100644 --- a/nbconvert_a11y/templates/a11y/table.html.j2 +++ b/nbconvert_a11y/templates/a11y/table.html.j2 @@ -9,26 +9,25 @@ cell_form, cell_source, cell_metadata, cell_output with context%} {%- set include_outputs = True -%} {%- set include_input = True -%} {%- if cell.cell_type in ["raw"] -%} -{%- set include_input = resources.global_content_filter.include_raw and not ( -cell.metadata.get("transient",{}).get("remove_source", false) or cell.metadata.get("jupyter", -{}).get("source_hidden", False))-%} +{%- set include_input = resources.global_content_filter.include_raw and +not cell.metadata.get("transient",{}).get("remove_source", false) and not cell.metadata.get("jupyter", +{}).get("source_hidden", False)-%} {%- elif cell.cell_type in ["markdown"]-%} -{%- set include_input = resources.global_content_filter.include_markdown and not ( -cell.metadata.get("transient",{}).get("remove_source", false) or cell.metadata.get("jupyter", -{}).get("source_hidden", False)) -%} +{%- set include_input = resources.global_content_filter.include_markdown and +not cell.metadata.get("transient",{}).get("remove_source", false) and not cell.metadata.get("jupyter", +{}).get("source_hidden", False) -%} {%- elif cell.cell_type in ["code"]-%} -{%- set include_input = resources.global_content_filter.include_code and not (not +{%- set include_input = resources.global_content_filter.include_code and resources.global_content_filter.include_input -or cell.metadata.get("jupyter", {}).get("source_hidden", False)) -or cell.metadata.get("transient",{}).get("remove_source", false) -%} +and not cell.metadata.get("jupyter", {}).get("source_hidden", False) +and not cell.metadata.get("transient",{}).get("remove_source", false) -%} {%- set include_outputs = cell.outputs and resources.global_content_filter.include_output -or not cell.metadata.get("jupyter", {}).get("outputs_hidden", False)-%} +and not cell.metadata.get("jupyter", {}).get("outputs_hidden", False)-%} {%- endif -%} {%- set slide_type = cell.get("metadata", {}).get("slide_type") -%} + {% if cell.cell_type=="code" %} {% endif %} data-index="{{index}}" data-slide_type={% if slide_type %}"{{slide_type}}" {% endif %}> {% endmacro %} @@ -89,7 +89,7 @@ the template provides the structure for new cells when they are added. #} {% block template_element %} {% endblock %} \ No newline at end of file
      message
      {{cell_anchor(index, cell.cell_type, cell.execution_count, cell.outputs)}} + cell.metadata, + cell.get("execution_count"))}}