├── .babelrc ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── logo.ai ├── logo.png ├── package.json ├── screenshot-button.png ├── screenshot-popover.png ├── src ├── background.js ├── generator.js ├── icon_32.png ├── icon_32_disabled.png ├── index.jsx ├── manifest-chrome.json ├── manifest-firefox.json ├── popup.html ├── popup_disabled.html └── prefs.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | dist-chrome 85 | dist-firefox 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | .DS_Store 109 | 110 | package-lock.json 111 | web-ext-artifacts* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Yuvi Panda 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nbgitpuller Link Generator WebExtension 2 | 3 | A browser extension to help you generate [nbgitpuller](https://jupyterhub.github.io/nbgitpuller/) 4 | links directly from GitHub. 5 | 6 | [Install Firefox Extension](https://addons.mozilla.org/en-US/firefox/addon/nbgitpuller-link-generator/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search) 7 | 8 | ## What does this do? 9 | 10 | It adds an icon to the browser toolbar which will be enabled whenever you are on GitHub. 11 | 12 | On clicking the button, a form pops up. You can enter a JupyterHub URL and the application 13 | you want to use to open this file, folder or repository. Then you 14 | can just copy the nbgitpuller URL, and share it with your students! 15 | 16 | ![nbgitpuller popover](screenshot-popover.png) 17 | 18 | The JupyterHub URL and application you choose are remembered, so 19 | you do not need to enter it over and over again. 20 | 21 | ## Installation 22 | 23 | ### On Firefox 24 | 25 | The preferred, easy method to install and keep the extension up to date is to install it 26 | from the [addons.mozilla.org](https://addons.mozilla.org/en-US/firefox/addon/nbgitpuller-link-generator/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search). 27 | 28 | ### On Chrome 29 | 30 | The preferred, easy method to install and keep the extension up to date is to install it 31 | from the [the Chrome Web Store](https://chrome.google.com/webstore/detail/nbgitpuller-link-generato/hpdbdpklpmppnoibabdkkhnfhkkehgnc). 32 | 33 | ## Manual Installation 34 | 35 | ### On Firefox 36 | 37 | 1. Download the `.zip` version of the latest [release](https://github.com/yuvipanda/nbgitpuller-link-generator/releases) 38 | of the extension. You want the file named `nbgitpuller_link_generator-.zip`. 39 | 2. In your firefox, go to the [about:debugging](about:debugging#/runtime/this-firefox) page, 40 | and select the 'This Firefox' tab on the left. 41 | 3. Under *Temporary Extensions*, click *Load Temporary Add-on...*. 42 | 4. Navigate to the `.zip` file you downloaded in step 1, and select that 43 | 5. This should enable the addon! If you go to any repo on GitHub, you should see 44 | the nbgitpuller button now! 45 | 46 | You can find more information [here](https://developer.mozilla.org/en-US/docs/Tools/about:debugging#extensions). 47 | 48 | ### On Google Chrome / Chromium 49 | 50 | 1. Download the `.zip` version of the latest [release](https://github.com/yuvipanda/nbgitpuller-link-generator/releases) 51 | of the extension. You want the file named `nbgitpuller_link_generator-.zip`. 52 | 2. Extract the `.zip` file you downloaded. 53 | 3. In your Google Chrome / Chromium, go to [chrome://extensions](chrome://extensions/). 54 | 4. Enable the *Developer Mode* toggle in the top right. This should make a few options visible 55 | in a new toolbar. 56 | 5. Select *Load Unpacked*, and select the directory into which the downloaded `.zip` file 57 | was extracted to. This directory should contain at least a `manifest.json` file that 58 | was part of the `.zip` file. 59 | 60 | You can find more information [here](https://developer.chrome.com/docs/extensions/mv3/getstarted/#manifest). 61 | 62 | ## Building the webextension 63 | 64 | ```bash 65 | npm install 66 | npx webpack --mode=production 67 | ``` 68 | 69 | Then build for either browser: (They have slightly different manifest files) 70 | 71 | ``` 72 | npx web-ext build -s dist-firefox/ -a web-ext-artifacts-firefox --overwrite-dest 73 | npx web-ext build -s dist-chrome/ -a web-ext-artifacts-chrome --overwrite-dest 74 | ``` 75 | 76 | This produces a `.zip` file in each `web-ext-artifacts*` folder that you can use. -------------------------------------------------------------------------------- /logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuvipanda/nbgitpuller-link-generator-webextension/9e13485891c1bc07bf56d995df1baee719ad49a3/logo.ai -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuvipanda/nbgitpuller-link-generator-webextension/9e13485891c1bc07bf56d995df1baee719ad49a3/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbgitpuller-link-generator", 3 | "version": "3.0.0", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/yuvipanda/nbgitpuller-link-generator.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/yuvipanda/nbgitpuller-link-generator/issues" 18 | }, 19 | "homepage": "https://github.com/yuvipanda/nbgitpuller-link-generator#readme", 20 | "devDependencies": { 21 | "@babel/core": "^7.16.0", 22 | "@babel/preset-env": "^7.16.0", 23 | "@babel/preset-react": "^7.16.0", 24 | "babel-loader": "^8.2.3", 25 | "copy-webpack-plugin": "^9.0.1", 26 | "web-ext": "^6.5.0", 27 | "webpack": "^5.62.1", 28 | "webpack-cli": "^4.9.1" 29 | }, 30 | "dependencies": { 31 | "@primer/components": "^31.1.0", 32 | "@primer/octicons": "^16.1.1", 33 | "@primer/octicons-react": "^16.1.1", 34 | "clipboard": "^2.0.8", 35 | "git-url-parse": "^11.6.0", 36 | "react": "^17.0.2", 37 | "react-dom": "^17.0.2", 38 | "styled-components": "^5.3.3" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /screenshot-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuvipanda/nbgitpuller-link-generator-webextension/9e13485891c1bc07bf56d995df1baee719ad49a3/screenshot-button.png -------------------------------------------------------------------------------- /screenshot-popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuvipanda/nbgitpuller-link-generator-webextension/9e13485891c1bc07bf56d995df1baee719ad49a3/screenshot-popover.png -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { 2 | if (tab.url.startsWith("https://github.com/")) { 3 | chrome.action.setIcon({ tabId, path: "icon_32.png" }); 4 | chrome.action.setPopup({ tabId, popup: "popup.html" }); 5 | } else { 6 | chrome.action.setIcon({ tabId, path: "icon_32_disabled.png" }); 7 | chrome.action.setPopup({ tabId, popup: "popup_disabled.html" }); 8 | }; 9 | }); 10 | -------------------------------------------------------------------------------- /src/generator.js: -------------------------------------------------------------------------------- 1 | function generateRegularUrl(hubUrl, repoUrl, branch, app, filepath) { 2 | 3 | let url = new URL(hubUrl); 4 | 5 | url.searchParams.set('repo', repoUrl); 6 | 7 | 8 | if (branch) { 9 | url.searchParams.set('branch', branch); 10 | } 11 | 12 | if (!url.pathname.endsWith('/')) { 13 | url.pathname += '/' 14 | } 15 | url.pathname += 'hub/user-redirect/git-pull'; 16 | 17 | url.searchParams.set('urlpath', AVAILABLE_APPS[app].generateUrlPath(filepath)); 18 | 19 | return url.toString(); 20 | } 21 | 22 | const AVAILABLE_APPS = { 23 | classic: { 24 | title: 'Classic Notebook', 25 | generateUrlPath: function (path) { return 'tree/' + path; }, 26 | }, 27 | retrolab: { 28 | title: 'RetroLab', 29 | generateUrlPath: function (path) { return 'retro/tree/' + path; }, 30 | }, 31 | jupyterlab: { 32 | title: 'JupyterLab', 33 | generateUrlPath: function (path) { return 'lab/tree/' + path; } 34 | }, 35 | shiny: { 36 | title: 'Shiny', 37 | generateUrlPath: function (path) { 38 | // jupyter-shiny-proxy requires everything to end with a trailing slash 39 | if (!path.endsWith("/")) { 40 | path = path + "/"; 41 | } 42 | return 'shiny/' + path; 43 | } 44 | }, 45 | rstudio: { 46 | title: 'RStudio', 47 | generateUrlPath: function (path) { return 'rstudio/'; } 48 | } 49 | } 50 | 51 | export {AVAILABLE_APPS, generateRegularUrl} -------------------------------------------------------------------------------- /src/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuvipanda/nbgitpuller-link-generator-webextension/9e13485891c1bc07bf56d995df1baee719ad49a3/src/icon_32.png -------------------------------------------------------------------------------- /src/icon_32_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuvipanda/nbgitpuller-link-generator-webextension/9e13485891c1bc07bf56d995df1baee719ad49a3/src/icon_32_disabled.png -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | // GitHub uses Primer (https://primer.style/css/) as its design system 2 | 3 | import ReactDOM from 'react-dom'; 4 | import React, { useEffect } from 'react'; 5 | import GitUrlParse from 'git-url-parse'; 6 | 7 | import { useState } from 'react'; 8 | 9 | import { Button, Box, Text, Popover, Heading, ThemeProvider, TextInput } from '@primer/components'; 10 | import { CopyIcon } from '@primer/octicons-react'; 11 | 12 | import { AVAILABLE_APPS, generateRegularUrl } from './generator'; 13 | import { getPref, setPref } from './prefs'; 14 | 15 | function copyGeneratedUrl(hubUrl, app) { 16 | const query = { active: true, currentWindow: true }; 17 | chrome.tabs.query(query, function (tabs) { 18 | const activeTab = tabs[0]; 19 | if (activeTab) { 20 | const parts = GitUrlParse(activeTab.url); 21 | const repoUrl = `${parts.protocol}://${parts.source}/${parts.full_name}`; 22 | const url = generateRegularUrl(hubUrl, repoUrl, parts.ref, app, parts.name + '/' + parts.filepath); 23 | navigator.clipboard.writeText(url); 24 | } 25 | }); 26 | } 27 | 28 | function Form() { 29 | const [hubUrl, setHubUrl] = useState(getPref('hub-url', '')); 30 | const [app, setApp] = useState(getPref('app', 'classic')); 31 | const [isValidHubUrl, setIsValidHubUrl] = useState(false); 32 | const [finishedCopying, setFinishedCopying] = useState(false); 33 | 34 | useEffect(() => { 35 | try { 36 | new URL(hubUrl); 37 | // hubUrl is a valid URL 38 | setIsValidHubUrl(true); 39 | } catch (_) { 40 | setIsValidHubUrl(false); 41 | } 42 | }, [hubUrl]); 43 | 44 | useEffect(() => { 45 | setPref('hub-url', hubUrl); 46 | }, [hubUrl]); 47 | 48 | useEffect(() => { 49 | setPref('app', app); 50 | }, [app]) 51 | 52 | return 53 | JupyterHub URL 54 | 55 | setHubUrl(ev.target.value)} placeholder="https://myjupyterhub.org" aria-label="JupyterHub URL" /> 56 | Enter a valid URL 57 | 58 | Open in 59 | 64 | 65 | 73 | 74 | } 75 | 76 | 77 | function setup() { 78 | const root = document.getElementById("root"); 79 | ReactDOM.render( 80 | 81 |
82 | , 83 | root 84 | ); 85 | } 86 | 87 | setup(); 88 | -------------------------------------------------------------------------------- /src/manifest-chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "nbgitpuller Link generator", 4 | "version": "3.0", 5 | "description": "A browser extension to generate nbgitpuller (http://jupyterhub.github.io/nbgitpullerbgitpuller) links directly from GitHub", 6 | "permissions": [ 7 | "tabs" 8 | ], 9 | "background": { 10 | "service_worker": "background.js" 11 | }, 12 | "action": { 13 | "default_icon": { 14 | "32": "icon_32.png" 15 | }, 16 | "default_title": "Generate nbgitpuller link", 17 | "default_popup": "popup.html" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/manifest-firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "nbgitpuller Link generator", 4 | "version": "3.0", 5 | "description": "A browser extension to generate nbgitpuller (http://jupyterhub.github.io/nbgitpullerbgitpuller) links directly from GitHub", 6 | "permissions": [ 7 | "tabs" 8 | ], 9 | "browser_specific_settings": { 10 | "gecko": { 11 | "id": "{2dc254a2-7e69-42c5-8749-45f9a1501cb8}" 12 | } 13 | }, 14 | "background": { 15 | "scripts": ["background.js"] 16 | }, 17 | "action": { 18 | "default_icon": { 19 | "32": "icon_32.png" 20 | }, 21 | "default_title": "Generate nbgitpuller link", 22 | "default_popup": "popup.html" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | NBGitPuller 4 | 5 | 6 |
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/popup_disabled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | NBGitPuller 4 | 5 | 6 |
7 | This extension is disabled for this page. 8 | Please navigate to a GitHub URL to use this extension. 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/prefs.js: -------------------------------------------------------------------------------- 1 | // World's simplest 'preference management system' 2 | // Only does key namespacing with a prefix, and defaults when pref is not set 3 | function getPref(name, def) { 4 | // Namespace our preference keys 5 | const key = `nbgitpuller-link-generator-${name}`; 6 | const value = localStorage.getItem(key); 7 | return value === null ? def : value; 8 | } 9 | 10 | function setPref(name, value) { 11 | // Namespace our preference keys 12 | const key = `nbgitpuller-link-generator-${name}`; 13 | localStorage.setItem(key, value); 14 | } 15 | 16 | export {setPref, getPref}; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyPlugin = require("copy-webpack-plugin"); 3 | 4 | const dists = ['dist-chrome', 'dist-firefox']; 5 | 6 | module.exports = { 7 | entry: { 8 | 'dist-chrome/bundle': './src/index.jsx', 9 | 'dist-firefox/bundle': './src/index.jsx' 10 | }, 11 | output: { 12 | filename: '[name].js', 13 | path: path.resolve(__dirname, './'), 14 | }, 15 | plugins: [ 16 | new CopyPlugin({ 17 | patterns: [ 18 | {from: "src/*.html", to: "dist-chrome/[name].html"}, 19 | {from: "src/*.html", to: "dist-firefox/[name].html"}, 20 | {from: "src/*.png", to: "dist-chrome/[name].png"}, 21 | {from: "src/*.png", to: "dist-firefox/[name].png"}, 22 | { from: "src/background.js", to: "dist-chrome/" }, 23 | { from: "src/background.js", to: "dist-firefox/" }, 24 | { from: "src/manifest-chrome.json", to: 'dist-chrome/manifest.json'}, 25 | { from: "src/manifest-firefox.json", to: 'dist-firefox/manifest.json'}, 26 | ], 27 | }), 28 | ], 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.jsx$/, 33 | exclude: /(node_modules)/, 34 | use: { 35 | loader: 'babel-loader' 36 | } 37 | } 38 | ] 39 | } 40 | 41 | }; --------------------------------------------------------------------------------