├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── NOTE.md ├── README.md ├── dev-env ├── build.js ├── dev.js ├── manifest │ ├── index.js │ ├── log.js │ ├── plugin.js │ ├── processor │ │ ├── action.js │ │ ├── assets.js │ │ ├── background.js │ │ ├── content.js │ │ ├── csp.js │ │ ├── lib │ │ │ ├── html.js │ │ │ └── script.js │ │ ├── options.js │ │ ├── overrides.js │ │ └── package_json.js │ └── processors.js ├── override │ ├── index.js │ └── template │ │ ├── JsonpMainTemplate.runtime.js │ │ └── log-apply-results.js ├── paths.js ├── util │ └── remove.js └── webpack │ ├── build.js │ ├── config.js │ └── server.js ├── package.json └── src ├── components ├── error.js ├── issue.js └── issues_container.js ├── css └── github_pr.css ├── github_pr.js ├── icons ├── icon128.png ├── icon16.png └── icon32.png ├── manifest.json ├── settings ├── settings.html └── settings.js └── support ├── commit_from_url.js ├── get_issues.js └── json_fetch.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015" 5 | ], 6 | "env": { 7 | "development": { 8 | "presets": [ 9 | "react-hmre" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "commonjs": true 6 | }, 7 | "globals": { 8 | "chrome": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | release/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | src/ copyright (c) 2016 steveklebanoff 4 | Based on webpack-chrome-extension boilerplate which is copyright (c) 2014 Khaled Jouda 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /NOTE.md: -------------------------------------------------------------------------------- 1 | Hot middleware 2 | 3 | 4 | **package.json** 5 | 6 | ``` 7 | "webpack-dev-middleware": "^1.5.1", 8 | "webpack-hot-middleware": "2.6.4", 9 | ``` 10 | 11 | 12 | **dev-env/manifest/plugin.js** 13 | 14 | ``` 15 | item = [ 16 | 'webpack-hot-middleware/client' 17 | 'webpack/hot/only-dev-server', 18 | script 19 | ] 20 | ``` 21 | 22 | 23 | **dev-env/webpack/server.js** 24 | 25 | ``` 26 | var webpackDevMiddleware = require('webpack-dev-middleware') 27 | var webpackHotMiddleware = require('webpack-hot-middleware') 28 | 29 | 30 | var app = express(); 31 | 32 | app.use(webpackDevMiddleware(compiler, webpackDevServerOptions)); 33 | 34 | app.use(webpackHotMiddleware(compiler)); 35 | 36 | app.listen(port, host, function(err) { 37 | if (err) { 38 | console.log(err) 39 | } else { 40 | console.log('Listening at https://' + host + ':' + port); 41 | } 42 | }); 43 | ``` 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PR Deetz 2 | 3 | When viewing a commit, it can be super useful to know what pull request the commit was introduced in. 4 | 5 | This is possible through github for PRs that have been merged. On the commit page, next to the branch names, there is a link to a pull request number. This extension expands on this functionality by: 6 | - Shows pull requests that haven't been merged yet 7 | - Shows actual names of the Pull Requests 8 | - Shows any issues that reference the commit 9 | - Shows PRs in seperate place as tags -- tags can get unwieldy at times if you use them for your deployment process 10 | 11 | ![new way](https://www.evernote.com/shard/s318/sh/6fd2522f-28fd-470e-b7ba-898794734bab/85172cae3102dc657f6045b7d71ea073/deep/0/Screenshot%207/15/16,%2012:27%20PM.jpg) 12 | 13 | # Installation 14 | 15 | Install from the [Chrome Store](https://chrome.google.com/webstore/detail/prdeetz/mjljchhaghlgdbpmnangcifkfdccomjc). 16 | 17 | If you'd like to develop the extension, see instructions below 18 | 19 | ## How to run development environment 20 | 21 | Boilerplate from https://github.com/schovi/webpack-chrome-extension 22 | 23 | First, run `npm install`. 24 | 25 | Now, you should do this before editing any code to see how it works: 26 | 27 | 1. run `npm start` (or `npm run dev`) which will start webpack-dev-server 28 | 2. in Chrome open `chrome://extensions/` 29 | 3. check `Developer mode` 30 | 4. click on `Load unpacked extension` 31 | 5. add REPOSITORY_DIRECTORY/build 32 | 6. Navigate to a commit on github and you should now see issue information appear. 33 | 34 | ## How to build extension 35 | 36 | 1. run `npm run build` 37 | 2. It will compile scripts, styles and other assets into release/build/ 38 | 3. It will make chrome extension into release/build.crx with certificate release/build.pem 39 | 40 | ## Troubleshooting 41 | 42 | 1. Everything looks fine, but scripts from webpack arent loading. 43 | - Probably problem with development ssl certificates. Open any script (i.e. https://localhost:3001/background/index.js) in separate tab and allow chrome to load it anyway. Then reload extension. 44 | -------------------------------------------------------------------------------- /dev-env/build.js: -------------------------------------------------------------------------------- 1 | // Native 2 | import fs from 'fs-extra'; 3 | import { exec } from 'child_process' 4 | 5 | // npm 6 | import clc from 'cli-color'; 7 | 8 | // package 9 | import makeWebpackConfig from './webpack/config'; 10 | import webpackBuild from './webpack/build'; 11 | import Manifest from './manifest' 12 | import * as paths from './paths' 13 | 14 | // Clear release direcotry 15 | fs.removeSync(paths.release) 16 | fs.mkdirSync(paths.release) 17 | 18 | // Create manifest 19 | const manifest = new Manifest({manifest: paths.manifest, build: paths.build}) 20 | manifest.run() 21 | 22 | // Build webpack 23 | const webpackConfig = makeWebpackConfig(manifest) 24 | const building = webpackBuild(webpackConfig) 25 | 26 | building.then(() => { 27 | console.error(clc.green("Building done")) 28 | 29 | // Build extension 30 | // TODO try detect system and Chrome path. Default is OSX :) 31 | const chromeBinaryPath = process.env.CHROME_BIN || '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome' 32 | 33 | console.log(clc.yellow(`Packing extension into '${paths.build}'`)) 34 | exec(`\$('${chromeBinaryPath}' --pack-extension=${paths.build})`, (error, stdout, stderr) => { 35 | console.log(clc.green('Done')); 36 | 37 | if(stdout) 38 | console.log(clc.yellow('stdout: ' + stdout)); 39 | 40 | if(stderr) 41 | console.log(clc.red('stderr: ' + stderr)); 42 | 43 | if(error !== null) 44 | console.log(clc.red('exec error: ' + error)); 45 | }) 46 | }).catch((reason) => { 47 | console.error(clc.red("Building failed")) 48 | console.error(clc.red(reason.stack)) 49 | }) 50 | -------------------------------------------------------------------------------- /dev-env/dev.js: -------------------------------------------------------------------------------- 1 | import makeWebpackConfig from './webpack/config'; 2 | import webpackDevServer from './webpack/server'; 3 | import overrideHotUpdater from './override' 4 | import Manifest from './manifest' 5 | import * as paths from './paths' 6 | 7 | // Override Webpack hot updater 8 | overrideHotUpdater() 9 | 10 | // Create manifest 11 | const manifest = new Manifest({manifest: paths.manifest, build: paths.build}) 12 | manifest.run() 13 | 14 | // Start webpack dev server 15 | const webpackConfig = makeWebpackConfig(manifest) 16 | webpackDevServer(webpackConfig) 17 | -------------------------------------------------------------------------------- /dev-env/manifest/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import path from 'path' 3 | // import chokidar from 'chokidar' 4 | 5 | import processors from './processors' 6 | import * as log from './log' 7 | 8 | export default class Manifest { 9 | constructor({ manifest, build }) { 10 | this.manifestPath = manifest 11 | this.buildPath = build 12 | } 13 | 14 | run() { 15 | this.prepareBuildDir() 16 | this.processManifest() 17 | this.writeManifest() 18 | } 19 | 20 | // Start as plugin in webpack 21 | apply() { 22 | this.run() 23 | // this.watch() 24 | } 25 | 26 | // watch() { 27 | // chokidar.watch(this.path).on('change', this.onChange) 28 | // } 29 | 30 | // onChange = (event, path) => { 31 | // this.processManifest() 32 | // } 33 | 34 | prepareBuildDir() { 35 | // Prepare clear build 36 | fs.removeSync(this.buildPath) 37 | fs.mkdirSync(this.buildPath) 38 | } 39 | 40 | writeManifest() { 41 | const manifestPath = path.join(this.buildPath, "manifest.json"); 42 | log.pending(`Making 'build/manifest.json'`) 43 | fs.writeFileSync(manifestPath, JSON.stringify(this.manifest, null, 2), {encoding: 'utf8'}) 44 | log.done() 45 | } 46 | 47 | loadManifest() { 48 | return JSON.parse(fs.readFileSync(this.manifestPath, 'utf8')) 49 | } 50 | 51 | processManifest() { 52 | this.scripts = [] 53 | this.manifest = this.loadManifest() 54 | 55 | // Iterate over each processor and process manifest with it 56 | processors.forEach((processor) => { 57 | this.applyProcessorResult( 58 | processor(this.manifest, this) 59 | ) 60 | }) 61 | 62 | return true 63 | } 64 | 65 | applyProcessorResult({manifest, scripts} = {}) { 66 | if(manifest) 67 | this.manifest = manifest 68 | 69 | if(scripts) { 70 | // TODO validace na skripty 71 | // const pushScriptName = function(scriptName) { 72 | // const scriptPath = path.join(paths.src, scriptName) 73 | // 74 | // if(!fs.existsSync(scriptPath)) { 75 | // console.warn(clc.red(`Missing script ${scriptPath}`)) 76 | // 77 | // return 78 | // } 79 | // 80 | // if(~scripts.indexOf(scriptName)) 81 | // return 82 | // 83 | // scripts.push(scriptName) 84 | // } 85 | 86 | scripts.forEach((script) => { 87 | this.scripts.push(script) 88 | }) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /dev-env/manifest/log.js: -------------------------------------------------------------------------------- 1 | import clc from 'cli-color'; 2 | 3 | export const pending = function(message) { 4 | console.log(clc.yellow(message)) 5 | } 6 | 7 | export const success = function(message) { 8 | console.log(clc.green(message)) 9 | } 10 | 11 | export const error = function(message) { 12 | console.error(clc.red(message)) 13 | } 14 | 15 | export const done = function() { 16 | success("Done") 17 | } 18 | -------------------------------------------------------------------------------- /dev-env/manifest/plugin.js: -------------------------------------------------------------------------------- 1 | import SingleEntryPlugin from "webpack/lib/SingleEntryPlugin" 2 | import MultiEntryPlugin from "webpack/lib/MultiEntryPlugin" 3 | import * as Remove from '../util/remove' 4 | 5 | export default class ManifestPlugin { 6 | constructor(Manifest) { 7 | this.Manifest = Manifest 8 | this.isDevelopment = process.env.NODE_ENV != "production" 9 | } 10 | 11 | apply(compiler) { 12 | this.Manifest.scripts.forEach((script) => { 13 | // name 14 | const name = Remove.extension(script) 15 | 16 | // item 17 | let item 18 | if(this.isDevelopment) { 19 | item = [ 20 | 'webpack-dev-server/client?https://localhost:3001', 21 | 'webpack/hot/only-dev-server', 22 | script 23 | ] 24 | } else { 25 | item = script 26 | } 27 | 28 | const entryClass = this.itemToPlugin(item, name) 29 | 30 | compiler.apply(entryClass) 31 | }) 32 | } 33 | 34 | itemToPlugin(item, name) { 35 | if(Array.isArray(item)) { 36 | return new MultiEntryPlugin(null, item, name); 37 | } else { 38 | return new SingleEntryPlugin(null, item, name); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/action.js: -------------------------------------------------------------------------------- 1 | import html from './lib/html' 2 | 3 | const process = function({action: {default_popup} = {}, buildPath, scripts}) { 4 | if(!default_popup) return 5 | 6 | scripts.push(html(default_popup, buildPath)) 7 | 8 | return true 9 | } 10 | 11 | export default function(manifest, {buildPath}) { 12 | 13 | const {browser_action, page_action} = manifest 14 | 15 | const scripts = [] 16 | 17 | // Browser action 18 | process({action: browser_action, buildPath, scripts}) 19 | 20 | // Page action 21 | process({action: page_action, buildPath, scripts}) 22 | 23 | return {scripts} 24 | } 25 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/assets.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import path from 'path' 3 | import _ from 'lodash' 4 | 5 | import * as paths from '../../paths' 6 | import * as log from '../log' 7 | import * as Remove from '../../util/remove'; 8 | 9 | const buildAssetsDir = "$assets" 10 | 11 | const processAsset = function(object, key, buildPath) { 12 | const assetPath = object[key] 13 | 14 | log.pending(`Processing asset '${assetPath}'`) 15 | 16 | // Create directory if not exists 17 | const buildAssetsDirPath = path.join(buildPath, buildAssetsDir) 18 | try { 19 | const buildAssetsDirStats = fs.lstatSync(buildAssetsDirPath); 20 | 21 | if(!buildAssetsDirStats.isDirectory()) { 22 | fs.mkdirsSync(buildAssetsDirPath) 23 | } 24 | } catch(ex) { 25 | fs.mkdirsSync(buildAssetsDirPath) 26 | } 27 | 28 | const assetSrcPath = path.join(paths.src, assetPath) 29 | const buildAssetPath = path.join(buildAssetsDir, Remove.path(assetPath)) 30 | const assetDestPath = path.join(buildPath, buildAssetPath) 31 | 32 | fs.copySync(assetSrcPath, assetDestPath) 33 | 34 | object[key] = buildAssetPath.replace(/\\/g,"/") 35 | 36 | log.done(`Done`) 37 | 38 | return true 39 | } 40 | 41 | export default function(manifest, {buildPath}) { 42 | 43 | // Process icons 44 | if (manifest.icons && Object.keys(manifest.icons).length) { 45 | _.forEach(manifest.icons, (iconPath, name) => processAsset(manifest.icons, name, buildPath)) 46 | } 47 | 48 | // TODO can there be more assets? 49 | 50 | return {manifest} 51 | } 52 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/background.js: -------------------------------------------------------------------------------- 1 | import script from './lib/script' 2 | import html from './lib/html' 3 | 4 | export default function(manifest, {buildPath}) { 5 | const {background} = manifest 6 | 7 | // Skip when there is no background property 8 | if(!background) 9 | return 10 | 11 | const scripts = [] 12 | 13 | // Process background scripts 14 | if(background.scripts) { 15 | background.scripts.forEach((scriptPath) => { 16 | script(scriptPath, buildPath) 17 | scripts.push(scriptPath) 18 | }) 19 | } 20 | 21 | // Background page 22 | if(background.page) { 23 | scripts.push(html(background.page, buildPath)) 24 | } 25 | 26 | return {manifest, scripts} 27 | } 28 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/content.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | import script from './lib/script' 4 | 5 | export default function(manifest, {buildPath}) { 6 | const {content_scripts} = manifest 7 | 8 | if(!content_scripts) return 9 | 10 | const scripts = [] 11 | 12 | _.each(content_scripts, (content_script) => { 13 | // TODO content_script can contain css too. 14 | // Maybe we can be strict, throw error and tell user to add css into scripts and leave it on webpack too 15 | _.each(content_script.js, (scriptPath) => { 16 | script(scriptPath, buildPath) 17 | scripts.push(scriptPath) 18 | }) 19 | }) 20 | 21 | return {scripts} 22 | } 23 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/csp.js: -------------------------------------------------------------------------------- 1 | import * as log from '../log' 2 | 3 | ////////// 4 | // CSP. Fix Content security policy to allow eval webpack scripts in development mode 5 | export default function(manifest) { 6 | log.pending("Processing CSP") 7 | 8 | if(process.env.NODE_ENV == 'development') { 9 | 10 | let csp = manifest["content_security_policy"] || "" 11 | 12 | const objectSrc = "object-src 'self'" 13 | 14 | if(~csp.indexOf('object-src')) { 15 | csp = csp.replace('object-src', objectSrc) 16 | } else { 17 | csp = `${objectSrc}; ${csp}` 18 | } 19 | 20 | // TODO add host into some config 21 | const scriptSrc = "script-src 'self' 'unsafe-eval' https://localhost:3001" 22 | 23 | if(~csp.indexOf('script-src')) { 24 | csp = csp.replace('script-src', scriptSrc) 25 | } else { 26 | csp = `${scriptSrc}; ${csp}` 27 | } 28 | 29 | manifest["content_security_policy"] = csp 30 | 31 | log.done("Done") 32 | } else { 33 | log.done("Skipped in production environment") 34 | } 35 | 36 | return {manifest} 37 | } 38 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/lib/html.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import fs from 'fs-extra' 3 | 4 | import * as log from '../../log' 5 | import script from './script' 6 | import * as Remove from '../../../util/remove'; 7 | 8 | const makeLayout = function({script, body}) { 9 | return ( 10 | ` 11 | 12 | 13 | 14 | 15 | 16 | 17 | ${body} 18 | ${script} 19 | 20 | ` 21 | ) 22 | } 23 | 24 | export default function(htmlFilepath, buildPath) { 25 | log.pending(`Making html '${htmlFilepath}'`) 26 | 27 | // Read body content 28 | const htmlContent = fs.readFileSync(path.resolve(path.join('src', htmlFilepath)), {encoding: "utf8"}) 29 | 30 | // Get just path and name ie: 'popup/index' 31 | const bareFilepath = Remove.extension(htmlFilepath) 32 | 33 | const scriptFilepath = `${bareFilepath}.js` 34 | 35 | const webpackScriptUrl = process.env.NODE_ENV == "development" ? path.join("https://localhost:3001", scriptFilepath) : `/${scriptFilepath}` 36 | const webpackScript = ``; 37 | 38 | script(scriptFilepath, buildPath) 39 | 40 | const html = makeLayout({ 41 | body: htmlContent, 42 | script: webpackScript 43 | }) 44 | 45 | const fullHtmlPath = path.join(buildPath, htmlFilepath) 46 | 47 | fs.mkdirsSync(Remove.file(fullHtmlPath)) 48 | 49 | fs.writeFileSync(fullHtmlPath, html) 50 | 51 | log.done() 52 | 53 | return scriptFilepath 54 | } 55 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/lib/script.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import path from 'path' 3 | 4 | import * as log from '../../log' 5 | import * as Remove from '../../../util/remove'; 6 | 7 | const makeInjector = function(scriptName) { 8 | return ( 9 | `// Injector file for '${scriptName}' 10 | var context = this; 11 | 12 | // http://stackoverflow.com/questions/8403108/calling-eval-in-particular-context/25859853#25859853 13 | function evalInContext(js, context) { 14 | return function() { return eval(js); }.call(context); 15 | } 16 | 17 | function reqListener () { 18 | evalInContext(this.responseText, context) 19 | } 20 | 21 | var request = new XMLHttpRequest(); 22 | request.onload = reqListener; 23 | request.open("get", "https://localhost:3001/${scriptName}", true); 24 | request.send();` 25 | ) 26 | } 27 | 28 | export default function(scriptName, buildPath) { 29 | if(process.env.NODE_ENV == 'development') { 30 | log.pending(`Making injector '${scriptName}'`) 31 | 32 | const injectorScript = makeInjector(scriptName); 33 | const injectorFilepath = path.join(buildPath, scriptName); 34 | const injectorPath = Remove.file(injectorFilepath) 35 | 36 | fs.mkdirsSync(injectorPath) 37 | fs.writeFileSync(injectorFilepath, injectorScript, {encoding: 'utf8'}) 38 | 39 | log.done() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/options.js: -------------------------------------------------------------------------------- 1 | import script from './lib/script' 2 | import html from './lib/html' 3 | import path from 'path' 4 | import fs from 'fs' 5 | 6 | export default function(manifest, {buildPath}) { 7 | const {options_ui} = manifest 8 | 9 | // Skip when there is no options_page property 10 | if(!options_ui.page) 11 | return 12 | 13 | const scripts = [] 14 | 15 | let dirname = path.dirname(options_ui.page) 16 | 17 | // Process background scripts 18 | let scriptName = dirname + '/settings.js'; 19 | scripts.push(scriptName) 20 | 21 | // Background page 22 | if(options_ui.page) { 23 | scripts.push(html(options_ui.page, buildPath)) 24 | } 25 | 26 | return {manifest, scripts} 27 | } 28 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/overrides.js: -------------------------------------------------------------------------------- 1 | import html from './lib/html' 2 | 3 | const process = function({page, buildPath, scripts}) { 4 | if(!page) return 5 | 6 | scripts.push(html(page, buildPath)) 7 | 8 | return true 9 | } 10 | 11 | export default function(manifest, {buildPath}) { 12 | 13 | if(!manifest.chrome_url_overrides) 14 | return 15 | 16 | const {bookmarks, history, newtab} = manifest.chrome_url_overrides 17 | 18 | const scripts = [] 19 | 20 | // Bookmarks page 21 | process({page: bookmarks, buildPath, scripts}) 22 | 23 | // History page 24 | process({page: history, buildPath, scripts}) 25 | 26 | // New tab page 27 | process({page: newtab, buildPath, scripts}) 28 | 29 | return {scripts} 30 | } 31 | -------------------------------------------------------------------------------- /dev-env/manifest/processor/package_json.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import _ from 'lodash' 3 | 4 | import * as paths from '../../paths' 5 | 6 | ////////// 7 | // Merge manifest.json with name, description and version from package.json 8 | export default function(manifest) { 9 | const packageConfig = JSON.parse(fs.readFileSync(paths.packageJson, 'utf8')) 10 | 11 | manifest = _.merge({}, manifest, _.pick(packageConfig, 'name', 'description', 'version')); 12 | 13 | return {manifest} 14 | } 15 | -------------------------------------------------------------------------------- /dev-env/manifest/processors.js: -------------------------------------------------------------------------------- 1 | import Csp from './processor/csp' 2 | import PackageJson from './processor/package_json' 3 | import Assets from './processor/assets' 4 | import Action from './processor/action' 5 | import Options from './processor/options' 6 | import Background from './processor/background' 7 | import Content from './processor/content' 8 | import Overrides from './processor/overrides' 9 | 10 | 11 | const processors = [ 12 | // Fix csp for devel 13 | Csp, 14 | // Mege package.json 15 | PackageJson, 16 | // Process assets 17 | Assets, 18 | // Process action (browse, or page) 19 | Action, 20 | // Process background script 21 | Background, 22 | // Process content script 23 | Content, 24 | // Process overrides 25 | Overrides, 26 | // Process options 27 | Options, 28 | ] 29 | 30 | export default processors 31 | -------------------------------------------------------------------------------- /dev-env/override/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import clc from 'cli-color'; 4 | 5 | export default function() { 6 | 7 | // HACK 8 | // Override Webpack HOT code loader with my custom one. 9 | // Hot update is loaded via XMLHttpRequest and evaled in extension 10 | // context instead of including script tag with that hot update 11 | 12 | const originalJsonpMainTemplatePath = require.resolve(path.join(__dirname, '../../node_modules/webpack/lib/JsonpMainTemplate.runtime.js')) 13 | const overridenJsonpMainTemplatePath = require.resolve(path.join(__dirname, './template/JsonpMainTemplate.runtime.js')) 14 | const overridenJsonpMainTemplate = fs.readFileSync(overridenJsonpMainTemplatePath, {encoding: "utf8"}) 15 | 16 | console.log(clc.green("Overriding 'node_modules/webpack/lib/JsonpMainTemplate.runtime.js'")) 17 | 18 | fs.writeFileSync(originalJsonpMainTemplatePath, overridenJsonpMainTemplate) 19 | 20 | const originalLogApplyResultPath = require.resolve(path.join(__dirname, '../../node_modules/webpack/hot/log-apply-result.js')) 21 | const overridenLogApplyResultPath = require.resolve(path.join(__dirname, './template/log-apply-results.js')) 22 | const overridenLogApplyResult = fs.readFileSync(overridenLogApplyResultPath, {encoding: "utf8"}) 23 | 24 | console.log(clc.green("Overriding 'node_modules/webpack/hot/log-apply-result.js'")) 25 | 26 | fs.writeFileSync(originalLogApplyResultPath, overridenLogApplyResult) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /dev-env/override/template/JsonpMainTemplate.runtime.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | /*globals hotAddUpdateChunk parentHotUpdateCallback document XMLHttpRequest $require$ $hotChunkFilename$ $hotMainFilename$ */ 6 | 7 | module.exports = function() { 8 | function webpackHotUpdateCallback(chunkId, moreModules) { // eslint-disable-line no-unused-vars 9 | hotAddUpdateChunk(chunkId, moreModules); 10 | if(parentHotUpdateCallback) parentHotUpdateCallback(chunkId, moreModules); 11 | } 12 | 13 | //////////////////// START 14 | // Schovi's hotDownloadUpdateChunk overwriter for current context 15 | 16 | console.log(">> Using custom overriden hotDownloadUpdateChunk") 17 | 18 | var context = this; 19 | 20 | // http://stackoverflow.com/questions/8403108/calling-eval-in-particular-context/25859853#25859853 21 | function evalInContext(js, context) { 22 | return function() { return eval(js); }.call(context); 23 | } 24 | 25 | function reqListener () { 26 | evalInContext(this.responseText, context) 27 | } 28 | 29 | context.hotDownloadUpdateChunk = function (chunkId) { // eslint-disable-line no-unused-vars 30 | var src = __webpack_require__.p + "" + chunkId + "." + hotCurrentHash + ".hot-update.js"; 31 | var request = new XMLHttpRequest(); 32 | 33 | request.onload = reqListener; 34 | request.open("get", src, true); 35 | request.send(); 36 | } 37 | //////////////////// END 38 | 39 | 40 | function hotDownloadManifest(callback) { // eslint-disable-line no-unused-vars 41 | if(typeof XMLHttpRequest === "undefined") 42 | return callback(new Error("No browser support")); 43 | try { 44 | var request = new XMLHttpRequest(); 45 | var requestPath = $require$.p + $hotMainFilename$; 46 | request.open("GET", requestPath, true); 47 | request.timeout = 10000; 48 | request.send(null); 49 | } catch(err) { 50 | return callback(err); 51 | } 52 | request.onreadystatechange = function() { 53 | if(request.readyState !== 4) return; 54 | if(request.status === 0) { 55 | // timeout 56 | callback(new Error("Manifest request to " + requestPath + " timed out.")); 57 | } else if(request.status === 404) { 58 | // no update available 59 | callback(); 60 | } else if(request.status !== 200 && request.status !== 304) { 61 | // other failure 62 | callback(new Error("Manifest request to " + requestPath + " failed.")); 63 | } else { 64 | // success 65 | try { 66 | var update = JSON.parse(request.responseText); 67 | } catch(e) { 68 | callback(e); 69 | return; 70 | } 71 | callback(null, update); 72 | } 73 | }; 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /dev-env/override/template/log-apply-results.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | module.exports = function(updatedModules, renewedModules) { 6 | var unacceptedModules = updatedModules.filter(function(moduleId) { 7 | return renewedModules && renewedModules.indexOf(moduleId) < 0; 8 | }); 9 | 10 | if(unacceptedModules.length > 0) { 11 | console.warn("[HMR] The following modules couldn't be hot updated: (They would need a full reload!)"); 12 | unacceptedModules.forEach(function(moduleId) { 13 | console.warn("[HMR] - " + moduleId); 14 | }); 15 | 16 | // Schovi's 'module couldn't be hot updated' fixer 17 | // TODO when we are not in background script, wee can only reload page. Should it be auto? 18 | if(chrome && chrome.runtime && chrome.runtime.reload) { 19 | console.warn("[HMR] Processing full extension reload"); 20 | chrome.runtime.reload() 21 | } else { 22 | console.warn("[HMR] Can't proceed full reload. chrome.runtime.reload is not available"); 23 | } 24 | //////////// 25 | } 26 | 27 | if(!renewedModules || renewedModules.length === 0) { 28 | console.log("[HMR] Nothing hot updated."); 29 | } else { 30 | console.log("[HMR] Updated modules:"); 31 | renewedModules.forEach(function(moduleId) { 32 | console.log("[HMR] - " + moduleId); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /dev-env/paths.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | export const root = path.normalize(path.join(__dirname, "..")) 4 | 5 | export const packageJson = path.normalize(path.join(root, "package.json")) 6 | 7 | export const src = path.normalize(path.join(root, "src")) 8 | 9 | export const release = path.normalize(path.join(root, "release")) 10 | 11 | export const build = process.env.NODE_ENV == "development" 12 | ? path.normalize(path.join(root, "build")) 13 | : path.normalize(path.join(release, "build")) 14 | 15 | export const manifest = path.normalize(path.join(src, "manifest.json")) 16 | -------------------------------------------------------------------------------- /dev-env/util/remove.js: -------------------------------------------------------------------------------- 1 | import nodePath from 'path' 2 | 3 | export function extension(filepath) { 4 | return filepath.split(".").slice(0,-1).join(".") 5 | } 6 | 7 | export function path(filepath) { 8 | const split = filepath.split(nodePath.sep) 9 | 10 | return split[split.length - 1] 11 | } 12 | 13 | export function all(filepath) { 14 | return extension(path(filepath)) 15 | } 16 | 17 | export function file(filepath) { 18 | return filepath.split(nodePath.sep).slice(0,-1).join(nodePath.sep) 19 | } 20 | -------------------------------------------------------------------------------- /dev-env/webpack/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | 5 | module.exports = function(webpackConfig) { 6 | return new Promise((resolve, reject) => { 7 | webpack(webpackConfig, function(fatalError, stats) { 8 | var jsonStats = stats.toJson(); 9 | 10 | // We can save jsonStats to be analyzed with 11 | // http://webpack.github.io/analyse or 12 | // https://github.com/robertknight/webpack-bundle-size-analyzer. 13 | // var fs = require('fs'); 14 | // fs.writeFileSync('./bundle-stats.json', JSON.stringify(jsonStats)); 15 | 16 | var buildError = fatalError || jsonStats.errors[0] || jsonStats.warnings[0]; 17 | 18 | if (buildError) { 19 | reject(buildError) 20 | } else { 21 | resolve(jsonStats) 22 | } 23 | }) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /dev-env/webpack/config.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path" 3 | import { execSync } from "child_process"; 4 | import webpack from 'webpack'; 5 | import _ from 'lodash'; 6 | import * as Remove from '../util/remove' 7 | import * as paths from '../paths' 8 | import ManifestPlugin from '../manifest/plugin' 9 | 10 | // NOTE Style preprocessors 11 | // If you want to use any of style preprocessor, add related npm package + loader and uncomment following line 12 | var styleLoaders = { 13 | 'css': '', 14 | // 'less': '!less-loader', 15 | // 'scss|sass': '!sass-loader', 16 | // 'styl': '!stylus-loader' 17 | }; 18 | 19 | function makeStyleLoaders() { 20 | return Object.keys(styleLoaders).map(function(ext) { 21 | // NOTE Enable autoprefixer loader 22 | var prefix = 'css-loader?sourceMap&root=../assets'//!autoprefixer-loader?browsers=last 2 version'; 23 | var extLoaders = prefix + styleLoaders[ext]; 24 | var loader = 'style-loader!' + extLoaders; 25 | 26 | return { 27 | loader: loader, 28 | test: new RegExp('\\.(' + ext + ')$'), 29 | exclude: /node_modules/ 30 | }; 31 | }); 32 | } 33 | 34 | function configGenerator(Manifest) { 35 | 36 | var isDevelopment = process.env.NODE_ENV != "production" 37 | 38 | return { 39 | ///// Lowlevel config 40 | cache: isDevelopment, 41 | debug: isDevelopment, 42 | devtool: isDevelopment ? 'cheap-module-eval-source-map' : '', 43 | context: __dirname, 44 | node: {__dirname: true}, 45 | 46 | ///// App config 47 | 48 | // Entry points in your app 49 | // There we use scripts from your manifest.json 50 | entry: {}, 51 | 52 | // Output 53 | output: (function() { 54 | var output = { 55 | path: paths.build, 56 | filename: '[name].js' 57 | } 58 | 59 | if(isDevelopment) { 60 | output.chunkFilename = '[name]-[chunkhash].js' 61 | output.publicPath = 'https://localhost:3001/' 62 | } 63 | 64 | return output 65 | })(), 66 | 67 | // Plugins 68 | plugins: (function() { 69 | let plugins = [ 70 | new webpack.optimize.OccurenceOrderPlugin(), 71 | new ManifestPlugin(Manifest), 72 | new webpack.DefinePlugin({ 73 | "global.GENTLY": false, 74 | "process.env": { 75 | NODE_ENV: JSON.stringify(isDevelopment ? 'development' : 'production'), 76 | IS_BROWSER: true 77 | } 78 | }) 79 | ]; 80 | 81 | if(isDevelopment) { 82 | // Development plugins for hot reload 83 | plugins = plugins.concat([ 84 | // NotifyPlugin, 85 | new webpack.HotModuleReplacementPlugin(), 86 | // Tell reloader to not reload if there is an error. 87 | new webpack.NoErrorsPlugin() 88 | ]) 89 | } else { 90 | // Production plugins for optimizing code 91 | plugins = plugins.concat([ 92 | new webpack.optimize.UglifyJsPlugin({ 93 | compress: { 94 | // Because uglify reports so many irrelevant warnings. 95 | warnings: false 96 | } 97 | }), 98 | new webpack.optimize.DedupePlugin(), 99 | // new webpack.optimize.LimitChunkCountPlugin({maxChunks: 15}), 100 | // new webpack.optimize.MinChunkSizePlugin({minChunkSize: 10000}), 101 | function() { 102 | this.plugin("done", function(stats) { 103 | if (stats.compilation.errors && stats.compilation.errors.length) { 104 | console.log(stats.compilation.errors) 105 | process.exit(1) 106 | } 107 | }) 108 | } 109 | ]) 110 | } 111 | 112 | // NOTE Custom plugins 113 | // if you need to exclude anything pro loading 114 | // plugins.push(new webpack.IgnorePlugin(/^(vertx|somethingelse)$/)) 115 | 116 | return plugins; 117 | })(), 118 | 119 | // NOTE Override external requires 120 | // If you need to change value of required (imported) module 121 | // for example if you dont want any module import 'net' for various reason like code only for non browser envirinment 122 | externals: { 123 | // net: function() {} 124 | }, 125 | 126 | resolve: { 127 | extensions: [ 128 | '', 129 | '.js', 130 | '.jsx', 131 | '.json' 132 | ], 133 | 134 | // NOTE where webpack resolve modules 135 | modulesDirectories: [ 136 | 'src', 137 | 'node_modules' 138 | ], 139 | 140 | root: [ 141 | path.join(__dirname, "../src") 142 | ], 143 | 144 | // NOTE Aliasing 145 | // If you want to override some path with another. Good for importing same version of React across different libraries 146 | alias: { 147 | // "react$": require.resolve(path.join(__dirname, '../../node_modules/react')) 148 | } 149 | }, 150 | 151 | // Loaders 152 | module: { 153 | loaders: (function() { 154 | var loaders = [] 155 | 156 | // Assets 157 | 158 | // Inline all assets with base64 into javascripts 159 | // TODO make and test requiring assets with url 160 | loaders = loaders.concat([ 161 | { 162 | test: /\.(png|jpg|jpeg|gif|svg)/, 163 | loader: "url-loader?limit=1000000&name=[name]-[hash].[ext]", 164 | exclude: /node_modules/ 165 | }, 166 | { 167 | test: /\.(woff|woff2)/, 168 | loader: "url-loader?limit=1000000&name=[name]-[hash].[ext]", 169 | exclude: /node_modules/ 170 | }, 171 | { 172 | test: /\.(ttf|eot)/, 173 | loader: "url-loader?limit=1000000?name=[name]-[hash].[ext]", 174 | exclude: /node_modules/ 175 | } 176 | ]) 177 | 178 | // Styles 179 | loaders = loaders.concat(makeStyleLoaders()) 180 | 181 | // Scripts 182 | loaders = loaders.concat([ 183 | { 184 | test: /\.jsx?$/, 185 | exclude: /node_modules/, 186 | loader: "babel-loader" 187 | } 188 | ]) 189 | 190 | // Json 191 | loaders = loaders.concat([ 192 | { 193 | test: /\.json/, 194 | exclude: /node_modules/, 195 | loader: "json-loader" 196 | } 197 | ]) 198 | 199 | // NOTE Custom loaders 200 | // loaders = loaders.concat([...]) 201 | 202 | return loaders 203 | })() 204 | } 205 | } 206 | } 207 | 208 | module.exports = configGenerator 209 | -------------------------------------------------------------------------------- /dev-env/webpack/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var express = require('express') 3 | var webpack = require('webpack'); 4 | var WebpackDevServer = require('webpack-dev-server') 5 | 6 | module.exports = function(webpackConfig) { 7 | var host = "0.0.0.0", 8 | port = 3001; 9 | 10 | var compiler = webpack(webpackConfig); 11 | 12 | var webpackDevServerOptions = { 13 | contentBase: 'https://localhost:3001', 14 | publicPath: webpackConfig.output.publicPath, 15 | https: true, 16 | // lazy: true, 17 | // watchDelay: 50, 18 | hot: true, 19 | // Unfortunately quiet swallows everything even error so it can't be used. 20 | quiet: false, 21 | // No info filters only initial compilation it seems. 22 | noInfo: false, 23 | // noInfo: true, 24 | // Remove console.log mess during watch. 25 | stats: { 26 | // assets: false, 27 | colors: true, 28 | // version: false, 29 | // hash: false, 30 | // timings: false, 31 | // chunks: false, 32 | // chunkModules: false 33 | } 34 | } 35 | 36 | new WebpackDevServer( 37 | compiler, 38 | webpackDevServerOptions 39 | ).listen(port, host, function (err, result) { 40 | if (err) { 41 | console.log(err) 42 | } else { 43 | console.log('Listening at https://' + host + ':' + port); 44 | } 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PRDeetz", 3 | "version": "0.0.1", 4 | "description": "Find issues related to commits.", 5 | "private": true, 6 | "scripts": { 7 | "start": "npm run dev", 8 | "dev": "cross-env NODE_ENV=development babel-node ./dev-env/dev.js", 9 | "build": "cross-env NODE_ENV=production babel-node ./dev-env/build.js" 10 | }, 11 | "keywords": [ 12 | "github", "extension", "pull requests" 13 | ], 14 | "dependencies": { 15 | "react": "15.2.1", 16 | "react-dom": "15.2.1" 17 | }, 18 | "devDependencies": { 19 | "babel-cli": "6.4.5", 20 | "babel-core": "6.4.5", 21 | "babel-loader": "6.2.1", 22 | "babel-preset-es2015": "6.3.13", 23 | "babel-preset-react": "6.3.13", 24 | "babel-preset-react-hmre": "1.0.1", 25 | "eslint-config-airbnb": "^9.0.1", 26 | "eslint-plugin-import": "^1.10.3", 27 | "eslint-plugin-jsx-a11y": "^1.5.5", 28 | "eslint-plugin-react": "^5.2.2", 29 | "chokidar": "1.4.2", 30 | "cli-color": "1.1.0", 31 | "cross-env": "^1.0.7", 32 | "css-loader": "0.23.1", 33 | "express": "4.13.4", 34 | "file-loader": "0.8.5", 35 | "fs-extra": "0.26.5", 36 | "json-loader": "0.5.4", 37 | "lodash": "4.1.0", 38 | "style-loader": "0.13.0", 39 | "url-loader": "0.5.7", 40 | "webpack": "1.12.2", 41 | "webpack-dev-server": "1.12.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/error.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const { PropTypes } = React; 4 | 5 | class Error extends React.Component { 6 | clickSettings() { 7 | window.open(chrome.runtime.getURL('settings/settings.html')); 8 | } 9 | render() { 10 | return ( 11 |
12 |

