diff --git a/README.md b/README.md index c5009374..53cd9c16 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,29 @@ This should automatically enable the extension. If it is not listed in `jupyter jupyter serverextension enable --py jupyter_offlinenotebook --sys-prefix +Usage +----- + + + +You should see up to five new buttons depending on your configuration and where you are running the notebook: +- download the in-memory (browser) state of the notebook +- save the in-memory state of the notebook to local-storage +- load a notebook from local-storage +- open the permanent URL of the repository containing this notebook +- copy the permanent mybinder URL to share this repository + +Saving and loading uses the repository ID and the path of the current notebook. + +You should always see the `Download` button. +If you are running this on mybinder all buttons should be visible. +See the configuration section below to enable the other buttons on other systems. + +If you don't see the buttons check the Javascript console log. + +See [example.ipynb](./example.ipynb) + + Configuration ------------- @@ -36,23 +59,8 @@ This extension can be configured in `jupyter_notebook_config.py` by setting the A callable that returns the repository reference URL. Default is the values of the `BINDER_LAUNCH_HOST` and `BINDER_PERSISTENT_REQUEST` environment variables. - - - -Usage ------ - - - -There are three new icons to: -- download the in-memory (browser) state of the notebook -- save the in-memory state of the notebook to local-storage -- load a notebook from local-storage - -Saving and loading uses the repository ID and the path of the current notebook. -If you don't see the buttons check the Javascritp console log, it may mean no repository ID was found. - -See [example.ipynb](./example.ipynb) +- `binder_repo_label`: + A callable that returns the label used to link to the repository. **WARNING** diff --git a/example.ipynb b/example.ipynb index 55433e1e..ae7d2a5e 100644 --- a/example.ipynb +++ b/example.ipynb @@ -16,10 +16,10 @@ "source": [ "1. Make some changes to this notebook (or run it to update the output).\n", "2. Do not save the notebook. You can even disconnect from the Jupyter server or your network.\n", - "3. Click the first button (`first aid kit`). This should prompt you to download the notebook.\n", - "4. Click the middle button (`download`). This should save the current notebook into your browser's [local-storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).\n", + "3. Click the first button (`Download`). This should prompt you to download the notebook.\n", + "4. Click the second button (`cloud download`). This should save the current notebook into your browser's [local-storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).\n", "5. Start a new instance of Jupyter, and open the original version of this notebook.\n", - "6. Click the third button (`upload`). This should restore the copy of the notebook from your browser's local-storage." + "6. Click the third button (`cloud upload`). This should restore the copy of the notebook from your browser's local-storage." ] }, { diff --git a/jupyter_offlinenotebook/__init__.py b/jupyter_offlinenotebook/__init__.py index 07fb3368..dbc00109 100644 --- a/jupyter_offlinenotebook/__init__.py +++ b/jupyter_offlinenotebook/__init__.py @@ -34,19 +34,29 @@ async def get(self): to work if the user subsequently goes offline """ config = self.settings['offline_notebook_config'] - repoid = config.repository_id() - binder_ref_url = config.repository_ref_url() - binder_persistent_url = config.binder_persistent_url() jcfg = json.dumps({ - 'repoid': repoid, - 'binder_ref_url': binder_ref_url, - 'binder_persistent_url': binder_persistent_url, + 'repoid': config.repository_id(), + 'binder_repo_label': config.repository_label(), + 'binder_ref_url': config.repository_ref_url(), + 'binder_persistent_url': config.binder_persistent_url(), }) self.log.debug('OfflineNotebook config:%s ', jcfg) self.set_header('Content-Type', 'application/json') self.write(jcfg) +def _repo_label_from_binder_request(): + try: + repotype = os.getenv('BINDER_PERSISTENT_REQUEST', '').split('/')[1] + except IndexError: + return '' + if repotype == 'gh': + return 'GitHub' + if repotype == 'gl': + return 'GitLab' + return repotype.capitalize() + + class OfflineNotebookConfig(Configurable): """ Holds server-side configuration @@ -61,6 +71,15 @@ class OfflineNotebookConfig(Configurable): """ ).tag(config=True) + repository_label = Callable( + default_value=_repo_label_from_binder_request, + help=""" + A callable that returns the repository label. + Default is to parse the `BINDER_PERSISTENT_REQUEST` environment + variable. + """ + ).tag(config=True) + repository_ref_url = Callable( default_value=lambda: os.getenv('BINDER_REF_URL', ''), help=""" diff --git a/jupyter_offlinenotebook/static/main.js b/jupyter_offlinenotebook/static/main.js index 47241340..3ea72922 100644 --- a/jupyter_offlinenotebook/static/main.js +++ b/jupyter_offlinenotebook/static/main.js @@ -5,16 +5,17 @@ define([ 'base/js/dialog', './dexie', 'jquery' - ], - function(Jupyter, events, utils, dialog, dexie, $) { +], + function (Jupyter, events, utils, dialog, dexie, $) { var repoid = null; + var repoLabel = null; var bindeRefUrl = null; var binderPersistentUrl = null var db = null; var dbname = 'jupyter-offlinenotebook'; - var initialise = function() { - $.getJSON(utils.get_body_data('baseUrl') + 'offlinenotebook/config', function(data) { + var initialise = function () { + $.getJSON(utils.get_body_data('baseUrl') + 'offlinenotebook/config', function (data) { repoid = data['repoid']; if (repoid) { console.log('offline-notebook repoid: ' + repoid); @@ -22,6 +23,8 @@ define([ else { console.log('offline-notebook repoid not found, disabled'); } + repoLabel = data['binder_repo_label'] || 'Repo' + console.log('offline-notebook repoLabel: ' + repoLabel); bindeRefUrl = data['binder_ref_url']; console.log('offline-notebook bindeRefUrl: ' + bindeRefUrl); binderPersistentUrl = data['binder_persistent_url'] @@ -30,63 +33,69 @@ define([ }); } - var getDb = function() { + var getDb = function () { if (!db) { db = new dexie(dbname); // Only define indexed fields. pk: primary key - db.version(1).stores({'offlinenotebook': 'pk,repoid,name,type'}); + db.version(1).stores({ 'offlinenotebook': 'pk,repoid,name,type' }); console.log('offline-notebook: Opened IndexedDB'); } return db; } - var addButtons = function() { - var downloadAction = Jupyter.actions.register({ + var addButtons = function () { + Jupyter.actions.register({ 'help': 'Download visible', - 'icon' : 'fa-medkit', + 'icon': 'fa-download', 'handler': downloadNotebookFromBrowser }, 'offline-notebook-download', 'offlinenotebook'); - var saveAction = Jupyter.actions.register({ + Jupyter.actions.register({ 'help': 'Save to browser storage', - 'icon' : 'fa-download', + 'icon': 'fa-cloud-download', 'handler': localstoreSaveNotebook }, 'offline-notebook-save', 'offlinenotebook'); - var loadAction = Jupyter.actions.register({ - 'help': 'Load from browser storage', - 'icon' : 'fa-upload', + Jupyter.actions.register({ + 'help': 'Restore from browser storage', + 'icon': 'fa-cloud-upload', 'handler': localstoreLoadNotebook }, 'offline-notebook-load', 'offlinenotebook'); - var showRepoAction = Jupyter.actions.register({ + var repoIcons = { + 'GitHub': 'fa-github', + 'GitLab': 'fa-gitlab', + 'Git': 'fa-git' + } + Jupyter.actions.register({ 'help': 'Visit Binder repository', - 'icon' : 'fa-external-link', + 'icon': repoIcons[repoLabel] || 'fa-external-link', 'handler': openBinderRepo }, 'offline-notebook-binderrepo', 'offlinenotebook'); - var showBinderAction = Jupyter.actions.register({ + Jupyter.actions.register({ 'help': 'Link to this Binder', - 'icon' : 'fa-external-link', + 'icon': 'fa-link', 'handler': showBinderLink }, 'offline-notebook-binderlink', 'offlinenotebook'); - var buttons = [ - downloadAction - ]; + var buttons = [{ + 'action': 'offlinenotebook:offline-notebook-download', + 'label': 'Download' + }]; if (repoid) { - buttons.push(saveAction); - buttons.push(loadAction); + buttons.push('offlinenotebook:offline-notebook-save'); + buttons.push('offlinenotebook:offline-notebook-load'); } Jupyter.toolbar.add_buttons_group(buttons); var binderButtons = [] if (bindeRefUrl) { binderButtons.push({ - 'action': showRepoAction, - 'label': 'Repo' + 'action': 'offlinenotebook:offline-notebook-binderrepo', + 'label': repoLabel }); } if (binderPersistentUrl) { binderButtons.push({ - 'action': showBinderAction, + 'action': 'offlinenotebook:offline-notebook-binderlink', 'label': 'Binder' }) } @@ -101,7 +110,7 @@ define([ } if (!buttons) { buttons = { - OK: {'class': 'btn-primary'} + OK: { 'class': 'btn-primary' } }; } dialog.modal({ @@ -116,18 +125,18 @@ define([ $('', { 'text': 'repoid: ' }).append( - $('', { - 'text': repoid - }) - )); + $('', { + 'text': repoid + }) + )); var displayPath = $('
').append( $('', { 'text': 'path: ' }).append( - $('', { - 'text': path - }) - )); + $('', { + 'text': path + }) + )); return displayRepoid.append(displayPath); } @@ -148,12 +157,12 @@ define([ 'format': 'json', 'type': 'notebook', 'content': nb - }).then(function(key) { + }).then(function (key) { console.log('offline-notebook saved: ', key); modalDialog( 'Notebook saved to browser storage', repopathDisplay); - }).catch(function(e) { + }).catch(function (e) { var body = repopathDisplay.append( $('', { 'text': e @@ -162,14 +171,14 @@ define([ 'Local storage IndexedDB error', body, 'alert alert-danger'); - throw(e); + throw (e); }); } function localstoreLoadNotebook() { var path = Jupyter.notebook.notebook_path; var primaryKey = 'repoid:' + repoid + ' path:' + path; - getDb().offlinenotebook.get(primaryKey).then(function(nb) { + getDb().offlinenotebook.get(primaryKey).then(function (nb) { var repopathDisplay = formatRepoPathforDialog(repoid, path); if (nb) { console.log('offline-notebook found ' + primaryKey); @@ -196,19 +205,19 @@ define([ repopathDisplay, 'alert alert-danger'); } - }).catch(function(e) { + }).catch(function (e) { var body = $('').append( $('', { 'text': primaryKey })).append( - $('', { - 'text': e - })); + $('', { + 'text': e + })); modalDialog( 'Local storage IndexedDB error', body, 'alert alert-danger'); - throw(e); + throw (e); }); } @@ -216,7 +225,7 @@ define([ function downloadNotebookFromBrowser() { var name = Jupyter.notebook.notebook_name; var nb = getNotebookFromBrowser(); - var blob = new Blob([JSON.stringify(nb)], {type: 'application/json'}); + var blob = new Blob([JSON.stringify(nb)], { type: 'application/json' }); var url = window.URL.createObjectURL(blob); var a = document.createElement('a'); document.body.appendChild(a); @@ -258,11 +267,11 @@ define([ 'style': 'flex-grow: 1; margin: 0;' })); var button = $('', { - 'title': 'Copy binder link to clipboard', - 'data-url': binderUrl - }).click(function() { - copy_link_into_clipboard(this); - }) + 'title': 'Copy binder link to clipboard', + 'data-url': binderUrl + }).click(function () { + copy_link_into_clipboard(this); + }) button.append( $('', { 'class': 'fa fa-clipboard' @@ -281,4 +290,4 @@ define([ return { load_ipython_extension: load_ipython_extension }; -}); \ No newline at end of file + }); diff --git a/offline-notebook-buttons.png b/offline-notebook-buttons.png index 6c59b2e4..3160ba68 100644 Binary files a/offline-notebook-buttons.png and b/offline-notebook-buttons.png differ