diff --git a/nbviewer/app.py b/nbviewer/app.py index 5e8b05a4..f5214fe2 100644 --- a/nbviewer/app.py +++ b/nbviewer/app.py @@ -89,6 +89,8 @@ class NBViewer(Application): { "base-url": "NBViewer.base_url", "binder-base-url": "NBViewer.binder_base_url", + "colab-base-url": "NBViewer.colab_base_url", + "mineo-base-url": "NBViewer.mineo_base_url", "cache-expiry-max": "NBViewer.cache_expiry_max", "cache-expiry-min": "NBViewer.cache_expiry_min", "config-file": "NBViewer.config_file", @@ -221,6 +223,16 @@ class NBViewer(Application): help="URL base for binder notebook execution service.", ).tag(config=True) + colab_base_url = Unicode( + default_value="https://colab.research.google.com", + help="URL base for colab notebook execution service.", + ).tag(config=True) + + mineo_base_url = Unicode( + default_value="https://b.mineo.app", + help="URL base for mineo notebook execution service.", + ).tag(config=True) + cache_expiry_max = Int( default_value=2 * 60 * 60, help="Maximum cache expiry (seconds)." ).tag(config=True) @@ -662,6 +674,8 @@ def init_tornado_application(self): allow_remote_access=True, base_url=self._base_url, binder_base_url=self.binder_base_url, + colab_base_url=self.colab_base_url, + mineo_base_url=self.mineo_base_url, cache=self.cache, cache_expiry_max=self.cache_expiry_max, cache_expiry_min=self.cache_expiry_min, diff --git a/nbviewer/providers/base.py b/nbviewer/providers/base.py index 032fb456..9f047af3 100644 --- a/nbviewer/providers/base.py +++ b/nbviewer/providers/base.py @@ -139,6 +139,14 @@ def base_url(self): def binder_base_url(self): return self.settings["binder_base_url"] + @property + def colab_base_url(self): + return self.settings["colab_base_url"] + + @property + def mineo_base_url(self): + return self.settings["mineo_base_url"] + @property def cache(self): return self.settings["cache"] @@ -664,11 +672,20 @@ def deliver_notebook(self, **kwargs): def render_notebook_template( self, body, nb, download_url, json_notebook, **namespace ): + executor_mineo_url = ( + "{mineo_base_url}/import/{notebook_url}".format( + mineo_base_url=self.mineo_base_url, notebook_url=download_url + ) + if self.mineo_base_url + else None + ) + return self.render_template( "formats/%s.html" % self.format, body=body, nb=nb, download_url=download_url, + executor_mineo_url=executor_mineo_url, format=self.format, default_format=self.default_format, format_prefix=format_prefix, diff --git a/nbviewer/providers/github/handlers.py b/nbviewer/providers/github/handlers.py index cf34ebf3..70057227 100644 --- a/nbviewer/providers/github/handlers.py +++ b/nbviewer/providers/github/handlers.py @@ -41,13 +41,18 @@ class GithubClientMixin: PROVIDER_CTX = { "provider_label": "GitHub", "provider_icon": "github", - "executor_label": "Binder", - "executor_icon": "icon-binder", + "executor_label_binder": "Binder", + "executor_icon_binder": "icon-binder", + "executor_label_colab": "Colab", + "executor_icon_colab": "icon-colab", } BINDER_TMPL = "{binder_base_url}/gh/{org}/{repo}/{ref}" BINDER_PATH_TMPL = BINDER_TMPL + "?filepath={path}" + COLAB_TMPL = "{colab_base_url}/github/{org}/{repo}/blob/{ref}" + COLAB_PATH_TMPL = COLAB_TMPL + "/{path}" + @property def github_url(self): if getattr(self, "_github_url", None) is None: @@ -169,7 +174,8 @@ def render_treelist_template( path, branches, tags, - executor_url, + executor_url_binder, + executor_url_colab, **namespace, ): """ @@ -177,8 +183,10 @@ def render_treelist_template( Breadcrumb 'name' and 'url' to render as links at the top of the notebook page provider_url: str URL to the notebook document upstream at the provider (e.g., GitHub) - executor_url: str, optional - URL to execute the notebook document (e.g., Binder) + executor_url_binder: str, optional + URL to execute the notebook document with Binder + executor_url_colab: str, optional + URL to execute the notebook document withe Colab """ return self.render_template( "treelist.html", @@ -193,7 +201,8 @@ def render_treelist_template( tags=tags, tree_type="github", tree_label="repositories", - executor_url=executor_url, + executor_url_binder=executor_url_binder, + executor_url_colab=executor_url_colab, **self.PROVIDER_CTX, **namespace, ) @@ -308,13 +317,20 @@ async def get(self, user: str, repo: str, ref: str, path: str): entries.extend(others) # Enable a binder navbar icon if a binder base URL is configured - executor_url = ( + executor_url_binder = ( self.BINDER_TMPL.format( binder_base_url=self.binder_base_url, org=user, repo=repo, ref=ref ) if self.binder_base_url else None ) + executor_url_colab = ( + self.COLAB_PATH_TMPL.format( + colab_base_url=self.colab_base_url, org=user, repo=repo, ref=ref + ) + if self.colab_base_url + else None + ) html = self.render_treelist_template( entries=entries, @@ -326,7 +342,8 @@ async def get(self, user: str, repo: str, ref: str, path: str): path=path, branches=branches, tags=tags, - executor_url=executor_url, + executor_url_binder=executor_url_binder, + executor_url_colab=executor_url_colab, ) await self.cache_and_finish(html) @@ -414,7 +431,7 @@ async def deliver_notebook( breadcrumbs.extend(self.breadcrumbs(dir_path, base_url)) # Enable a binder navbar icon if a binder base URL is configured - executor_url = ( + executor_url_binder = ( self.BINDER_PATH_TMPL.format( binder_base_url=self.binder_base_url, org=user, @@ -425,6 +442,17 @@ async def deliver_notebook( if self.binder_base_url else None ) + executor_url_colab = ( + self.COLAB_PATH_TMPL.format( + colab_base_url=self.colab_base_url, + org=user, + repo=repo, + ref=ref, + path=quote(path), + ) + if self.binder_base_url + else None + ) try: # filedata may be bytes, but we need text @@ -447,7 +475,8 @@ async def deliver_notebook( nbjson, raw_url, provider_url=blob_url, - executor_url=executor_url, + executor_url_binder=executor_url_binder, + executor_url_colab=executor_url_colab, breadcrumbs=breadcrumbs, msg="file from GitHub: %s" % raw_url, public=True, diff --git a/nbviewer/static/img/icon-colab-color.png b/nbviewer/static/img/icon-colab-color.png new file mode 100644 index 00000000..0ae49965 Binary files /dev/null and b/nbviewer/static/img/icon-colab-color.png differ diff --git a/nbviewer/static/img/icon-colab.png b/nbviewer/static/img/icon-colab.png new file mode 100644 index 00000000..d5c91403 Binary files /dev/null and b/nbviewer/static/img/icon-colab.png differ diff --git a/nbviewer/static/img/icon-mineo-color.png b/nbviewer/static/img/icon-mineo-color.png new file mode 100644 index 00000000..4ae9acc9 Binary files /dev/null and b/nbviewer/static/img/icon-mineo-color.png differ diff --git a/nbviewer/static/img/icon-mineo.png b/nbviewer/static/img/icon-mineo.png new file mode 100644 index 00000000..2dc8cb97 Binary files /dev/null and b/nbviewer/static/img/icon-mineo.png differ diff --git a/nbviewer/static/less/layout.less b/nbviewer/static/less/layout.less index e07cb084..8885a9f7 100644 --- a/nbviewer/static/less/layout.less +++ b/nbviewer/static/less/layout.less @@ -81,3 +81,33 @@ td.page_links { background-image: url(../img/icon-binder-color.png); } } + +// colab icon +.fa-icon-colab { + width: 24px; + height: 24px; + margin-top: -8px; + background-image: url(../img/icon-colab.png); + background-repeat: no-repeat; + background-position: center right; + background-size: 24px; + &:hover, &.active { + &:extend(.fa-icon-colab); + background-image: url(../img/icon-colab-color.png); + } +} + +// mineo icon +.fa-icon-mineo { + width: 24px; + height: 24px; + margin-top: -8px; + background-image: url(../img/icon-mineo.png); + background-repeat: no-repeat; + background-position: center right; + background-size: 24px; + &:hover, &.active { + &:extend(.fa-icon-mineo); + background-image: url(../img/icon-mineo-color.png); + } +} diff --git a/nbviewer/templates/faq.md b/nbviewer/templates/faq.md index 5d9891f7..ed20b025 100644 --- a/nbviewer/templates/faq.md +++ b/nbviewer/templates/faq.md @@ -53,7 +53,16 @@ saved in a notebook document as a web page. [mybinder.org](https://mybinder.org/) is a separate web service that lets you open notebooks in an executable environment, making your code immediately reproducible by anyone, anywhere. nbviewer shows an *Execute on -Binder* icon in its navbar which +Binder* icon in its navbar + +[Google Colab](https://colab.research.google.com/) offers a cloud environment +for editing, running, and sharing notebooks. nbviewer includes an +*Execute on Colab* icon in its navbar. + +[mneo.app](https://mineo.app/) is another cloud platform for running and sharing +notebooks with Code, No-code & AI capabilities. +nbviewer features an *Execute on Mineo* icon in its navbar. + ## Why does the Execute on Binder button not appear for a notebook? diff --git a/nbviewer/templates/notebook.html b/nbviewer/templates/notebook.html index f7c03663..d6ff5cd2 100644 --- a/nbviewer/templates/notebook.html +++ b/nbviewer/templates/notebook.html @@ -22,10 +22,16 @@ {{ layout.head_icon(provider_url, "View on " + provider_label, provider_icon) }} {% endif %} - {% if executor_url %} - {{ layout.head_icon(executor_url, "Execute on " + executor_label, executor_icon) }} + {% if executor_url_binder %} + {{ layout.head_icon(executor_url_binder, "Execute on " + executor_label_binder, executor_icon_binder) }} {% endif %} + {% if executor_url_colab %} + {{ layout.head_icon(executor_url_colab, "Execute on " + executor_label_colab, executor_icon_colab) }} + {% endif %} + + {{ head_icon(executor_mineo_url, "Execute on MINEO", "icon-mineo") }} + {{ layout.head_icon(download_url, "Download Notebook", "download", True) }} {% endblock %} diff --git a/nbviewer/tests/test_app.py b/nbviewer/tests/test_app.py index 92ba8195..6fd1eab6 100644 --- a/nbviewer/tests/test_app.py +++ b/nbviewer/tests/test_app.py @@ -51,6 +51,8 @@ def test_generate_config(): assert "NBViewer.base_url" in cfg_text assert "NBViewer._base_url" not in cfg_text # This shouldn't be configurable assert "NBViewer.binder_base_url" in cfg_text + assert "NBViewer.colab_base_url" in cfg_text + assert "NBViewer.mineo_base_url" in cfg_text assert "NBViewer.cache_expiry_max" in cfg_text assert "NBViewer.cache_expiry_min" in cfg_text assert "NBViewer.client" in cfg_text