13 | 14 | Error fetching issues 15 | : {this.props.message}. 16 |

17 |

18 | If this is a private repo, you need to configure an auth token in 19 | 20 | settings 21 | . 22 |

23 |
24 | ); 25 | } 26 | } 27 | 28 | Error.propTypes = { 29 | message: PropTypes.string, 30 | }; 31 | 32 | export default Error; 33 | -------------------------------------------------------------------------------- /src/components/issue.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const { PropTypes } = React; 3 | 4 | function Issue(props) { 5 | return ( 6 | 7 | {props.title} 8 | 9 | ); 10 | } 11 | 12 | Issue.propTypes = { 13 | title: React.PropTypes.string.isRequired, 14 | url: React.PropTypes.string.isRequired, 15 | }; 16 | 17 | export default Issue; 18 | -------------------------------------------------------------------------------- /src/components/issues_container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Error from './error'; 3 | import Issue from './issue'; 4 | 5 | const { PropTypes } = React; 6 | 7 | function IssuesContainer(props) { 8 | if (props.isLoading) { 9 | return (
Loading Issues…
); 10 | } 11 | 12 | if (props.errorMessage) { 13 | return ; 14 | } 15 | 16 | if (props.issues && props.issues.length === 0) { 17 | return (
Not found in any issues.
); 18 | } 19 | 20 | return ( 21 |
22 | Seen In: 23 | {props.issues.map((i) => )} 24 |
25 | ); 26 | } 27 | 28 | IssuesContainer.propTypes = { 29 | isLoading: React.PropTypes.bool.isRequired, 30 | issues: React.PropTypes.array, 31 | errorMessage: React.PropTypes.string, 32 | }; 33 | 34 | export default IssuesContainer; 35 | -------------------------------------------------------------------------------- /src/css/github_pr.css: -------------------------------------------------------------------------------- 1 | .pr-deetz-container { 2 | padding: 8px; 3 | margin-right: -8px; 4 | margin-left: -8px; 5 | background: #d8e6ec; 6 | border-top: 1px solid #d8e6ec; 7 | border-bottom-right-radius: 3px; 8 | border-bottom-left-radius: 3px; 9 | font-size: 12px; 10 | } 11 | .pr-deetz-issue { 12 | margin-left: 5px; 13 | } 14 | .pr-deetz-error { 15 | margin-top: 5px; 16 | } 17 | .pr-deetz-error-highlight { 18 | color: red; 19 | font-weight: bold; 20 | } 21 | .pr-deetz-error-message { 22 | margin-bottom: 2px; 23 | } 24 | .pr-deetz-error-suggestion { 25 | font-size: 12px; 26 | } 27 | .pr-deetz-error-suggestion-link { 28 | margin-left: 4px; 29 | } 30 | -------------------------------------------------------------------------------- /src/github_pr.js: -------------------------------------------------------------------------------- 1 | import commitFromUrl from './support/commit_from_url'; 2 | import getIssues from './support/get_issues'; 3 | import IssuesContainer from './components/issues_container'; 4 | import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | 7 | import './css/github_pr.css'; 8 | 9 | function setup() { 10 | const reactDiv = document.createElement('div'); 11 | const commitDiv = document.getElementsByClassName('commit-meta')[0]; 12 | commitDiv.parentNode.insertBefore(reactDiv, commitDiv); 13 | 14 | ReactDOM.render(, reactDiv); 15 | 16 | chrome.storage.sync.get({ authToken: '' }, (config) => { 17 | const repoInfo = commitFromUrl(window.location.pathname); 18 | getIssues(repoInfo.repo, repoInfo.commit, config.authToken).then((issues) => { 19 | ReactDOM.render( 20 | , reactDiv 21 | ); 22 | }).catch((e) => { 23 | ReactDOM.render( 24 | , reactDiv 25 | ); 26 | }); 27 | }); 28 | } 29 | 30 | if (document.readyState === 'interactive' || 31 | document.readyState === 'complete') { 32 | setup(); 33 | } else { 34 | document.addEventListener('DOMContentLoaded', setup); 35 | } 36 | -------------------------------------------------------------------------------- /src/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steveklebanoff/pr_deetz/31fd7821ac97d0ae4b58492b7d0d00ff69b7fdbf/src/icons/icon128.png -------------------------------------------------------------------------------- /src/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steveklebanoff/pr_deetz/31fd7821ac97d0ae4b58492b7d0d00ff69b7fdbf/src/icons/icon16.png -------------------------------------------------------------------------------- /src/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steveklebanoff/pr_deetz/31fd7821ac97d0ae4b58492b7d0d00ff69b7fdbf/src/icons/icon32.png -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "PR Deetz", 4 | "homepage_url": "https://github.com/steveklebanoff/pr_deetz", 5 | "content_scripts": [ 6 | { 7 | "matches": ["https://github.com/*/*/commit/*"], 8 | "js": ["github_pr.js"] 9 | } 10 | ], 11 | "permissions": [ 12 | "storage" 13 | ], 14 | "icons": { 15 | "16": "icons/icon16.png", 16 | "32": "icons/icon32.png", 17 | "128": "icons/icon128.png" 18 | }, 19 | "options_ui": { 20 | "page": "settings/settings.html", 21 | "chrome_style": true, 22 | "open_in_tab": true 23 | }, 24 | "web_accessible_resources": ["settings/settings.html"] 25 | } 26 | -------------------------------------------------------------------------------- /src/settings/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PR Deetz Options 5 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | You will have to add a Github Auth Token with full "repo" access if you want to find issues in private repos. 27 | You can generate one here. 28 |
29 | 30 |
31 | Loading... 32 |
33 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/settings/settings.js: -------------------------------------------------------------------------------- 1 | // TODO: would be nice to put this config stuff 2 | // in a seperate module and not expose this stuff on window 3 | const statusEl = document.getElementById('save-status'); 4 | const authTokenEl = document.getElementById('auth-token'); 5 | const authFormEl = document.getElementById('auth-form'); 6 | const loadingEl = document.getElementById('auth-form-loading'); 7 | 8 | function saveOptions() { 9 | loadingEl.style.display = 'block'; 10 | authFormEl.style.display = 'none'; 11 | 12 | const authToken = authTokenEl.value; 13 | chrome.storage.sync.set({ authToken }, () => { 14 | // Update status to let user know options were saved. 15 | statusEl.textContent = 'Options saved.'; 16 | 17 | loadingEl.style.display = 'none'; 18 | authFormEl.style.display = 'block'; 19 | }); 20 | } 21 | 22 | // Restores select box and checkbox state using the preferences 23 | // stored in chrome.storage. 24 | function restoreOptions() { 25 | // Use default value color = 'red' and likesColor = true. 26 | chrome.storage.sync.get({ authToken: '' }, (config) => { 27 | authTokenEl.value = config.authToken; 28 | 29 | loadingEl.style.display = 'none'; 30 | authFormEl.style.display = 'block'; 31 | }); 32 | } 33 | 34 | restoreOptions(); 35 | document.getElementById('save').addEventListener('click', saveOptions); 36 | -------------------------------------------------------------------------------- /src/support/commit_from_url.js: -------------------------------------------------------------------------------- 1 | export default (urlPath) => { 2 | const urlParts = urlPath.split('/'); 3 | if (urlParts.length !== 5) { 4 | throw new Error(`Unexpected URL Parts Length of ${urlParts.length}`); 5 | } 6 | if (urlParts[0] !== '' || urlParts[3] !== 'commit') { 7 | throw new Error(`Unexpected urlParts ${urlParts}`); 8 | } 9 | return { 10 | repo: `${urlParts[1]}/${urlParts[2]}`, 11 | commit: urlParts[4], 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/support/get_issues.js: -------------------------------------------------------------------------------- 1 | import jsonFetch from './json_fetch'; 2 | 3 | function parseIssuesResponse(githubResponse) { 4 | const items = githubResponse.items; 5 | if (items.length === 0) { 6 | return items; 7 | } 8 | return items.map((issue) => { 9 | return { 10 | title: issue.title, 11 | url: issue.html_url, 12 | username: issue.user.login, 13 | }; 14 | }); 15 | } 16 | 17 | function getIssues(repo, commitHash, authToken) { 18 | let fetchOptions = {}; 19 | if (authToken !== '') { 20 | fetchOptions = { headers: { Authorization: `token ${authToken}` } }; 21 | } 22 | 23 | return new Promise((fulfill, reject) => { 24 | const apiUrl = `https://api.github.com/search/issues?q=${commitHash}+repo:${repo}`; 25 | jsonFetch(apiUrl, fetchOptions) 26 | .then((r) => { 27 | return fulfill(parseIssuesResponse(r)); 28 | }) 29 | .catch(reject); 30 | }); 31 | } 32 | 33 | export default getIssues; 34 | -------------------------------------------------------------------------------- /src/support/json_fetch.js: -------------------------------------------------------------------------------- 1 | // from http://stackoverflow.com/a/34787336 2 | function checkStatus(response) { 3 | if (response.ok) { 4 | return response; 5 | } 6 | const error = new Error(response.statusText); 7 | error.response = response; 8 | throw error; 9 | } 10 | 11 | export default function jsonFetch(url, options) { 12 | return fetch(url, options) 13 | .then(checkStatus) 14 | .then((r) => r.json()); 15 | } 16 | --------------------------------------------------------------------------------