From b0ab1e8f3f1715560a6507c2c0ba15bc0605e5f3 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 2 Nov 2022 19:13:25 -0500 Subject: [PATCH] Add basic markdown presenter (#32) --- CHANGELOG.md | 2 + atest/resources/Deck.resource | 81 ++++-- atest/resources/DeckSelectors.resource | 8 +- atest/resources/Design.resource | 4 +- atest/resources/Lab.resource | 3 + atest/resources/LabSelectors.resource | 12 + atest/resources/Screenshots.resource | 25 ++ atest/suites/__init__.robot | 2 + atest/suites/lab/01-examples.robot | 40 ++- atest/suites/lab/02-navigate.robot | 6 +- atest/suites/lab/03-layers.robot | 2 +- atest/suites/lab/04-design-tools.robot | 5 +- atest/suites/lab/05-slide-layout.robot | 3 +- examples/README.ipynb | 15 +- examples/README.md | 30 +- examples/hello_world.py | 7 + js/jupyterlab-deck/package.json | 1 + js/jupyterlab-deck/src/manager.ts | 34 ++- js/jupyterlab-deck/src/markdown/presenter.ts | 186 ++++++++++++ js/jupyterlab-deck/src/notebook/presenter.ts | 6 +- js/jupyterlab-deck/src/plugin.ts | 17 +- js/jupyterlab-deck/src/tokens.ts | 36 ++- js/jupyterlab-deck/src/tools/remote.tsx | 18 +- js/jupyterlab-deck/style/index.css | 7 +- js/jupyterlab-deck/style/markdown.css | 26 ++ yarn.lock | 283 ++++++++++--------- 26 files changed, 652 insertions(+), 207 deletions(-) create mode 100644 examples/hello_world.py create mode 100644 js/jupyterlab-deck/src/markdown/presenter.ts create mode 100644 js/jupyterlab-deck/style/markdown.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a9dcc2..25ea3cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ ### `0.1.3` (unreleased) +- [#19] adds a basic Markdown presenter, with slides delimited by `---` - [#22] adds a stack of previously-viewed documents when navigating between documents - [#27] adds a drag-and-drop _slide layout_ overlay and design tools to support customization - [#29] adds support for using `#
` anchors while presenting +[#19]: https://github.com/deathbeds/jupyterlab-deck/issues/19 [#22]: https://github.com/deathbeds/jupyterlab-deck/issues/22 [#27]: https://github.com/deathbeds/jupyterlab-deck/issues/27 [#29]: https://github.com/deathbeds/jupyterlab-deck/issues/29 diff --git a/atest/resources/Deck.resource b/atest/resources/Deck.resource index fdd6851..9cf314c 100644 --- a/atest/resources/Deck.resource +++ b/atest/resources/Deck.resource @@ -13,15 +13,38 @@ ${ZERO_PAD} {0:03d} *** Keywords *** Start Deck With Notebook Toolbar Button [Documentation] Use the notebook toolbar to start deck. - Wait Until Element Is Not Visible css:${CSS_LAB_SPINNER} + Wait Until Element Is Not Visible css:${CSS_LAB_SPINNER} timeout=1s Click Element css:${JLAB CSS ACTIVE DOC} Click Element css:${CSS_DECK_NOTEBOOK_BUTTON} - Wait Until Element Is Visible css:${CSS_DECK_PRESENTING} - Wait Until Element Is Visible css:${CSS_DECK_VISIBLE} + Wait Until Element Is Visible css:${CSS_DECK_PRESENTING} timeout=1s + Wait Until Element Is Visible css:${CSS_DECK_VISIBLE} timeout=1s Really Start Deck With Notebook Toolbar Button [Documentation] REALLY use the notebook toolbar to start deck. + Send Error Screenshots To Trash Wait Until Keyword Succeeds 5x 0.1s Start Deck With Notebook Toolbar Button + [Teardown] Resume Screenshots + +Start Markdown Deck From Editor + [Documentation] Start a deck from a markdown editor with the context menu. + [Arguments] ${host} + Select From Context Menu ${CSS_LAB_EDITOR} li${CSS_LAB_CMD_MARKDOWN_PREVIEW} + Close JupyterLab Dock Panel Tab ${host} + Close JupyterLab Dock Panel Tab Launcher + Click Element css:${CSS_LAB_MARKDOWN_VIEWER} + Execute JupyterLab Command Start Deck + Wait Until Element Is Visible css:${CSS_DECK} + Click Element css:${CSS_DECK} + +Select From Context Menu + [Documentation] Open the context menu and click an item. + [Arguments] ${host} ${item} + Open Context Menu css:${host} + Wait Until Element Is Visible css:${item} + Mouse Over css:${item} ${CSS_LM_MENU_ITEM_LABEL} + Wait Until Element Is Visible css:${item}${CSS_LM_MOD_ACTIVE} + Click Element css:${item}${CSS_LM_MOD_ACTIVE} ${CSS_LM_MENU_ITEM_LABEL} + Wait Until Element Is Not Visible css:${item} Stop Deck With Remote [Documentation] Use the on-screen remote to stop deck. @@ -60,12 +83,7 @@ Advance Deck With Remote And Screenshot Maybe Click An Anchor And Return [Documentation] If a `#` anchor exists, click it, and come back [Arguments] ${host} ${screenshot}=${EMPTY} - IF ${host.__contains__(".ipynb")} - ${anchor_links} = Get WebElements - ... css:${JLAB CSS ACTIVE CELL}${CSS_DECK_VISIBLE} ${CSS_LAB_NOT_INTERNAL_ANCHOR} - ELSE - ${anchor_links} = Get WebElements css:${JLAB CSS ACTIVE DOC} ${CSS_LAB_NOT_INTERNAL_ANCHOR} - END + ${anchor_links} = Find Cross-Document Anchor Links ${host} IF not ${anchor_links.__len__()} RETURN ${href} = Get Element Attribute ${anchor_links[0]} href IF ${href.__contains__('''${host}''')} RETURN @@ -75,10 +93,41 @@ Maybe Click An Anchor And Return Click Element css:${CSS_DECK_DIR_STACK} button Sleep 0.1s -Advance Deck With Keyboard +Find Cross-Document Anchor Links + [Documentation] Find anchor links that don't reference their host. + [Arguments] ${host} + ${anchor_links} = Create List + IF ${host.__contains__(".ipynb")} + ${anchor_links} = Get WebElements + ... css:${JLAB CSS ACTIVE CELL}${CSS_DECK_VISIBLE} ${CSS_LAB_NOT_INTERNAL_ANCHOR} + ELSE IF ${host.__contains__(".md")} + ${anchor_links} = Get WebElements + ... css:${CSS_LAB_MARKDOWN_VIEWER} ${CSS_LAB_NOT_INTERNAL_ANCHOR}:not([href^\='#']) + END + ${anchor_links} = Filter Visible Elements ${anchor_links} + RETURN ${anchor_links} + +Filter Visible Elements + [Documentation] Filter a list down to just the visible ones. + [Arguments] ${elements} + ${visible} = Create List + Send Error Screenshots To Trash + FOR ${el} IN @{elements} + TRY + Element Should Be Visible ${el} + Append To List ${visible} ${el} + EXCEPT + Log ${el} is not visible + END + END + RETURN ${visible} + [Teardown] Resume Screenshots + +Advance Notebook Deck With Keyboard [Documentation] Go to the down/forward slide with space, wait a bit, then screenshot. [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} ${backup}=${FALSE} ${index} = Get Active Cell Index + Send Error Screenshots To Trash IF ${backup} Press Keys css:body SHIFT+SPACE ELSE @@ -87,23 +136,23 @@ Advance Deck With Keyboard Wait Until Cell Is Not Active ${index} 1s IF ${expect.__len__()} Wait Until Element Contains css:${JLAB CSS ACTIVE CELL} ${expect} - ELSE - Sleep 0.2s END + Resume Screenshots IF ${screenshot.__len__()} Capture Page Screenshot ${screenshot} + [Teardown] Resume Screenshots Back Up Deck With Keyboard [Documentation] Go to the up/back slide with space, wait a bit, then screenshot. [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} - Advance Deck With Keyboard ${screenshot} ${expect} backup=${TRUE} + Advance Notebook Deck With Keyboard ${screenshot} ${expect} backup=${TRUE} -Really Advance Deck With Keyboard +Really Advance Notebook Deck With Keyboard [Documentation] REALLY go to the down/forward slide with space, wait a bit, then screenshot. [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} ${backup}=${FALSE} Wait Until Keyword Succeeds 5x 0.5s - ... Advance Deck With Keyboard ${screenshot} ${expect} ${backup} + ... Advance Notebook Deck With Keyboard ${screenshot} ${expect} ${backup} Really Back Up Deck With Keyboard [Documentation] REALLY go to the up/back slide with space, wait a bit, then screenshot. [Arguments] ${screenshot}=${EMPTY} ${expect}=${EMPTY} - Really Advance Deck With Keyboard ${screenshot} ${expect} backup=${TRUE} + Really Advance Notebook Deck With Keyboard ${screenshot} ${expect} backup=${TRUE} diff --git a/atest/resources/DeckSelectors.resource b/atest/resources/DeckSelectors.resource index 5d2a5b5..f7e721e 100644 --- a/atest/resources/DeckSelectors.resource +++ b/atest/resources/DeckSelectors.resource @@ -6,9 +6,8 @@ Documentation Selectors defined in this repo... could be loaded from JSON? # body ${CSS_DECK_PRESENTING} [data-jp-deck-mode="presenting"] -# cells -${CSS_DECK_VISIBLE} .jp-deck-mod-visible -${CSS_DECK_ONSCREEN} .jp-deck-mod-onscreen +# deck +${CSS_DECK} .jp-Deck # remote ${CSS_DECK_STOP} .jp-Deck-Remote .jp-deck-mod-stop @@ -21,8 +20,11 @@ ${CSS_DECK_DIR_STACK} .jp-Deck-Remote .jp-Deck-Remote-WidgetStack @{CSS_DECK_NEXT} down forward @{CSS_DECK_PREV} up back + # notebook ${CSS_DECK_NOTEBOOK_BUTTON} .jp-ToolbarButtonComponent\[data-command="deck:toggle"] +${CSS_DECK_VISIBLE} .jp-deck-mod-visible +${CSS_DECK_ONSCREEN} .jp-deck-mod-onscreen # metadata ${CSS_DECK_LAYER_SELECT} \#id-jp-decktools-select-layer diff --git a/atest/resources/Design.resource b/atest/resources/Design.resource index 15deabb..83f40ba 100644 --- a/atest/resources/Design.resource +++ b/atest/resources/Design.resource @@ -62,8 +62,8 @@ Resize A Part IF not ${screenshot.__len__()} RETURN Capture Page Screenshot ${screenshot} -Unstyle A Part - [Documentation] Use the reset button to unstyle a part. +Unfix A Part + [Documentation] Use the reset button to unfix a part. [Arguments] ${index} ${screenshot}=${EMPTY} ${part} = Set Variable ${CSS_DECK_LAYOVER_PART}:nth-child(${index}) ${unstyle} = Set Variable ${part} ${CSS_DECK_LAYOVER_UNSTYLE} diff --git a/atest/resources/Lab.resource b/atest/resources/Lab.resource index cb2dbdb..efc4568 100644 --- a/atest/resources/Lab.resource +++ b/atest/resources/Lab.resource @@ -2,6 +2,7 @@ Documentation Keywords for working with decks. Resource ./LabSelectors.resource +Resource ./Screenshots.resource Library Collections Library JupyterLibrary @@ -26,6 +27,7 @@ Enter Command Mode Really Set Cell Type With Keyboard [Documentation] Use the keyboard to change the cell type. [Arguments] ${index} ${type} ${limit}=10 ${timeout}=1s + Send Error Screenshots To Trash ${ok} = Set Variable ${FALSE} WHILE not ${ok} limit=${limit} ${ok} = Run Keyword And Return Status Set Cell Type With Keyboard ${index} ${type} ${timeout} @@ -39,6 +41,7 @@ Really Set Cell Type With Keyboard END Sleep 0.1s END + [Teardown] Resume Screenshots Set Cell Type With Keyboard [Documentation] Use the keyboard to change the cell type. diff --git a/atest/resources/LabSelectors.resource b/atest/resources/LabSelectors.resource index f7892a8..9efa1f2 100644 --- a/atest/resources/LabSelectors.resource +++ b/atest/resources/LabSelectors.resource @@ -3,6 +3,12 @@ Documentation Selectors that should maybe go upstream. *** Variables *** +# lumino +${CSS_LM_MOD_ACTIVE} .lm-mod-active +${CSS_LM_MENU_ITEM_LABEL} .lm-Menu-itemLabel +${CSS_LM_CLOSE_ICON} .lm-TabBar-tabCloseIcon + +# lab ${CSS_LAB_FILES_HOME} .jp-BreadCrumbs-home ${CSS_LAB_FILES_DIR_ITEM} .jp-DirListing-item ${CSS_LAB_SPINNER} .jp-Spinner @@ -21,7 +27,13 @@ ${CSS_LAB_CELL_META_JSON_CM_HIDDEN} ${CSS_LAB_MOD_HIDDEN} ${CSS_LAB_CELL_MET ${CSS_LAB_ADVANCED_COLLAPSE} .jp-NotebookTools .jp-Collapse-header ${CSS_LAB_INTERNAL_ANCHOR} .jp-InternalAnchorLink ${CSS_LAB_NOT_INTERNAL_ANCHOR} a[href*\="#"]:not([href^="https"]):not(${CSS_LAB_INTERNAL_ANCHOR}) +${CSS_LAB_TAB_NOT_CURRENT} .lm-DockPanel .lm-TabBar-tab:not(.jp-mod-current) # icons ${CSS_LAB_ICON_ELLIPSES} [data-icon="ui-components:ellipses"] ${CSS_LAB_ICON_CARET_LEFT} [data-icon="ui-components:caret-left"] + +# markdown +${CSS_LAB_EDITOR} .jp-FileEditor +${CSS_LAB_MARKDOWN_VIEWER} .jp-MarkdownViewer +${CSS_LAB_CMD_MARKDOWN_PREVIEW} [data-command="fileeditor:markdown-preview"] diff --git a/atest/resources/Screenshots.resource b/atest/resources/Screenshots.resource index 42b132c..77fabd2 100644 --- a/atest/resources/Screenshots.resource +++ b/atest/resources/Screenshots.resource @@ -1,12 +1,37 @@ *** Settings *** Documentation Keywords for working with screenshots +Library OperatingSystem Library JupyterLibrary +*** Variables *** +${SCREENSHOT_TRASH} ${OUTPUT_DIR}${/}__trash__ +${OLD_SCREENSHOTS} ${OUTPUT_DIR}${/}screenshots + + *** Keywords *** Set Attempt Screenshot Directory [Documentation] Set a screenshot directory that includes the attempt [Arguments] ${path} Set Screenshot Directory ... ${OUTPUT_DIR}${/}screenshots${/}${OS.lower()[:2]}_${ATTEMPT}${/}${path} + +Send Error Screenshots To Trash + [Documentation] Throw screenshots in the trash for a while. + ${old_screens} = Set Screenshot Directory ${SCREENSHOT_TRASH} + Set Global Variable ${OLD_SCREENSHOTS} ${old_screens} + +Resume Screenshots + [Documentation] Restore Screenshots + Set Screenshot Directory ${OLD_SCREENSHOTS} + +Empty Screenshot Trash + [Documentation] Clean out trash. + Run Keyword And Ignore Error + ... Remove Directory ${SCREENSHOT_TRASH} ${TRUE} + +Capture Page Screenshot And Tag With Error + [Documentation] Capture a screenshot if not going to the trash + ${path} = Capture Page Screenshot + IF "__trash__" not in "${path}" Set Tags screenshot:unexpected diff --git a/atest/suites/__init__.robot b/atest/suites/__init__.robot index 566e25a..080d79b 100644 --- a/atest/suites/__init__.robot +++ b/atest/suites/__init__.robot @@ -15,8 +15,10 @@ Force Tags attempt:${attempt} os:${os} py:${py} Set Up Root Suite [Documentation] Do global suite setup. Set Attempt Screenshot Directory ${EMPTY} + Register Keyword To Run On Failure Capture Page Screenshot And Tag With Error Tear Down Root Suite [Documentation] Do global suite teardown. Close All Browsers Terminate All Processes + Empty Screenshot Trash diff --git a/atest/suites/lab/01-examples.robot b/atest/suites/lab/01-examples.robot index e3e8a0f..05abc48 100644 --- a/atest/suites/lab/01-examples.robot +++ b/atest/suites/lab/01-examples.robot @@ -14,13 +14,37 @@ Force Tags suite:examples *** Test Cases *** -The Example Can Be Navigated +The README Markdown Can Be Navigated [Documentation] All slides and fragments are reachable. - [Template] Visit All Example Slides And Fragments - ${README_IPYNB} - ${README_MD} - ${HISTORY_IPYNB} - ${LAYERS_IPYNB} + [Tags] activity:markdown + Visit All Example Slides And Fragments ${README_MD} + Execute JupyterLab Command Start Deck + Wait Until Element Is Visible css:${CSS_DECK} + ${anchors} = Get WebElements css:${CSS_LAB_MARKDOWN_VIEWER} a[href\^="#"] + ${anchors} = Filter Visible Elements ${anchors} + Click Element ${anchors[0]} + Capture Page Screenshot readme.md-10-post-deck-anchor.png + Press Keys css:body SHIFT+SPACE + Capture Page Screenshot readme.md-10-post-deck-reverse.png + [Teardown] Reset Example Test + +The README Notebook Can Be Navigated + [Documentation] All slides and fragments are reachable. + [Tags] activity:notebook + Visit All Example Slides And Fragments ${README_IPYNB} + [Teardown] Reset Example Test + +The History Notebook Can Be Navigated + [Documentation] All slides and fragments are reachable. + [Tags] activity:notebook + Visit All Example Slides And Fragments ${HISTORY_IPYNB} + [Teardown] Reset Example Test + +The Layers Notebook Can Be Navigated + [Documentation] All slides and fragments are reachable. + [Tags] activity:notebook feature:layers + Visit All Example Slides And Fragments ${LAYERS_IPYNB} + [Teardown] Reset Example Test *** Keywords *** @@ -32,6 +56,8 @@ Visit All Example Slides And Fragments Capture Page Screenshot ${stem}-00-before-deck.png IF ${example.endswith('.ipynb')} Really Start Deck With Notebook Toolbar Button + ELSE IF ${example.endswith('.md')} + Start Markdown Deck From Editor ${example} ELSE Execute JupyterLab Command Start Deck END @@ -39,7 +65,6 @@ Visit All Example Slides And Fragments Visit Slides And Fragments With Remote ${example} ${stem}-02-walk Stop Deck With Remote Capture Page Screenshot ${stem}-03-after-deck.png - [Teardown] Reset Example Test Set Up Example Suite [Documentation] Prepare for this suite. @@ -50,4 +75,5 @@ Reset Example Test [Documentation] Clean up after each test. Maybe Open JupyterLab Sidebar Commands Execute JupyterLab Command Stop Deck + Execute JupyterLab Command Save Execute JupyterLab Command Close All Tabs diff --git a/atest/suites/lab/02-navigate.robot b/atest/suites/lab/02-navigate.robot index 827f448..ee60e59 100644 --- a/atest/suites/lab/02-navigate.robot +++ b/atest/suites/lab/02-navigate.robot @@ -14,7 +14,7 @@ Suite Setup Set Up Interactive Suite navigate Suite Teardown Tear Down Interactive Suite Test Teardown Reset Interactive Test -Force Tags suite:navigate +Force Tags suite:navigate activity:notebook *** Test Cases *** @@ -24,8 +24,8 @@ Build and Navigate a Notebook Slide With Keyboard Start Basic Notebook Deck Really Back Up Deck With Keyboard s0-03-backup.png item1234 Really Back Up Deck With Keyboard s0-04-backup.png World - Really Advance Deck With Keyboard s0-05-advance.png item1234 - Really Advance Deck With Keyboard s0-06-advance.png item4567 + Really Advance Notebook Deck With Keyboard s0-05-advance.png item1234 + Really Advance Notebook Deck With Keyboard s0-06-advance.png item4567 Build and Navigate a Notebook Slide With Anchors [Documentation] Build and navigate a basic slide. diff --git a/atest/suites/lab/03-layers.robot b/atest/suites/lab/03-layers.robot index 4485241..4b1f546 100644 --- a/atest/suites/lab/03-layers.robot +++ b/atest/suites/lab/03-layers.robot @@ -14,7 +14,7 @@ Suite Setup Set Up Interactive Suite layers Suite Teardown Tear Down Interactive Suite Test Teardown Reset Interactive Test -Force Tags suite:layers +Force Tags suite:layers feature:layers activity:notebook *** Variables *** diff --git a/atest/suites/lab/04-design-tools.robot b/atest/suites/lab/04-design-tools.robot index 10e29db..12225d5 100644 --- a/atest/suites/lab/04-design-tools.robot +++ b/atest/suites/lab/04-design-tools.robot @@ -14,12 +14,13 @@ Suite Setup Set Up Interactive Suite design Suite Teardown Tear Down Interactive Suite Test Teardown Reset Design Tools Test -Force Tags suite:design +Force Tags suite:design activity:notebook *** Test Cases *** Slide Types [Documentation] Use the slide type tool to work with parts. + [Tags] feature:slidetype Set Attempt Screenshot Directory lab${/}design${/}slide-types Start Basic Notebook Deck Maybe Open Design Tools @@ -30,6 +31,7 @@ Slide Types Layer Scopes [Documentation] Use the layer scope tool to work with parts. + [Tags] feature:layers Set Attempt Screenshot Directory lab${/}design${/}layer-scopes Start Basic Notebook Deck Make Markdown Cell - itemA itemA @@ -44,6 +46,7 @@ Layer Scopes Sliders [Documentation] Use the slider tools to work with parts. + [Tags] feature:slidestyle Set Attempt Screenshot Directory lab${/}design${/}sliders Start Basic Notebook Deck Maybe Open Design Tools diff --git a/atest/suites/lab/05-slide-layout.robot b/atest/suites/lab/05-slide-layout.robot index bc05da5..a1e057f 100644 --- a/atest/suites/lab/05-slide-layout.robot +++ b/atest/suites/lab/05-slide-layout.robot @@ -20,6 +20,7 @@ Force Tags suite:design *** Test Cases *** Slide Layout [Documentation] Use the design tools to work with parts. + [Tags] activity:notebook feature:layover Set Attempt Screenshot Directory lab${/}layout${/}layover Start Basic Notebook Deck Maybe Open Slide Layout @@ -28,6 +29,6 @@ Slide Layout FOR ${i} ${handle} IN ENUMERATE @{PART_HANDLES} Resize A Part 1 ${handle} 10 10 02-${i}-resized.png END - Unstyle A Part 1 03-unstyled.png + Unfix A Part 1 03-unfixed.png Maybe Close Design Tools Capture Page Screenshot 04-presenting.png diff --git a/examples/README.ipynb b/examples/README.ipynb index 7a9b715..229519e 100644 --- a/examples/README.ipynb +++ b/examples/README.ipynb @@ -489,7 +489,20 @@ "tags": [] }, "source": [ - " - images (_no link here, yet, as it's hard to get back!_)..." + " - [images](./deck.svg) ..." + ] + }, + { + "cell_type": "markdown", + "id": "d139b228-9fbe-494d-b238-46c08b0709a0", + "metadata": { + "slideshow": { + "slide_type": "fragment" + }, + "tags": [] + }, + "source": [ + " - [code](./hello_world.py) ..." ] }, { diff --git a/examples/README.md b/examples/README.md index a0a5c90..e4df63d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,8 +10,34 @@ The contents of this folder are copied into the JupyterLite site. If you got here from the main presentation, you can [go back](./README.ipynb#Multiple-Documents). ``` +> This file is **also** a [basic deck](#Simple-Markdown-Decks) that can be viewed with +> the _Start Deck_ command. + +--- + +## Simple Markdown Decks + +- each `---` starts a new slide + - including the first +- not-yet-appearing + - no subslides + - no fragments + - no document styles + - no live editing: see [workaround](#Live-Updating-Workaround) + --- -## Markdown Presentations +## Future Work + +- running code blocks +- styling with front/back matter + +--- + +## Live Updating Workaround + +- doesn't currently support this + - well, sorta: right click and select _Show Markdown Editor_ + - ctrl+shift+d -> coming soon +> this may be improved in [future work](#Future-Work) diff --git a/examples/hello_world.py b/examples/hello_world.py new file mode 100644 index 0000000..a9ded1a --- /dev/null +++ b/examples/hello_world.py @@ -0,0 +1,7 @@ +"""Just an example of a code file + +Click the link in the remote to go back! +""" + +def hello_world(world="Jupyter") -> str: + return f"Hello {world}" diff --git a/js/jupyterlab-deck/package.json b/js/jupyterlab-deck/package.json index 041cfea..fbfb31d 100644 --- a/js/jupyterlab-deck/package.json +++ b/js/jupyterlab-deck/package.json @@ -22,6 +22,7 @@ "dependencies": { "@jupyterlab/application": "3", "@jupyterlab/apputils": "3", + "@jupyterlab/markdownviewer": "3", "@jupyterlab/notebook": "3", "@jupyterlab/statusbar": "3", "@jupyterlab/ui-components": "3", diff --git a/js/jupyterlab-deck/src/manager.ts b/js/jupyterlab-deck/src/manager.ts index a18bd52..85c379c 100644 --- a/js/jupyterlab-deck/src/manager.ts +++ b/js/jupyterlab-deck/src/manager.ts @@ -320,7 +320,7 @@ export class DeckManager implements IDeckManager { public getSlideType(): TSlideType { let { _activeWidget, _activePresenter } = this; - if (_activeWidget && _activePresenter?.capabilities.slideType) { + if (_activeWidget && _activePresenter?.getSlideType) { return _activePresenter.getSlideType(_activeWidget) || null; } /* istanbul ignore next */ @@ -329,14 +329,14 @@ export class DeckManager implements IDeckManager { public setSlideType(slideType: TSlideType): void { let { _activeWidget, _activePresenter } = this; - if (_activeWidget && _activePresenter?.capabilities.slideType) { + if (_activeWidget && _activePresenter?.setSlideType) { _activePresenter.setSlideType(_activeWidget, slideType); } } public getLayerScope(): TLayerScope | null { let { _activeWidget, _activePresenter } = this; - if (_activeWidget && _activePresenter?.capabilities.layerScope) { + if (_activeWidget && _activePresenter?.getLayerScope) { return _activePresenter.getLayerScope(_activeWidget) || null; } /* istanbul ignore next */ @@ -345,14 +345,14 @@ export class DeckManager implements IDeckManager { public setLayerScope(layerScope: TLayerScope | null): void { let { _activeWidget, _activePresenter } = this; - if (_activeWidget && _activePresenter?.capabilities.layerScope) { + if (_activeWidget && _activePresenter?.setLayerScope) { _activePresenter.setLayerScope(_activeWidget, layerScope); } } public getPartStyles(): GlobalStyles | null { let { _activeWidget, _activePresenter } = this; - if (_activeWidget && _activePresenter?.capabilities.stylePart) { + if (_activeWidget && _activePresenter?.getPartStyles) { const styles = _activePresenter.getPartStyles(_activeWidget) || null; return styles; } @@ -361,7 +361,7 @@ export class DeckManager implements IDeckManager { } public setPartStyles(styles: GlobalStyles | null): void { let { _activeWidget, _activePresenter } = this; - if (_activeWidget && _activePresenter?.capabilities.stylePart) { + if (_activeWidget && _activePresenter?.setPartStyles) { _activePresenter.setPartStyles(_activeWidget, styles); } } @@ -517,18 +517,22 @@ export class DeckManager implements IDeckManager { } } - cacheStyle(node: HTMLElement) { - if (!this._styleCache.get(node)) { - this._styleCache.set(node, node.getAttribute('style') || ''); + cacheStyle(...nodes: HTMLElement[]) { + for (const node of nodes) { + if (!this._styleCache.get(node)) { + this._styleCache.set(node, node.getAttribute('style') || ''); + } + node.setAttribute('style', ''); } - node.setAttribute('style', ''); } - uncacheStyle(node: HTMLElement) { - const style = this._styleCache.get(node); - if (style) { - node.setAttribute('style', style); - this._styleCache.delete(node); + uncacheStyle(...nodes: HTMLElement[]) { + for (const node of nodes) { + const style = this._styleCache.get(node); + if (style) { + node.setAttribute('style', style); + this._styleCache.delete(node); + } } } diff --git a/js/jupyterlab-deck/src/markdown/presenter.ts b/js/jupyterlab-deck/src/markdown/presenter.ts new file mode 100644 index 0000000..282752e --- /dev/null +++ b/js/jupyterlab-deck/src/markdown/presenter.ts @@ -0,0 +1,186 @@ +import { MarkdownDocument } from '@jupyterlab/markdownviewer'; +import { CommandRegistry } from '@lumino/commands'; +import { ISignal, Signal } from '@lumino/signaling'; +import { Widget } from '@lumino/widgets'; + +import { + IDeckManager, + IPresenter, + TCanGoDirection, + TDirection, + CSS, + DIRECTION, + CommandIds, + DIRECTION_KEYS, + COMPOUND_KEYS, +} from '../tokens'; + +export class SimpleMarkdownPresenter implements IPresenter { + protected _activeChanged = new Signal, void>(this); + + public readonly id = 'simple-markdown'; + public readonly rank = 100; + public readonly capabilities = {}; + protected _manager: IDeckManager; + protected _previousActiveCellIndex: number = -1; + protected _commands: CommandRegistry; + protected _activeSlide = new Map(); + protected _lastSlide = new Map(); + protected _stylesheets = new Map(); + + constructor(options: SimpleMarkdownPresenter.IOptions) { + this._manager = options.manager; + this._commands = options.commands; + this._addKeyBindings(); + this._addWindowListeners(); + } + + public accepts(widget: Widget): MarkdownDocument | null { + if (widget instanceof MarkdownDocument) { + return widget; + } + return null; + } + + public async stop(panel: MarkdownDocument): Promise { + this._removeStyle(panel); + return; + } + public async start(panel: MarkdownDocument): Promise { + const activeSlide = this._activeSlide.get(panel) || 1; + await panel.content.ready; + this._updateSheet(panel, activeSlide); + return; + } + + public async go( + panel: MarkdownDocument, + direction: TDirection, + alternate?: TDirection + ): Promise { + await panel.content.ready; + let index = this._activeSlide.get(panel) || 1; + let lastSlide = this._lastSlide.get(panel) || -1; + if (direction == 'forward' || alternate == 'forward') { + index++; + } else if (direction == 'back' || alternate == 'back') { + index--; + } + index = index < 1 ? 1 : index > lastSlide ? lastSlide : index; + this._updateSheet(panel, index); + } + + public canGo(panel: MarkdownDocument): Partial { + let index = this._activeSlide.get(panel) || 1; + // TODO: someplace better + let hrCount = panel.content.renderer.node.querySelectorAll('hr').length; + this._lastSlide.set(panel, hrCount); + return { + forward: index < hrCount, + back: index > 1, + }; + } + + public style(panel: MarkdownDocument): void { + const { _manager } = this; + panel.addClass(CSS.deck); + _manager.cacheStyle(panel.node, panel.content.node, panel.content.renderer.node); + } + + public get activeChanged(): ISignal, void> { + return this._activeChanged; + } + + /** overload the stock notebook keyboard shortcuts */ + protected _addKeyBindings() { + for (const direction of Object.values(DIRECTION)) { + this._commands.addKeyBinding({ + command: CommandIds[direction], + keys: DIRECTION_KEYS[direction], + selector: `.${CSS.deck} .${CSS.markdownViewer}`, + }); + } + for (const [directions, keys] of COMPOUND_KEYS.entries()) { + const [direction, alternate] = directions; + this._commands.addKeyBinding({ + command: CommandIds.go, + args: { direction, alternate }, + keys, + selector: `.${CSS.deck} .${CSS.markdownViewer}`, + }); + } + } + + protected _addWindowListeners() { + window.addEventListener('hashchange', this._onHashChange); + } + + protected _onHashChange = (event: HashChangeEvent) => { + const { activeWidget } = this._manager; + const panel = activeWidget && this.accepts(activeWidget); + /* istanbul ignore if */ + if (!panel) { + return; + } + const url = new URL(event.newURL); + const { hash } = url || '#'; + /* istanbul ignore if */ + if (hash === '#') { + return; + } + this._activateByAnchor(panel, hash); + }; + + protected _activateByAnchor(panel: MarkdownDocument, fragment: string) { + const anchored = document.getElementById(fragment.slice(1)); + /* istanbul ignore if */ + if (!anchored || !panel.node.contains(anchored)) { + return; + } + let index = 0; + for (const child of panel.content.renderer.node.children) { + if (child.tagName === 'HR') { + index += 1; + continue; + } + if (child === anchored) { + this._updateSheet(panel, index); + break; + } + } + } + + protected _updateSheet(panel: MarkdownDocument, index: number) { + let sheet = this._stylesheets.get(panel); + if (sheet == null) { + sheet = document.createElement('style'); + sheet.className = CSS.sheet; + this._stylesheets.set(panel, sheet); + document.body.appendChild(sheet); + } + sheet.textContent = ` + #${panel.id} > .${CSS.markdownViewer} .${ + CSS.renderedMarkdown + } > hr:nth-of-type(${index}) ~ :not(hr:nth-of-type(${index + 1}) ~ *):not(hr) { + display: block; + }`; + this._activeSlide.set(panel, index); + } + + protected _removeStyle(panel: MarkdownDocument) { + /* istanbul ignore if */ + if (panel.isDisposed) { + return; + } + const { _manager } = this; + panel.removeClass(CSS.deck); + _manager.uncacheStyle(panel.content.node, panel.node, panel.content.renderer.node); + } +} + +export namespace SimpleMarkdownPresenter { + export interface IOptions { + manager: IDeckManager; + commands: CommandRegistry; + } +} diff --git a/js/jupyterlab-deck/src/notebook/presenter.ts b/js/jupyterlab-deck/src/notebook/presenter.ts index 478d151..2c92129 100644 --- a/js/jupyterlab-deck/src/notebook/presenter.ts +++ b/js/jupyterlab-deck/src/notebook/presenter.ts @@ -72,8 +72,7 @@ export class NotebookPresenter implements IPresenter { public style(panel: NotebookPanel): void { panel.addClass(CSS.deck); - this._manager.cacheStyle(panel.node); - this._manager.cacheStyle(panel.content.node); + this._manager.cacheStyle(panel.node, panel.content.node); } public async stop(panel: NotebookPanel): Promise { @@ -387,8 +386,7 @@ export class NotebookPresenter implements IPresenter { } const { _manager } = this; panel.removeClass(CSS.deck); - _manager.uncacheStyle(panel.content.node); - _manager.uncacheStyle(panel.node); + _manager.uncacheStyle(panel.content.node, panel.node); for (const cell of panel.content.widgets) { cell.removeClass(CSS.layer); cell.removeClass(CSS.onScreen); diff --git a/js/jupyterlab-deck/src/plugin.ts b/js/jupyterlab-deck/src/plugin.ts index eb4e5f1..eee686a 100644 --- a/js/jupyterlab-deck/src/plugin.ts +++ b/js/jupyterlab-deck/src/plugin.ts @@ -12,6 +12,7 @@ import { IStatusBar, StatusBar } from '@jupyterlab/statusbar'; import { ITranslator, nullTranslator } from '@jupyterlab/translation'; import { DeckManager } from './manager'; +import { SimpleMarkdownPresenter } from './markdown/presenter'; import { NotebookDeckExtension } from './notebook/extension'; import { NotebookPresenter } from './notebook/presenter'; import { NS, IDeckManager, CommandIds, CATEGORY, PLUGIN_ID } from './tokens'; @@ -88,4 +89,18 @@ const notebookPlugin: JupyterFrontEndPlugin = { }, }; -export default [plugin, notebookPlugin]; +const simpleMarkdownPlugin: JupyterFrontEndPlugin = { + id: `${NS}:simple-markdown`, + requires: [IDeckManager], + autoStart: true, + activate: (app: JupyterFrontEnd, decks: IDeckManager) => { + const { commands } = app; + const presenter = new SimpleMarkdownPresenter({ + manager: decks, + commands, + }); + decks.addPresenter(presenter); + }, +}; + +export default [plugin, notebookPlugin, simpleMarkdownPlugin]; diff --git a/js/jupyterlab-deck/src/tokens.ts b/js/jupyterlab-deck/src/tokens.ts index 841a7a0..ca668ca 100644 --- a/js/jupyterlab-deck/src/tokens.ts +++ b/js/jupyterlab-deck/src/tokens.ts @@ -26,8 +26,8 @@ export interface IDeckManager { __: (msgid: string, ...args: string[]) => string; go(direction: TDirection, alternate?: TDirection): void; canGo(): Partial; - cacheStyle(node: HTMLElement): void; - uncacheStyle(node: HTMLElement): void; + cacheStyle(...nodes: HTMLElement[]): void; + uncacheStyle(...nodes: HTMLElement[]): void; addPresenter(presenter: IPresenter): void; addStylePreset(preset: IStylePreset): void; stylePresets: IStylePreset[]; @@ -55,10 +55,11 @@ export interface IDeckManager { export const IDeckManager = new Token(PLUGIN_ID); export interface IPresenterCapbilities { - layout: boolean; - slideType: boolean; - layerScope: boolean; - stylePart: boolean; + layout?: boolean; + slideType?: boolean; + layerScope?: boolean; + stylePart?: boolean; + subslides?: boolean; } export const INCAPABLE: IPresenterCapbilities = Object.freeze({ @@ -66,9 +67,19 @@ export const INCAPABLE: IPresenterCapbilities = Object.freeze({ slideType: false, layerScope: false, stylePart: false, + subslides: false, }); -export interface IPresenter { +export interface IPresenterOptional { + setSlideType(widget: T, slideType: TSlideType): void; + getSlideType(widget: T): TSlideType; + setLayerScope(widget: T, layerType: TLayerScope | null): void; + getLayerScope(widget: T): TLayerScope | null; + getPartStyles(widget: T): GlobalStyles | null; + setPartStyles(widget: T, styles: GlobalStyles | null): void; +} + +export interface IPresenter extends Partial> { id: string; rank: number; capabilities: IPresenterCapbilities; @@ -79,13 +90,6 @@ export interface IPresenter { canGo(widget: T): Partial; style(widget: T): void; activeChanged: ISignal, void>; - - setSlideType(widget: T, slideType: TSlideType): void; - getSlideType(widget: T): TSlideType; - setLayerScope(widget: T, layerType: TLayerScope | null): void; - getLayerScope(widget: T): TLayerScope | null; - getPartStyles(widget: T): GlobalStyles | null; - setPartStyles(widget: T, styles: GlobalStyles | null): void; } export namespace DATA { @@ -107,6 +111,8 @@ export namespace CSS { export const accept = 'jp-mod-accept'; export const active = 'jp-mod-active'; export const mainContent = 'jp-main-content-panel'; + export const renderedMarkdown = 'jp-RenderedMarkdown'; + export const markdownViewer = 'jp-MarkdownViewer'; // deck export const deck = 'jp-Deck'; export const presenting = `[data-jp-deck-mode='${DATA.presenting}']`; @@ -142,6 +148,8 @@ export namespace CSS { export const zoom = 'jp-deck-mod-zoom'; export const opacity = 'jp-deck-mod-opacity'; export const zIndex = 'jp-deck-mod-z-index'; + // sheets + export const sheet = 'jp-Deck-Stylesheet'; } export namespace ID { diff --git a/js/jupyterlab-deck/src/tools/remote.tsx b/js/jupyterlab-deck/src/tools/remote.tsx index 99da6c3..94f3244 100644 --- a/js/jupyterlab-deck/src/tools/remote.tsx +++ b/js/jupyterlab-deck/src/tools/remote.tsx @@ -2,6 +2,7 @@ import { VDomRenderer, VDomModel } from '@jupyterlab/apputils'; import { PathExt } from '@jupyterlab/coreutils'; import { LabIcon } from '@jupyterlab/ui-components'; import { JSONExt } from '@lumino/coreutils'; +import type { Widget } from '@lumino/widgets'; import React from 'react'; import { ICONS } from '../icons'; @@ -11,6 +12,7 @@ import { DIRECTION, DIRECTION_LABEL, TCanGoDirection, + IPresenter, } from '../tokens'; export class DeckRemote extends VDomRenderer { @@ -106,6 +108,7 @@ export namespace DeckRemote { export class Model extends VDomModel { private _manager: IDeckManager; private _canGo: Partial = {}; + private _activePresenter: IPresenter | null = null; constructor(options: IOptions) { super(); @@ -128,11 +131,24 @@ export namespace DeckRemote { private _onActiveChanged() { const canGo = this._manager.canGo(); + let emit = false; if (!JSONExt.deepEqual(canGo, this._canGo)) { this._canGo = canGo; - this.stateChanged.emit(void 0); + emit = true; + } + let { activePresenter } = this._manager; + if (activePresenter !== this._activePresenter) { + this._activePresenter = activePresenter; + emit = true; + } + if (emit) { + this.emit(); } } + + private emit = () => { + this.stateChanged.emit(void 0); + }; } export interface IOptions { manager: IDeckManager; diff --git a/js/jupyterlab-deck/style/index.css b/js/jupyterlab-deck/style/index.css index be56ceb..86643c5 100644 --- a/js/jupyterlab-deck/style/index.css +++ b/js/jupyterlab-deck/style/index.css @@ -1,7 +1,12 @@ @import './variables.css'; -@import './notebook.css'; @import './shell.css'; + +/* tools */ @import './remote.css'; @import './decktools.css'; @import './layover.css'; @import './design.css'; + +/* docs */ +@import './markdown.css'; +@import './notebook.css'; diff --git a/js/jupyterlab-deck/style/markdown.css b/js/jupyterlab-deck/style/markdown.css new file mode 100644 index 0000000..08b88e2 --- /dev/null +++ b/js/jupyterlab-deck/style/markdown.css @@ -0,0 +1,26 @@ +.jp-Deck > .jp-MarkdownViewer { + display: flex; + flex-direction: column; + position: fixed; + left: var(--jp-private-sidebar-tab-width); + right: var(--jp-private-sidebar-tab-width); + justify-content: center; + align-content: center; + align-items: stretch; + height: 100vh; + overflow-y: hidden; +} + +.jp-Deck > .jp-MarkdownViewer > .jp-RenderedMarkdown { + flex-shrink: 0; + gap: 0; + overflow-y: auto; + max-height: 100vh; + margin: 0; + padding: 0; + background: transparent; +} + +.jp-Deck > .jp-MarkdownViewer > .jp-RenderedMarkdown > * { + display: none; +} diff --git a/yarn.lock b/yarn.lock index f3674b8..2c0c357 100644 --- a/yarn.lock +++ b/yarn.lock @@ -253,6 +253,7 @@ dependencies: "@jupyterlab/application" "3" "@jupyterlab/apputils" "3" + "@jupyterlab/markdownviewer" "3" "@jupyterlab/notebook" "3" "@jupyterlab/statusbar" "3" "@jupyterlab/ui-components" "3" @@ -456,18 +457,18 @@ "@lumino/signaling" "^1.10.0" "@lumino/widgets" "^1.33.0" -"@jupyterlab/apputils@3", "@jupyterlab/apputils@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/apputils/-/apputils-3.4.8.tgz#09126a7628958ab2409379d3b691a00d144eee90" - integrity sha512-b7vvPQUdkXcrZnfPUXJVYw4vpKl5HmPqFtFTGmatY+F734FSLYkZeIrNxByinI2DJg35I1p1NfSQW/3DVVwpog== - dependencies: - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/observables" "^4.4.8" - "@jupyterlab/services" "^6.4.8" - "@jupyterlab/settingregistry" "^3.4.8" - "@jupyterlab/statedb" "^3.4.8" - "@jupyterlab/translation" "^3.4.8" - "@jupyterlab/ui-components" "^3.4.8" +"@jupyterlab/apputils@3", "@jupyterlab/apputils@^3.4.8", "@jupyterlab/apputils@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/apputils/-/apputils-3.5.0.tgz#9ce715f6d56d3647798dc8d1108c638466459d3f" + integrity sha512-brL1CR0F2ocxt+YSWQGRh9OoJWxlqQb5BxQNJy+qJceCpwkMyZmZyf2gxHc9bu67HkL96Sa46wGIn6WKobARrA== + dependencies: + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/observables" "^4.5.0" + "@jupyterlab/services" "^6.5.0" + "@jupyterlab/settingregistry" "^3.5.0" + "@jupyterlab/statedb" "^3.5.0" + "@jupyterlab/translation" "^3.5.0" + "@jupyterlab/ui-components" "^3.5.0" "@lumino/algorithm" "^1.9.0" "@lumino/commands" "^1.19.0" "@lumino/coreutils" "^1.11.0" @@ -592,17 +593,17 @@ marked "^4.0.17" react "^17.0.1" -"@jupyterlab/codeeditor@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/codeeditor/-/codeeditor-3.4.8.tgz#2684c9a6f40192f5144937951e1852d1b652dd9e" - integrity sha512-1ymmnK6zE40ivJRpVRd3XI/p7WydqTyD/qbgcYK4JHRzDUvfIJz0OQkei4Rq1oOD9DsMdJOIuyyEXgA8FPE4/g== - dependencies: - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/nbformat" "^3.4.8" - "@jupyterlab/observables" "^4.4.8" - "@jupyterlab/shared-models" "^3.4.8" - "@jupyterlab/translation" "^3.4.8" - "@jupyterlab/ui-components" "^3.4.8" +"@jupyterlab/codeeditor@^3.4.8", "@jupyterlab/codeeditor@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/codeeditor/-/codeeditor-3.5.0.tgz#b5345f028196c68077424b4a9f33ca315ec49828" + integrity sha512-imdYuovxyIIQqZdoRnZAr0VQHqiIVPPFwk8hAgDYtfl8VxFOPMTh203Z6y+CLv5V62J03OU7HZutP/f5u1nZ1w== + dependencies: + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/nbformat" "^3.5.0" + "@jupyterlab/observables" "^4.5.0" + "@jupyterlab/shared-models" "^3.5.0" + "@jupyterlab/translation" "^3.5.0" + "@jupyterlab/ui-components" "^3.5.0" "@lumino/coreutils" "^1.11.0" "@lumino/disposable" "^1.10.0" "@lumino/dragdrop" "^1.13.0" @@ -610,19 +611,19 @@ "@lumino/signaling" "^1.10.0" "@lumino/widgets" "^1.33.0" -"@jupyterlab/codemirror@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/codemirror/-/codemirror-3.4.8.tgz#fe72b58d5d01da83da9725f59393326a6bf8ca90" - integrity sha512-HojL7+vaUTBPIgg0gzCJKBbsN8TdVR+DxXfeGII1RoQf5uUCQdBz9/Ksyt96uHCS+EmYRWkLik2dU94LIEr4dw== - dependencies: - "@jupyterlab/apputils" "^3.4.8" - "@jupyterlab/codeeditor" "^3.4.8" - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/nbformat" "^3.4.8" - "@jupyterlab/observables" "^4.4.8" - "@jupyterlab/shared-models" "^3.4.8" - "@jupyterlab/statusbar" "^3.4.8" - "@jupyterlab/translation" "^3.4.8" +"@jupyterlab/codemirror@^3.4.8", "@jupyterlab/codemirror@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/codemirror/-/codemirror-3.5.0.tgz#b16190e99584acfb0b18c44dd1c005366a829062" + integrity sha512-i6rGYLnWsBuL8zkCpPTCMeZc2lHI5pIgtEpO/CEfeigYhZI9NkaLSiF64Jwt8bgurS10O02bxl+3hIgU3mSSQA== + dependencies: + "@jupyterlab/apputils" "^3.5.0" + "@jupyterlab/codeeditor" "^3.5.0" + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/nbformat" "^3.5.0" + "@jupyterlab/observables" "^4.5.0" + "@jupyterlab/shared-models" "^3.5.0" + "@jupyterlab/statusbar" "^3.5.0" + "@jupyterlab/translation" "^3.5.0" "@lumino/algorithm" "^1.9.0" "@lumino/commands" "^1.19.0" "@lumino/coreutils" "^1.11.0" @@ -634,10 +635,10 @@ react "^17.0.1" y-codemirror "^3.0.1" -"@jupyterlab/coreutils@^5.4.8": - version "5.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-5.4.8.tgz#e3a81a8edb51c9a8d40f9baf4149f86c5e5109d0" - integrity sha512-UICv9nBCL+thSSOFlLWGjPm+UTT1ioPq+pOMjgn0E/DPliUMAMKtrAU5viAbRhITGAU55uL2KH9ijMUIc6o3xA== +"@jupyterlab/coreutils@^5.4.8", "@jupyterlab/coreutils@^5.5.0": + version "5.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/coreutils/-/coreutils-5.5.0.tgz#0cbceb74e75a96cc69c8dd14b61f37d8ea940d75" + integrity sha512-mVBuVDUA87hvtS5DfbjfLIE1EFdhAGEU8f19G33QfhD/w2vYDi7vE4ro4arNT47r17MzXW4XfaE4LwatR6uvPw== dependencies: "@lumino/coreutils" "^1.11.0" "@lumino/disposable" "^1.10.0" @@ -668,34 +669,34 @@ "@lumino/widgets" "^1.33.0" react "^17.0.1" -"@jupyterlab/docprovider@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/docprovider/-/docprovider-3.4.8.tgz#d75e8ccfc9d3f08cf13c6b9b97cd987bdbdcd6d2" - integrity sha512-srOLhPzP+pfOjpQNtXmKuSyBsQzTxf087+MpUdTMPt177PCGG7eyMcCNcDTkBXo6QzuwQuJIj+uESQjVYmQ7Hw== +"@jupyterlab/docprovider@^3.4.8", "@jupyterlab/docprovider@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/docprovider/-/docprovider-3.5.0.tgz#8593ada08c0e7ec014084e16e918d26aac14c441" + integrity sha512-F5VtIIDUpWEFKc0S/xDs8GIjEZC/xn6SVrdNY0+ixDPyC5VNJo+IU5JmqrcU25DlJ+jMbnKlPdRLYsRtJTDKrw== dependencies: - "@jupyterlab/shared-models" "^3.4.8" + "@jupyterlab/shared-models" "^3.5.0" "@lumino/coreutils" "^1.11.0" lib0 "^0.2.42" y-websocket "^1.3.15" yjs "^13.5.17" -"@jupyterlab/docregistry@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/docregistry/-/docregistry-3.4.8.tgz#d9e4c3d7a69b718ce3e6a57f1c4a76bca7cb95b7" - integrity sha512-4DRy4zMrUIhdlEsStbKSOrUUe27EtJBMhwFqTIiKQdaQp4QHgMaHYTLqX/DomGJd4FPmXtcgNfkTr+nM2Tf3yw== - dependencies: - "@jupyterlab/apputils" "^3.4.8" - "@jupyterlab/codeeditor" "^3.4.8" - "@jupyterlab/codemirror" "^3.4.8" - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/docprovider" "^3.4.8" - "@jupyterlab/observables" "^4.4.8" - "@jupyterlab/rendermime" "^3.4.8" - "@jupyterlab/rendermime-interfaces" "^3.4.8" - "@jupyterlab/services" "^6.4.8" - "@jupyterlab/shared-models" "^3.4.8" - "@jupyterlab/translation" "^3.4.8" - "@jupyterlab/ui-components" "^3.4.8" +"@jupyterlab/docregistry@^3.4.8", "@jupyterlab/docregistry@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/docregistry/-/docregistry-3.5.0.tgz#aa3ebc2cd676f7ff564dd055fdd629f0dd16b5b1" + integrity sha512-OdP+q4rvZARqJvZWCyae23K8IHN+TvSP0xPyTVHd1aXFXi6cWlNUOUGRHd9TlEUNqyJxKjkZNuhozMu8ANEBAQ== + dependencies: + "@jupyterlab/apputils" "^3.5.0" + "@jupyterlab/codeeditor" "^3.5.0" + "@jupyterlab/codemirror" "^3.5.0" + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/docprovider" "^3.5.0" + "@jupyterlab/observables" "^4.5.0" + "@jupyterlab/rendermime" "^3.5.0" + "@jupyterlab/rendermime-interfaces" "^3.5.0" + "@jupyterlab/services" "^6.5.0" + "@jupyterlab/shared-models" "^3.5.0" + "@jupyterlab/translation" "^3.5.0" + "@jupyterlab/ui-components" "^3.5.0" "@lumino/algorithm" "^1.9.0" "@lumino/coreutils" "^1.11.0" "@lumino/disposable" "^1.10.0" @@ -744,10 +745,24 @@ "@lumino/coreutils" "^1.11.0" "@lumino/widgets" "^1.33.0" -"@jupyterlab/nbformat@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.4.8.tgz#8552c9d32e8f04bd3e9be468be57662f0c5307c2" - integrity sha512-RcyITAagwXMIWqehpctb43mVB1H3LrTfikGvykLICmA5AfT+byhooCDN4d+ipg4rkeioUmEgX+2uTfForCsJWQ== +"@jupyterlab/markdownviewer@3": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/markdownviewer/-/markdownviewer-3.5.0.tgz#f7255d317c485ffd86d355d98f50579fd285d434" + integrity sha512-qYV7l4KgSJhzSxKajgXOUevsLuMmnC6MngkCfXfm6pcbnnaeGD6yYAngbmVDWIJpIcHPhGXaMapFSjhjWvS45Q== + dependencies: + "@jupyterlab/apputils" "^3.5.0" + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/docregistry" "^3.5.0" + "@jupyterlab/rendermime" "^3.5.0" + "@jupyterlab/translation" "^3.5.0" + "@lumino/coreutils" "^1.11.0" + "@lumino/messaging" "^1.10.0" + "@lumino/widgets" "^1.33.0" + +"@jupyterlab/nbformat@^3.4.8", "@jupyterlab/nbformat@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/nbformat/-/nbformat-3.5.0.tgz#ea3d926b90db9ff2da988db1ea3c8ac1dc3ba9fa" + integrity sha512-tQ0MCJ2NSlGTYM7auiL2vdqirIv39Pd2/gfFd4XdHClJgvT65b7XkNDOwBv6mqIuhNdHo3Mc3RXiODTo1tle7Q== dependencies: "@lumino/coreutils" "^1.11.0" @@ -781,10 +796,10 @@ "@lumino/widgets" "^1.33.0" react "^17.0.1" -"@jupyterlab/observables@^4.4.8": - version "4.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/observables/-/observables-4.4.8.tgz#a73833e4f33b3d7e9c2a59306e8f526e13c043d9" - integrity sha512-TT7YQNxvLnfuzbHQjoovfVN02dXDG/zxfWiA1RkycAJnQ/aTgRtEMlLMs7dUqNCh6ej6zNQOUEduJInro/OL4A== +"@jupyterlab/observables@^4.4.8", "@jupyterlab/observables@^4.5.0": + version "4.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/observables/-/observables-4.5.0.tgz#3cba31bf2358c11f3c7ba39b7aa840e727733405" + integrity sha512-YiUljeHNz80YpIPDi0zoUC26AwAhyDu1UXm2kH5J/lPViycz8X22RWXkIBc40kvWoasUTSomZiEv/W2hFUs0Vw== dependencies: "@lumino/algorithm" "^1.9.0" "@lumino/coreutils" "^1.11.0" @@ -812,28 +827,28 @@ "@lumino/widgets" "^1.33.0" resize-observer-polyfill "^1.5.1" -"@jupyterlab/rendermime-interfaces@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/rendermime-interfaces/-/rendermime-interfaces-3.4.8.tgz#2ed70bf936a03523fbd0a8838cded9c23af6211f" - integrity sha512-zaJbCv9fhgwAlNOcmWnz2rSmoRPR3QLUvhEQ7e08k0jrG6O3Zf19SObEOcp1TM0qwPNSEFT71AbAa81ari13Jg== +"@jupyterlab/rendermime-interfaces@^3.4.8", "@jupyterlab/rendermime-interfaces@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/rendermime-interfaces/-/rendermime-interfaces-3.5.0.tgz#e833b1304ff1e86934fcb039dec7b5ffbf374ae9" + integrity sha512-SWpNX8dwRuAH0GMeuamN1O096Ypn2XcosNbo60P8860qi2KzTXgxADt5xcOf6FK+tXVQ+qi3hJi+055+1xjq+g== dependencies: - "@jupyterlab/translation" "^3.4.8" + "@jupyterlab/translation" "^3.5.0" "@lumino/coreutils" "^1.11.0" "@lumino/widgets" "^1.33.0" -"@jupyterlab/rendermime@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/rendermime/-/rendermime-3.4.8.tgz#9775185c6d71f4159feebeec3d936c00c73d298b" - integrity sha512-9GrfLst9BRJpVAng2mcVCIt2zCIw0+ho8CEMnlq/gwUMUxW8fpstaUnxkLoW7yIvtSnFAYbUZ3GBKenc084nOQ== - dependencies: - "@jupyterlab/apputils" "^3.4.8" - "@jupyterlab/codemirror" "^3.4.8" - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/nbformat" "^3.4.8" - "@jupyterlab/observables" "^4.4.8" - "@jupyterlab/rendermime-interfaces" "^3.4.8" - "@jupyterlab/services" "^6.4.8" - "@jupyterlab/translation" "^3.4.8" +"@jupyterlab/rendermime@^3.4.8", "@jupyterlab/rendermime@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/rendermime/-/rendermime-3.5.0.tgz#279a2672690b63ac23990b366f2dc5cdc786dacc" + integrity sha512-vA5bQA/v7/P/6a3WXdrSoTeGgIJy1iLvpVpJ3DfR9NIpPrXzazDtRplipwcHsNjtUn4P2oS8C46s/eTOEPsQOw== + dependencies: + "@jupyterlab/apputils" "^3.5.0" + "@jupyterlab/codemirror" "^3.5.0" + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/nbformat" "^3.5.0" + "@jupyterlab/observables" "^4.5.0" + "@jupyterlab/rendermime-interfaces" "^3.5.0" + "@jupyterlab/services" "^6.5.0" + "@jupyterlab/translation" "^3.5.0" "@lumino/algorithm" "^1.9.0" "@lumino/coreutils" "^1.11.0" "@lumino/messaging" "^1.10.0" @@ -842,16 +857,16 @@ lodash.escape "^4.0.1" marked "^4.0.17" -"@jupyterlab/services@^6.4.8": - version "6.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/services/-/services-6.4.8.tgz#2da20fd5a5c94ab8f8200da633a252792927318a" - integrity sha512-/acj4d1A1V9KDN+k4CUokOA8e/IxaoJW2B+FJxVnTZvVOBh7093EIG+HYL1SQuQ8CUc2T4DNiq9mG3skiSe2fQ== - dependencies: - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/nbformat" "^3.4.8" - "@jupyterlab/observables" "^4.4.8" - "@jupyterlab/settingregistry" "^3.4.8" - "@jupyterlab/statedb" "^3.4.8" +"@jupyterlab/services@^6.4.8", "@jupyterlab/services@^6.5.0": + version "6.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/services/-/services-6.5.0.tgz#cf89407d8f39ed708394e8ba14b5cba5b65b9cba" + integrity sha512-g5fa7oFu1I6i0agOmx6ud/1fjYAsr3zHzoymE4oAGN3nIbt8HTcmzLbiwmaWssGCVUF4h06GOYWcAe/x/ND8JA== + dependencies: + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/nbformat" "^3.5.0" + "@jupyterlab/observables" "^4.5.0" + "@jupyterlab/settingregistry" "^3.5.0" + "@jupyterlab/statedb" "^3.5.0" "@lumino/algorithm" "^1.9.0" "@lumino/coreutils" "^1.11.0" "@lumino/disposable" "^1.10.0" @@ -860,12 +875,12 @@ node-fetch "^2.6.0" ws "^7.4.6" -"@jupyterlab/settingregistry@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/settingregistry/-/settingregistry-3.4.8.tgz#10f52898b8553639ed4d9d786294617ac599c4a9" - integrity sha512-w9MNFivKXUOLrEvWckpcYm3XAZr0sbcKQ33SkftaLSQODsFlUwkcsjCPJJATVyxiWXAsCAgUlOKdNcqWxYXvOA== +"@jupyterlab/settingregistry@^3.4.8", "@jupyterlab/settingregistry@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/settingregistry/-/settingregistry-3.5.0.tgz#2a6a0c2771c61643ab4aff9355ac863b4c8fc0ab" + integrity sha512-y9H9U4iMVfe2btImp5DR9mGAs36Sow0hI6ajK61bhHVJ4CumYdFBd8drrQGuYcyk/Y4ypU5HO9EaLBQU3CLCug== dependencies: - "@jupyterlab/statedb" "^3.4.8" + "@jupyterlab/statedb" "^3.5.0" "@lumino/commands" "^1.19.0" "@lumino/coreutils" "^1.11.0" "@lumino/disposable" "^1.10.0" @@ -873,22 +888,22 @@ ajv "^6.12.3" json5 "^2.1.1" -"@jupyterlab/shared-models@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/shared-models/-/shared-models-3.4.8.tgz#88156a1c584e50f03cb378ea56f42e8800ba8c3d" - integrity sha512-sUBGyYTAYDOG6S5537ZLTTlqR/ut6XU1sLz/khjQOjROhyAC5kH7Vs7oUoGoGfSCLIwCAlCX2NZHXcvhElNbIg== +"@jupyterlab/shared-models@^3.4.8", "@jupyterlab/shared-models@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/shared-models/-/shared-models-3.5.0.tgz#d34b5ac6d0121ea8daf3862bb92926959aade209" + integrity sha512-QZL9BPCC+iV12AsUbUAwQvZeeo3fKh1X8h9odtlc+Oc+dyZAqREYXuZGjVlaG9qwbF62xDr7acfO4HqCK6Kjyw== dependencies: - "@jupyterlab/nbformat" "^3.4.8" + "@jupyterlab/nbformat" "^3.5.0" "@lumino/coreutils" "^1.11.0" "@lumino/disposable" "^1.10.0" "@lumino/signaling" "^1.10.0" y-protocols "^1.0.5" yjs "^13.5.17" -"@jupyterlab/statedb@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/statedb/-/statedb-3.4.8.tgz#78654b5563f97e8f63683c54403bdc391f4c8df3" - integrity sha512-PMlo+x4R8uXPH1BgCJUVVIj/H8SY9scGJU0pqHhYa6mm3R2EHNAwr8JxyqGjAqT3C0VCCCIDzHtQ3f9inW+OXg== +"@jupyterlab/statedb@^3.4.8", "@jupyterlab/statedb@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/statedb/-/statedb-3.5.0.tgz#6118a7cf339a3d5f033b7b61c3480bfd9bb97a48" + integrity sha512-S4/BjcfSN8tGMyL4jjrD4TMoLTABI3zkLjaqSNRfT6iyKnqN8VKcMHBZXOq90uWGkw+caQZ5GiL+L7uEtahE4w== dependencies: "@lumino/commands" "^1.19.0" "@lumino/coreutils" "^1.11.0" @@ -896,16 +911,16 @@ "@lumino/properties" "^1.8.0" "@lumino/signaling" "^1.10.0" -"@jupyterlab/statusbar@3", "@jupyterlab/statusbar@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/statusbar/-/statusbar-3.4.8.tgz#ac99f5304cf54ec06b7fb02badde8f22201f713e" - integrity sha512-WdeiqMmUEfxfOSMyOTRHQwPKSpcE9ucmq8SL0KN5pY5jcEfw3JTJGSb1tKV3WpI6lqErCQITcu29i1jiP/p+Vg== +"@jupyterlab/statusbar@3", "@jupyterlab/statusbar@^3.4.8", "@jupyterlab/statusbar@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/statusbar/-/statusbar-3.5.0.tgz#62155a3938f6aff6081820c54007d7cbae93bf68" + integrity sha512-kXgMN7x5V3bTWP45mahOt2SaNOMXgeohlMsIou9f+OHZeR++6dmMMKyHPnE7QXES4At26FQu3swRI+8+A2klgA== dependencies: - "@jupyterlab/apputils" "^3.4.8" - "@jupyterlab/codeeditor" "^3.4.8" - "@jupyterlab/services" "^6.4.8" - "@jupyterlab/translation" "^3.4.8" - "@jupyterlab/ui-components" "^3.4.8" + "@jupyterlab/apputils" "^3.5.0" + "@jupyterlab/codeeditor" "^3.5.0" + "@jupyterlab/services" "^6.5.0" + "@jupyterlab/translation" "^3.5.0" + "@jupyterlab/ui-components" "^3.5.0" "@lumino/algorithm" "^1.9.0" "@lumino/coreutils" "^1.11.0" "@lumino/disposable" "^1.10.0" @@ -916,25 +931,25 @@ react "^17.0.1" typestyle "^2.0.4" -"@jupyterlab/translation@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/translation/-/translation-3.4.8.tgz#9958bbbcabdcf913e96fcb267b223dccfa4d1151" - integrity sha512-5hIdMcA33qQpa2jR2Ho+bslfrf+feMyZbu37eCV58kHZjG3BsW47PWe5M0PCCe8WEIkTDu9z7BAmIUtbfwaZgA== +"@jupyterlab/translation@^3.4.8", "@jupyterlab/translation@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/translation/-/translation-3.5.0.tgz#4f8cfd7382009365297df112abb51b7a8d531081" + integrity sha512-68Cyc9gVKef/Gr9tx9YisiPEIzXUk+mnM7u9huthq5A0aHh1W0E51CM/m0BwJDBurbY+W7erphy0nSWSEk7vCg== dependencies: - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/services" "^6.4.8" - "@jupyterlab/statedb" "^3.4.8" + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/services" "^6.5.0" + "@jupyterlab/statedb" "^3.5.0" "@lumino/coreutils" "^1.11.0" -"@jupyterlab/ui-components@3", "@jupyterlab/ui-components@^3.4.8": - version "3.4.8" - resolved "https://registry.npmjs.org/@jupyterlab/ui-components/-/ui-components-3.4.8.tgz#1aec1b7c6a07abe8d84424d8b3085d8f5627b360" - integrity sha512-mkbJnllKCHaKEtUAtCwQAHrJjoD13njlcaDM2Ml9x8vF7PQB8bwRfp/ml4d6n1jOEJjd+a8HRrpzD2X1mTneZQ== +"@jupyterlab/ui-components@3", "@jupyterlab/ui-components@^3.4.8", "@jupyterlab/ui-components@^3.5.0": + version "3.5.0" + resolved "https://registry.npmjs.org/@jupyterlab/ui-components/-/ui-components-3.5.0.tgz#329d0d0b3db666c041fa1a647af68400909d09e9" + integrity sha512-1AIKMUhyLgPYh3R3qvEPRhLKkiVwBtPg571If9UxTvDEJqVwtNTayn47sRsWlOKlueLVwebgEHVSkk2ahxgF6Q== dependencies: "@blueprintjs/core" "^3.36.0" "@blueprintjs/select" "^3.15.0" - "@jupyterlab/coreutils" "^5.4.8" - "@jupyterlab/translation" "^3.4.8" + "@jupyterlab/coreutils" "^5.5.0" + "@jupyterlab/translation" "^3.5.0" "@lumino/algorithm" "^1.9.0" "@lumino/commands" "^1.19.0" "@lumino/coreutils" "^1.11.0"