├── .appveyor.yml ├── .editorconfig ├── .env.example ├── .eslintignore ├── .gitattributes ├── .github └── issue_template.md ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── assets ├── link-icon-chrome-webstore.png ├── link-icon.png ├── manifest.json └── options.html ├── lib ├── app.js ├── options │ ├── components │ │ └── index.js │ ├── options.js │ ├── page.js │ └── storage.js ├── permalinker.js └── user-repo-branch.js ├── package.json ├── scripts └── chrome-launch.js ├── test ├── fixtures │ ├── PR_review_comment │ │ └── page.html │ ├── already_permalink │ │ └── page.html │ ├── badge │ │ └── page.html │ ├── do_not_permalink_issue_comment_self-links │ │ └── page.html │ ├── eslump_readme │ │ └── page.html │ ├── fall_back_to_master_branch │ │ └── page.html │ ├── gist │ │ └── page.html │ ├── gist_comments │ │ └── page.html │ ├── issue_comment │ │ └── page.html │ ├── markdown_blob │ │ └── page.html │ ├── markdown_internal_link │ │ └── page.html │ ├── milestones_url │ │ └── page.html │ └── search_links │ │ └── page.html ├── permalinker.js └── user-repo-branch.js ├── webpack.config.js └── yarn.lock /.appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against this version of Node.js 2 | environment: 3 | nodejs_version: "8" 4 | GITHUB_TOKEN: 5 | secure: Mh3yVwUwbcHCUYF2NkA+Qs84kuijuUswzz6QgXBw7/HvNiSM0EAHVreUeDlqZCh3 6 | 7 | cache: 8 | - node_modules 9 | - "%LOCALAPPDATA%\\Yarn" 10 | 11 | # Install scripts. (runs after repo cloning) 12 | install: 13 | # Get the latest stable version of Node.js or io.js 14 | - ps: Install-Product node $env:nodejs_version 15 | - appveyor-retry choco install zip 16 | # install modules 17 | - yarn 18 | 19 | # Post-install test scripts. 20 | test_script: 21 | # Output useful info for debugging. 22 | - node --version 23 | - npm --version 24 | # Make sure build works on Windows 25 | - npm run package-all 26 | # run tests 27 | - appveyor-retry call npm test 28 | 29 | # Don't actually build. 30 | build: off 31 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.json] 15 | indent_size = 2 16 | 17 | # We recommend you to keep these unchanged 18 | end_of_line = lf 19 | charset = utf-8 20 | trim_trailing_whitespace = true 21 | insert_final_newline = true 22 | 23 | [*.md] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_TOKEN= 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | test/fixtures/*/* binary 3 | packages/blob-reader/fixtures/* linguist-documentation 4 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | **Browser name:** 7 | 8 | **Browser version:** 9 | 10 | **OctoPermalinker version:** 11 | 12 | **URL number where issue occurs:** 13 | 14 | **Expected behavior:** 15 | 16 | **Actual behavior:** 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | 3 | node_modules 4 | npm-debug.log 5 | 6 | dist/ 7 | out/ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 7 5 | - node 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | before_script: 11 | - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then yarn upgrade && git diff --exit-code; 12 | fi 13 | - npm run package-all 14 | script: 15 | - travis_retry npm test 16 | deploy: 17 | - provider: script 18 | script: npm run release 19 | skip_cleanup: true 20 | on: 21 | branch: master 22 | tags: true 23 | env: 24 | global: 25 | - EXTENSION_ID=bcnkgcoohaaaclieohdlkphgfinkgbfm 26 | - secure: WMMEr2jhFsS8e/LFcJp5CfZEWuV4hDPHS844PzfZSmr6OL/k1C/zoUH2TQC8fB1imj9wtQd4uKekuOmn6pIXAqKGYHUcKfqqEh+DxuhvjC/IHtLXANlsHReDHDuPE9CkdtRPOQgU3D97NmFZzgb1whDDA1zZxVS8wTON9b/7IWcXXh9L2WU/m5pJzafI6PVkBWzWqOpnmOrYulP26PvtBgcc2HoqDMM3C4Jk9jkbJADhlBUHCrhCb3zDC6YJNYRps0XHkH2k1lkVjz8hiv4pL6fk8zIMUIaKL6MmYXGaNwaEDHvDD6wRqpCgUdqSUFqwoe3UlxehpWP3UtJN8Chd/79c357GboiQ6X/c21sKLhYHaHKlp5nd1MuoRdW3p9GaOfQvnaR2p1Te+6+n9qEn4wzpmmd4shsf034T0Do4KVbotknzDvj1xHkxSmEQD0gBwEjc5t7H8hpVgJ7K9E1qjP0PgVd9U07ZRxHC8q5641dEUJPqtHLD2Uky2hzx4Ox7ZdiN/CYEZzwdHaI4efBiyqCQulvtmxHn+t/TBn4Ki9MmCfT9NgJ0fcfSooFmYxNmA6X/MyzwLMNIaHwCbZsM1ZGJeB2Bcv3RM/VJIMuE0yRGuQcnPhFAcz9r+msEnjEn/qXcy9efDNzQAB8ywna/FZGcjRTn4w2r9hAxl9HRm30= 27 | - secure: Yse5gX5KErF8VwthtF2LgWqli4Xg6tZbxjSzXRLrbsjZ9gfS0QeXN+OeojYEEXrvYd1z2I1nFYCxcY1gmVuMMSB5L2g66En0zOy9AcR81UOC5OoiTnAT0DuuLVulROM9U2FO/vBrPDgw1ZKdTXLOtRlLJa2XMxtsMHA03q0WcubeEj45XsZBnAcT1Jhy6uqknlHCAZR6tF709d9XJuNBu8X9tBYCbXKq4YISQeTb9SwftDcZLt8/jwyHwPSFPKl5xHt4AkFGgXHMcF/7EuDo3u1crN9g7xl1XTMTROdumQXy/OFkYwYohfmiai5cyPo9UXg6Pw8yeed6TVinH3IElAPkib+H642MKM9S6kKBVAq0WDg5n+XGl+8/mf9oO3ozwy5s+hkIaru5TuwdBu1PAIso9KIPb7b258j6RXQ7BstsvBVOTQQxqGCop+DCn5ANAFJqQtdjcn07GJlVUoW5/8IzwoLXuntzjnsMGVLncsv6C2ropDHyXEH+tdz4+sNVa2PHbmmEuEDttYzgxv2CYvnZffxzvXt3ckKcqrm0HYvA3KbsrPJ0/aYU449CzBT5tYDZ92F/lckgs8DJhXe7sxuM9YuTBM/YNqOUqr9sRldg5j2/M4Ty8ropoUbqnu+7c/LywiVUIBIcj5xYrGphgofNO9UQsTDWy/fFoLRY14I= 28 | - secure: LaVnrsGk9PeHL2Tls4SMKqFErJEtOEUoxQimCoSSUpHI/q9+GG9pjKzfQIJ25ejFuhCcJumH0c0fi8eYgFIZ3p4k5boIr1v5A9SH9G0nDEIdTwqZZMcwtMTQhEAn64V3YDZwcgcIL9UbNd0bCwiyV/1A8IfRYWWtFQGkC6S5cxWqUge+FtGoyr6PzXA0xGG6WhoKyuXvQw/He9R8hdgXouURpP+LA1/JYVNVMMEDrB3AViMdvpLJHoNCmQJJ2rH8wsbtlEpco2P5Yqh2kKb6Fd84sLao3rGgy6tlBL6tzUEIQvK2NBxsa6vdgadvWOqfDrqx1SW/R8oXXtRR2zmU0xD+msBjqw2gAHo9UAdNzlyn70ykaSxOIjjWoF6akHzWHaE0Gbql5QC7c6lJ4wM54hPLCjTHtN3mFUD++gZ6t0UZnhhEQdfnhcsVOqeYkzh4tK0VCJv2VWaTAJ1RpVq/5XKw9TbLTD/37WBoOo/4oqODubAvZdGPFEGMwFwyRjIljEmcI7l1PaeIWZJN3pIFi+ea1XR2sAeMHe5/9zbrtPrfikSbOhoCi8hMq/OdJTIwBwm3/wPyhxhIVcU8JZaJqBZY1E3sIFk+FIg0UZoLtKrKp5qMDBAriDf+mMCg0+//LTinSrRf7e/l+U/JOWHpjSw+uEkrmhU0S4e8bvnhBqg= 29 | - secure: srEiojxHc60jwdShTSLWHl6K0eXSBDdWInBUHdXGOvf/crZsJu+Kgpb0HmOCfU0LUKjyI63UrRbLzDAU/RBUJCj4VHNkb0srKazUimanF52MWBD0AP+d1MgPSVy68bwpex0V4ED0OVf/7DRBj2m4ePsalgWQ/RzbgAuCO8cfKdeda3Y+S2Xbj9pkbKCqEq3WNiDZrPTJY6QsNepdtfHAjAYXyYshpw73F9TTIXkLXb9NUfD3PP1VngS5WTq3flEHbj4KvroPjTbS58B6qe0Yqc8HRcZI6c+Q0QtbuMfApNid4ge1SegobduldFto2HFofttYZ+v/SnW+wLKIrhohm0omyYV+H/pTuvzokkYrvqkTZec+7c5bK32yFQf5y0KDnmAsYws64i7g7mMH0ECwY6ckzepbKWXZg8NusIYUlbebCClj31Tkeze+cXWM12eRHoOYaqA3REoQyFs8x06F2K0c4UH5ermGatTgTXnnYFkxtYxfzeYZzsFdhDRPcetuE7xZuNYjY2lMGpxlwtDnUEn1iW8snBeB7d/gOKYgUjV+e57KznC8/niKfV6CG0INwsxAqbYyES6MmObh7V7wrUy4zYdZoRLfFOM0CMrIWWxEXvVnkYvNi8CHCCQFZCRtNe2c3dAtc8jX2VHTBUCBEjAOrOnPLxYhuzHp47zloU4= 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | First off, thanks for taking the time to contribute! :tada: :+1: 5 | 6 | # Developer Guide 7 | 8 | ## Quick Start 9 | 10 | To build and run the extension follow these steps. 11 | 12 | 1. Clone the repository. 13 | 1. Make sure [Yarn](https://yarnpkg.com/docs/install) is installed. 14 | 1. Run `yarn install` to setup the project and install all required dependencies. 15 | 1. Build and load the extension: 16 | * Firefox (Quickstart): 17 | 1. `npm run firefox-open` 18 | * Chrome (Quickstart): 19 | 1. `npm run chrome-open` 20 | * Chrome (Long Version): 21 | 1. To build the extension once run `npm run chrome-build` or `npm run chrome-watch` during development. 22 | 1. Load extension https://developer.chrome.com/extensions/getstarted#unpacked. 23 | 24 | ## Pull Request Guidelines 25 | 26 | - Please check to make sure that there aren't existing pull requests attempting to address the issue mentioned. We also recommend checking for issues related to the issue on the tracker, as a team member may be working on the issue in a branch or fork. 27 | - Non-trivial changes should be discussed in an issue first 28 | - Develop in a topic branch, not master 29 | - Lint the code by `npm run lint` 30 | - Add relevant tests to cover the change 31 | - Make sure test-suite passes: `npm test` 32 | - Squash your commits 33 | - Write a convincing description of your PR and why we should land it 34 | 35 | ## Release Checklist 36 | 37 | - Run [`npm version patch`](https://docs.npmjs.com/cli/version) to update the version number. Use `minor` or `major` instead of `patch` if needed (see [semver.org](http://semver.org/) for details). 38 | - Open a [pull request](https://github.com/josephfrazier/octopermalinker/pulls) with the new version. 39 | - Once the pull request is merged in, tag the resulting commit as `vX.Y.Z` (where `X`, `Y`, `Z` are the major, minor, and patch versions). 40 | - Push the tag to GitHub. This will trigger Travis CI to create a new [GitHub Release](https://github.com/josephfrazier/octopermalinker/releases) and submit the new Chrome extension to the Chrome Web Store. See [.travis.yml](https://github.com/josephfrazier/octopermalinker/blob/master/.travis.yml) for details. 41 | - Submit `firefox-octopermalinker-X.Y.Z.zip` from the [GitHub Release](https://github.com/josephfrazier/octopermalinker/releases) to [addons.mozilla.org](https://addons.mozilla.org/en-US/developers/addon/octopermalinker/versions#version-upload). Be sure to include the `Source code (zip)` file from the release as well. 42 | - Submit `opera-octopermalinker-X.Y.Z.zip` from the [GitHub Release](https://github.com/josephfrazier/octopermalinker/releases) to [addons.opera.com](https://addons.opera.com/developer/package/226344/?tab=versions). Afterwards, go to the [Conversation tab](https://addons.opera.com/developer/package/226344/?tab=conversation), add a link to the `Source code (zip)` file and copy/paste the build instructions from previous releases. 43 | - Update release notes at https://github.com/josephfrazier/octopermalinker/releases/tag/vX.Y.Z. You can find a list of changes since the previous release at https://github.com/josephfrazier/octopermalinker/compare/vA.B.C...vX.Y.Z, where `A.B.C` is the previous version number. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017–present Joseph Frazier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OctoPermalinker [![Build Status](https://travis-ci.org/josephfrazier/octopermalinker.svg?branch=master)](https://travis-ci.org/josephfrazier/octopermalinker) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/josephfrazier/octopermalinker?svg=true&branch=master)](https://ci.appveyor.com/project/josephfrazier/octopermalinker) 2 | 3 | OctoPermalinker is a browser extension that searches GitHub comments/files for links to files on branches, and adds a link to where the branch pointed when the comment/file was made/updated. This helps you avoid following a link that was broken after being posted. For context, here's some discussion about broken GitHub links: [Don't link to line numbers in GitHub](https://news.ycombinator.com/item?id=8046710). 4 | 5 | # Demo 6 | 7 | ## Before 8 | ![Imgur](http://i.imgur.com/0x7mF6h.gif) 9 | 10 | ## After 11 | ![Imgur](http://i.imgur.com/NmyKDRk.gif) 12 | 13 | # Install 14 | 15 | OctoPermalinker is available from the [Chrome Web Store]. 16 | If you use Firefox, install [Chrome Store Foxified] first. 17 | 18 | Alternatively, you can build and install OctoPermalinker locally by following the [Quick Start] section of the Contributing guide. 19 | 20 | [Chrome Web Store]: https://chrome.google.com/webstore/detail/octopermalinker/bcnkgcoohaaaclieohdlkphgfinkgbfm 21 | [Chrome Store Foxified]: https://addons.mozilla.org/en-US/firefox/addon/chrome-store-foxified/ 22 | [Quick Start]: ./CONTRIBUTING.md#quick-start 23 | 24 | # Features 25 | 26 | ### OAuth Support 27 | 28 | Without authentication, [the GitHub API allows only 60 requests per hour](https://developer.github.com/v3/#rate-limiting). You can increase this to 5,000 requests per hour by [creating an OAuth token](https://github.com/settings/tokens) (no scopes needed) and entering it into the extension options page (at `chrome://extensions/` in Chrome, or `about:addons` -> Extensions in Firefox). 29 | 30 | ### Paste Protection 31 | 32 | OctoPermalinker will also detect if you paste a fragile link into a comment box. When this happens, OctoPermalinker will notify you of the corresponding permalink, in case you'd like to use it instead. 33 | 34 | # Want to contribute? 35 | 36 | Anyone can help make this project better - check out the [Contributing](/CONTRIBUTING.md) guide! 37 | 38 | # Feedback 39 | 40 | If you encounter a problem using OctoPermalinker, or would like to request an enhancement, feel free to create an [issue](https://github.com/josephfrazier/octopermalinker/issues). 41 | 42 | # Thanks 43 | 44 | - [@stefanbuck](https://github.com/stefanbuck) for building [OctoLinker], which this project is based on. 45 | 46 | [OctoLinker]: https://github.com/OctoLinker/browser-extension/ 47 | 48 | # Legal and License 49 | 50 | The OctoPermalinker project is not affiliated with, sponsored by, or endorsed by github, inc. 51 | 52 | Copyright (c) 2017–present [Joseph Frazier](https://github.com/josephfrazier) Licensed under the MIT license. 53 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | * Add PR link after commit links/references 5 | * Handle `/blame/` links 6 | * Handle `/commits/` links 7 | * Example: https://github.com/localForage/localForage/pull/555#issuecomment-392352127 8 | * href is https://github.com/localForage/localForage/commits/v2.0-dev?after=40c70210a54645c33919a283b2a1138f40b7a001+69 9 | * Permalink urls in Reddit comments: https://www.reddit.com/r/reactjs/comments/4y43uc/react_router_how_to_deal_with_search_queries/d6l7q2f/ 10 | * Fix link at https://github.com/greenkeeperio/greenkeeper/issues/314#issuecomment-255737405 11 | * href is https://github.com/greenkeeperio/greenkeeper/blob/master/src/evilhackerdude.js 12 | * Fix link at https://github.com/prettier/prettier/pull/2190#discussion_r122602431 13 | * href is https://github.com/tdeekens/prettier/blob/fix/css-modules-composes/src/doc-utils.js#L161 14 | * Permalink urls in Hacker News comments: https://news.ycombinator.com/item?id=14110168 15 | * https://news.ycombinator.com/item?id=13917368 16 | * https://news.ycombinator.com/item?id=14697480 17 | * https://news.ycombinator.com/item?id=14807951 18 | * handle wiki links - "wide-spectrum blocker" at https://github.com/gorhill/uBlock/tree/f4f52c32209348dc050b77b6e0d84b9be5897541#beware-ublock-origin-is-completely-unrelated-to-the-web-site-ublockorg 19 | * permalink raw.githubusercontent.com links: https://web.archive.org/web/20170329173434id_/https://github.com/github/markup/issues/1022#issue-215368549 20 | * permalink urls in code blobs: https://github.com/MatAtBread/fast-async/blob/fbd2c4db54813b5130f49a46f6280bcad17c7f53/plugin.js#L35 21 | * see if github provides blame api to figure out when it was committed 22 | * Use e.g. `git log -S` to guess when links were added. 23 | * For example, the `more...` permalink here is broken: https://github.com/nice-registry/nice-package/tree/5388835b5412c2e5f448fbd5043117f07d87a210#nice-package--- 24 | * Figure out what's going on with 96bd92ba935682d06693c47775b93dec32d019c2 25 | -------------------------------------------------------------------------------- /assets/link-icon-chrome-webstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephfrazier/octopermalinker/058bb281747f9280b528cb8ae78be101f970149f/assets/link-icon-chrome-webstore.png -------------------------------------------------------------------------------- /assets/link-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephfrazier/octopermalinker/058bb281747f9280b528cb8ae78be101f970149f/assets/link-icon.png -------------------------------------------------------------------------------- /assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OctoPermalinker", 3 | "version": "1.0.0", 4 | "manifest_version": 2, 5 | "author": "Joseph Frazier", 6 | "description": "Fixes broken GitHub links.", 7 | "homepage_url": "https://github.com/josephfrazier/octopermalinker", 8 | "icons": { 9 | "212": "link-icon.png" 10 | }, 11 | "applications": { 12 | "gecko": { 13 | "id": "OctoPermalinker" 14 | } 15 | }, 16 | "options_ui": { 17 | "page": "options.html", 18 | "chrome_style": true 19 | }, 20 | "content_scripts": [ 21 | { 22 | "matches": [ 23 | "https://github.com/*", 24 | "https://gist.github.com/*" 25 | ], 26 | "js": [ 27 | "app.js" 28 | ], 29 | "run_at": "document_idle", 30 | "all_frames": false 31 | } 32 | ], 33 | "web_accessible_resources": [ 34 | "link-icon.png" 35 | ], 36 | "permissions": [ 37 | "storage", 38 | "https://github.com/", 39 | "https://gist.github.com/" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /assets/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/app.js: -------------------------------------------------------------------------------- 1 | import injection from 'github-injection' 2 | import permalink from './permalinker.js' 3 | import * as storage from './options/storage.js' 4 | 5 | storage.load().then(() => { 6 | injection(window, () => permalink({ 7 | token: storage.get('githubOauthToken') 8 | })) 9 | }) 10 | -------------------------------------------------------------------------------- /lib/options/components/index.js: -------------------------------------------------------------------------------- 1 | export default function ({ type, ...rest }) { 2 | if (type === 'input') { 3 | return input(rest) 4 | } 5 | } 6 | 7 | function input ({ name, label, value, defaultValue }) { 8 | const val = (value === undefined) ? defaultValue : value 9 | 10 | return `
11 | 14 |
` 15 | } 16 | -------------------------------------------------------------------------------- /lib/options/options.js: -------------------------------------------------------------------------------- 1 | export const options = [ 2 | { 3 | type: 'input', 4 | name: 'githubOauthToken', 5 | label: 'GitHub OAuth Token', 6 | value: '', 7 | defaultValue: '' 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /lib/options/page.js: -------------------------------------------------------------------------------- 1 | import components from './components' 2 | import * as storage from './storage' 3 | import { options } from './options' 4 | 5 | const formEl = document.querySelector('#options') 6 | 7 | storage.load().then(() => { 8 | options.forEach((item) => { 9 | formEl.innerHTML += components({ 10 | ...item, 11 | value: storage.get(item.name) 12 | }) 13 | }) 14 | }) 15 | 16 | formEl.addEventListener('input', ({ target }) => { 17 | const tag = target.tagName.toLowerCase() 18 | 19 | if (tag === 'input' && target.type === 'text') { 20 | storage.set(target.name, target.value) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /lib/options/storage.js: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill' 2 | import { options } from './options' 3 | 4 | const store = {} 5 | const defaults = {} 6 | 7 | options.forEach((item) => { 8 | defaults[item.name] = item.defaultValue 9 | }) 10 | 11 | export const get = key => store[key] 12 | 13 | export const set = async (key, value) => { 14 | const data = { 15 | [key]: value 16 | } 17 | 18 | try { 19 | return await browser.storage.sync.set(data) 20 | } catch (err) { 21 | return browser.storage.local.set(data) 22 | } 23 | } 24 | 25 | export const load = async () => { 26 | let data 27 | 28 | try { 29 | data = await browser.storage.sync.get(null) 30 | } catch (err) { 31 | data = await browser.storage.local.get(null) 32 | } 33 | 34 | Object.assign(store, defaults, data) 35 | } 36 | -------------------------------------------------------------------------------- /lib/permalinker.js: -------------------------------------------------------------------------------- 1 | import { parse } from 'url' 2 | import GitHub from 'github-api' 3 | import concatMap from 'concat-map' 4 | import domify from 'domify' 5 | import userRepoBranch from './user-repo-branch' 6 | 7 | /* 8 | * for each comment on the page (limit to issues/pulls?) 9 | * get the datetime attribute of the comment 10 | * for each link in the comment 11 | * skip if the link is not to a branch (check this with a github url parser) 12 | * Get the latest commit on the branch before the datetime, using https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository 13 | * append link with the commit hash instead of the branch name 14 | */ 15 | 16 | export default async (githubOptions = {}, document = global.document) => { 17 | const githubApi = new GitHub(githubOptions) 18 | 19 | setupPasteListeners({ githubApi, document }) 20 | 21 | const roots = await getRootsAndDateTimes({ githubApi, document }) 22 | 23 | return Promise.all(concatMap(roots, ({ rootEl, datetime }) => 24 | getBranchLinks(rootEl).map(async (anchor) => { 25 | try { 26 | const permalink = await getPermalink({ githubApi, href: anchor.href, datetime }) 27 | insertPermalink({ document, anchor, permalink, datetime }) 28 | } catch (err) { 29 | // eslint-disable-line no-empty 30 | } 31 | }) 32 | )) 33 | } 34 | 35 | function getMetaProperty (document, propertyName) { 36 | return document.querySelector(`meta[property="${propertyName}"]`).getAttribute('content') 37 | } 38 | 39 | function setupPasteListeners ({ githubApi, document }) { 40 | const textareas = selectAll(document, 'textarea') 41 | 42 | textareas.forEach((field) => { 43 | const attrName = 'data-octopermalinker-paste-listener' 44 | if (field.getAttribute(attrName)) { 45 | return 46 | } 47 | field.setAttribute(attrName, 'true') 48 | 49 | field.addEventListener('paste', pasteListener, false) 50 | }) 51 | 52 | async function pasteListener (event) { 53 | // https://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser/6804718#6804718j 54 | const href = event.clipboardData.getData('text') 55 | const now = (new Date()).toISOString() 56 | 57 | // Don't paste-protect urls to a repo's homepage. 58 | // They're likely intentional. 59 | if (parse(href).path.slice(1).split('/').length === 2) { 60 | return href 61 | } 62 | 63 | const permalink = await getPermalink({ 64 | href, 65 | githubApi, 66 | datetime: now 67 | }) 68 | 69 | const message = "It looks like you just pasted a fragile link. Here's a permalink:" 70 | global.prompt(message, permalink) 71 | } 72 | } 73 | 74 | async function getRootsAndDateTimes ({ githubApi, document }) { 75 | let roots = [] 76 | 77 | const isGist = getMetaProperty(document, 'og:site_name') === 'Gist' 78 | if (isGist) { 79 | roots = await getGistFilesAndDatetimes({ githubApi, document }) 80 | roots = roots.concat(getCommentsAndDatetimes(document)) 81 | } else { 82 | roots = getCommentsAndDatetimes(document) 83 | if (roots.length === 0) { 84 | roots = getMarkdownBodiesAndDatetimes(document) 85 | } 86 | } 87 | 88 | return roots 89 | } 90 | 91 | async function getGistFilesAndDatetimes ({ githubApi, document }) { 92 | const location = parse(getMetaProperty(document, 'og:url')) 93 | const gistId = location.pathname.split('/')[2] 94 | 95 | // copied from https://github.com/github-tools/github/blob/46dd1d7c20b6fa686c47d4219a1aff0ca53e6ea0/lib/Gist.js#L118 96 | // TODO use listCommits instead of _requestAllPages once this is resolved: 97 | // https://github.com/github-tools/github/issues/436 98 | // 99 | // docs: https://developer.github.com/v3/gists/#list-gist-commits 100 | // example output: https://api.github.com/gists/77916f3fcdd7d285f7c9/commits 101 | const response = await githubApi.getGist(gistId)._requestAllPages(`/gists/${gistId}/commits`) 102 | const commits = response.data 103 | const commit = commits[0] 104 | const datetime = commit.committed_at 105 | 106 | return selectAll(document, '.file-box').map(file => ({ 107 | rootEl: file, 108 | datetime 109 | })) 110 | } 111 | 112 | const selectAll = (element, selector) => Array.from(element.querySelectorAll(selector)) 113 | const pluck = key => obj => obj[key] 114 | 115 | function getCommentsAndDatetimes (document) { 116 | const parentSelectors = ['.comment', '.review-comment-contents'] 117 | return concatMap(parentSelectors, selector => 118 | selectAll(document, selector).map(rootEl => ({ 119 | rootEl, 120 | datetimeEl: rootEl.querySelector('relative-time') 121 | })).filter(pluck('datetimeEl')).map(({ rootEl, datetimeEl }) => ({ 122 | rootEl, 123 | datetime: datetimeEl.getAttribute('datetime') 124 | })) 125 | ) 126 | } 127 | 128 | function getMarkdownBodiesAndDatetimes (document) { 129 | const datetime = document.querySelector('.repository-content relative-time').getAttribute('datetime') 130 | return selectAll(document, '.markdown-body').map(rootEl => ({ 131 | rootEl, 132 | datetime 133 | })) 134 | } 135 | 136 | function getBranchLinks (rootEl) { 137 | return selectAll(rootEl, 'a') 138 | .filter(anchor => anchor.getAttribute('data-octopermalink') == null) 139 | .filter(anchor => anchor.getAttribute('aria-hidden') !== 'true') 140 | .filter(anchor => anchor.getAttribute('href') && !anchor.getAttribute('href').startsWith('#')) 141 | .filter(anchor => !anchorIsBadge(anchor)) 142 | } 143 | 144 | function anchorIsBadge (anchor) { 145 | return anchor.firstChild === anchor.lastChild && 146 | anchor.firstChild.tagName === 'IMG' 147 | } 148 | 149 | async function getPermalink ({ githubApi, href, datetime }) { 150 | const githubInfo = userRepoBranch(href) 151 | const { user, repo, branch } = githubInfo 152 | href = githubInfo.href 153 | 154 | if (!branch || isCommitHash(branch)) { 155 | throw Error('href does not have a branch, or is to a commit hash') 156 | } 157 | 158 | const response = await githubApi.getRepo(user, repo).listCommits({ branch, until: datetime }) 159 | const commits = response.data 160 | const commit = commits[0] 161 | const sha = commit.sha 162 | 163 | return href.replace(branch, sha) 164 | } 165 | 166 | function isCommitHash (branch) { 167 | // If the "branch" name is all hexadecimal, and at least 7 characters long, 168 | // it's probably actually a commit hash. 169 | // TODO use github api instead of pattern matching 170 | return /^[a-f0-9]{7,}$/.test(branch) 171 | } 172 | 173 | function insertPermalink ({ document, anchor, permalink, datetime }) { 174 | if (!anchor.title) { 175 | anchor.title = anchor.href 176 | } 177 | if (anchor.nextElementSibling && anchor.nextElementSibling.href === permalink) { 178 | return 179 | } 180 | const imageUrl = global.chrome ? global.chrome.extension.getURL('link-icon.png') : '' // tests don't have global.chrome 181 | const image = `` 182 | const relativeTime = `at ${datetime}` 183 | const permaAnchor = `${image} ${relativeTime}` 184 | insertAfter(domify(` (${permaAnchor}) `, document), anchor) 185 | } 186 | 187 | function insertAfter (node, ref) { 188 | ref.parentNode.insertBefore(node, ref.nextSibling) 189 | } 190 | -------------------------------------------------------------------------------- /lib/user-repo-branch.js: -------------------------------------------------------------------------------- 1 | import { parse, resolve } from 'url' 2 | import githubUrl from 'github-url-to-object' 3 | 4 | export default function userRepoBranch (href) { 5 | const githubInfo = githubUrl(href) 6 | 7 | if (!githubInfo) { 8 | return {} 9 | } 10 | 11 | const { user, repo } = githubInfo 12 | 13 | // branch names can have slashes, but since they aren't escaped, 14 | // github-url-to-object can't tell where the branch name ends, 15 | // and it might include part of the path in the branch name, 16 | // so just assume the branch name doesn't have slashes 17 | const branch = githubInfo.branch.split('/')[0] 18 | 19 | // skip false positives like issue/pull urls 20 | if (isNotBranch(href, user, repo)) { 21 | return { user, repo } 22 | } 23 | if (!href.includes(branch)) { 24 | if (branch !== 'master') { 25 | return { user, repo } 26 | } 27 | href = assumeBranch(href, 'master') 28 | } 29 | 30 | return { 31 | user, 32 | repo, 33 | branch, 34 | href 35 | } 36 | } 37 | 38 | function isNotBranch (href, user, repo) { 39 | return [ 40 | 'issues', 41 | 'pull/', 42 | 'pulls', 43 | 'commit/', 44 | 'search?', 45 | 'milestones', 46 | 'edit/', 47 | 'graphs/', 48 | 'releases', 49 | 'compare/' 50 | ].some(component => href.includes(`${user}/${repo}/${component}`)) 51 | } 52 | 53 | function assumeBranch (href, branch) { 54 | href = href.replace(/\/$/, '') 55 | const { pathname, hash } = parse(href) 56 | const last = pathname.split('/').slice(-1) 57 | return resolve(href, `${last}/tree/${branch}${hash || ''}`) 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OctoPermalinker", 3 | "version": "1.0.0", 4 | "engines": { 5 | "node": ">=7.6.0" 6 | }, 7 | "main": "lib/app.js", 8 | "scripts": { 9 | "lint": "standard", 10 | "pretest": "npm run lint", 11 | "test": "ava", 12 | "version": "json -I -f assets/manifest.json -e \"this.version='`json -f package.json version`'\" && git add assets/manifest.json", 13 | "package-all": "npm-run-all chrome-pack firefox-pack && cp out/chrome-octopermalinker-$npm_package_version.zip out/opera-octopermalinker-$npm_package_version.zip", 14 | "release": "webstore upload --source out/chrome-octopermalinker-$npm_package_version.zip --auto-publish", 15 | "chrome-manifest": "mkdirp dist && json -e \"delete this.applications;\" < assets/manifest.json > dist/manifest.json", 16 | "chrome-build": "npm run chrome-manifest && webpack", 17 | "chrome-watch": "npm run chrome-manifest && webpack --watch", 18 | "chrome-pack": "npm run chrome-build && mkdirp out && zip -rj out/chrome-octopermalinker-$npm_package_version.zip dist", 19 | "chrome-open": "npm run chrome-build && npm run chrome-launch --", 20 | "chrome-launch": "./scripts/chrome-launch.js", 21 | "firefox-manifest": "mkdirp dist && cp assets/manifest.json dist/manifest.json", 22 | "firefox-build": "npm run firefox-manifest && webpack", 23 | "firefox-watch": "npm run firefox-manifest && webpack --watch", 24 | "firefox-pack": "npm run firefox-build && mkdirp out && zip -rj out/firefox-octopermalinker-$npm_package_version.zip dist/", 25 | "firefox-open": "npm run firefox-build && npm run firefox-launch --", 26 | "firefox-launch": "web-ext run --source-dir dist --pref startup.homepage_welcome_url='https://github.com/isaacs/github/issues/625#issuecomment-203464167'" 27 | }, 28 | "babel": { 29 | "plugins": [ 30 | [ 31 | "transform-runtime", 32 | { 33 | "polyfill": false 34 | } 35 | ], 36 | "transform-object-rest-spread" 37 | ], 38 | "presets": [ 39 | "env" 40 | ] 41 | }, 42 | "ava": { 43 | "require": [ 44 | "babel-register" 45 | ] 46 | }, 47 | "dependencies": { 48 | "babel-runtime": "^6.26.0", 49 | "concat-map": "^0.0.1", 50 | "domify": "^1.4.0", 51 | "github-api": "^3.0.0", 52 | "github-injection": "^1.0.1", 53 | "github-url-to-object": "^4.0.2", 54 | "webextension-polyfill": "^0.1.1" 55 | }, 56 | "devDependencies": { 57 | "ava": "^0.22.0", 58 | "babel-core": "^6.26.0", 59 | "babel-loader": "^7.1.2", 60 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 61 | "babel-plugin-transform-runtime": "^6.23.0", 62 | "babel-preset-env": "^1.6.0", 63 | "babel-register": "^6.26.0", 64 | "chrome-launch": "^1.1.4", 65 | "chrome-webstore-upload-cli": "^1.1.1", 66 | "copy-webpack-plugin": "^4.0.1", 67 | "dotenv-safe": "^4.0.3", 68 | "fs-extra": "^4.0.1", 69 | "got": "^7.1.0", 70 | "jsdom": "^11.2.0", 71 | "json": "^9.0.6", 72 | "mkdirp": "^0.5.1", 73 | "npm-run-all": "^4.1.0", 74 | "pify": "^3.0.0", 75 | "standard": "^10.0.3", 76 | "web-ext": "^2.0.0", 77 | "webpack": "^3.5.5" 78 | }, 79 | "optionalDependencies": { 80 | "fsevents": "*" 81 | }, 82 | "resolutions": { 83 | "tough-cookie": "2.3.3" 84 | }, 85 | "private": true 86 | } 87 | -------------------------------------------------------------------------------- /scripts/chrome-launch.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const chromeLaunch = require('chrome-launch') // eslint-disable-line import/no-extraneous-dependencies 4 | 5 | const url = 'https://github.com/isaacs/github/issues/625#issuecomment-203464167' 6 | const args = ['--load-extension=./dist'] 7 | 8 | chromeLaunch(url, { args }) 9 | console.log('A new instance of Chrome should now be open in the background.') // eslint-disable-line no-console 10 | -------------------------------------------------------------------------------- /test/fixtures/fall_back_to_master_branch/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | fix: cross-env needs to prefix all individual commands by kentcdodds · Pull Request #1 · mikechabot/cross-env-example · GitHub 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
111 | Skip to content 112 |
113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 165 | 166 | 167 |
168 | 169 |
170 | 171 |
172 |
173 | 174 | 175 | 176 |
177 |
178 |
179 | 180 | 181 | 182 |
183 |
184 | 185 | 186 | 230 | 231 |

232 | 233 | /cross-env-example 236 | 237 |

238 | 239 |
240 | 290 |
291 | 292 |
293 |
294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 |
302 | 303 |
304 | 305 |
310 | 311 |
312 |
313 |
314 | 315 |

316 | 317 | fix: cross-env needs to prefix all individual commands 318 | 319 | #1 320 |

321 |
322 | 323 | 324 |
325 |
326 |
327 | 328 | Merged 329 |
330 |
331 |
332 | 335 | merged 1 commit into 336 | 337 | 338 | 339 | 340 | 341 | from 342 | 343 | 344 | 345 | 346 | Mar 9, 2016 347 | 348 |
349 |
350 |
351 | 352 | 353 | 354 |
355 |
356 | 357 | 358 | +2 359 | 360 | 361 | −2 362 | 363 | 364 | 365 | 366 | 367 |
368 | 369 | 398 |
399 | 400 | 401 | 402 |
403 |
404 |
408 | 409 | 422 | 423 | 436 | 437 | 450 | 451 |
452 |
453 | 454 |

455 | Projects 456 |

457 | 458 | 459 | 460 | None yet 461 | 462 | 463 |
464 | 465 | 476 | 477 | 478 |
479 |
480 | 481 |

482 | 2 participants 483 |

484 |
485 | @kentcdodds 486 | @mikechabot 487 |
488 |
489 |
490 | 491 | 492 | 493 | 494 |
495 | 496 | 497 |
498 | 499 |
500 |
501 | 502 | 503 |
504 | @kentcdodds 505 | 506 | 507 |
512 | 513 | 514 |
515 |
516 | 517 | 518 |
519 | 520 | 521 | 522 | Contributor 523 | 524 | 525 | 526 | 527 |
528 | 529 | 530 | kentcdodds 531 | 532 | 533 | commented 534 | 535 | Mar 8, 2016 536 | 537 |
538 |
539 | 540 | 541 |
542 | 543 | 544 | 545 | 550 | 551 | 552 |
546 |

The problem is that each command separated by && is actually totally isolated from one another, so cross-env can't do anything about other isolated commands. So you need to prefix each one.

547 |

This is noted in the docs.

548 |

I'm sorry it took so long to get back to you.

549 |
553 | 554 | 555 | 556 |
559 |
560 | 561 | 562 |
563 | 572 | 581 |
582 |
583 | 584 |
585 | 586 |
587 | 588 |
589 | 590 |
591 |
592 | 593 | 594 | 595 | 598 | 599 | 605 | 606 | 609 | 610 | 615 | 616 | 620 | 621 | 626 | 627 | 630 | 631 | 632 |
596 | 597 | 600 | 601 | @kentcdodds 602 | 603 | 604 | 607 | kentcdodds 608 | 611 | fix: cross-env needs to prefix all individual commands 612 | 613 | 614 | 617 | 618 | 619 | 622 | 623 | 624 | 625 | 628 | 8c08f73 629 |
633 |
634 |
635 | 636 | 637 |
638 |
639 | 640 | 641 | 642 | 643 | @kentcdodds 644 | kentcdodds 645 | 646 | 647 | referenced 648 | this pull request 649 | in kentcdodds/cross-env 650 | 651 | Mar 8, 2016 652 | 653 |
654 | 655 | 656 | 657 | Closed 658 | 659 | 660 | 661 | 662 |

663 | 664 | cross-env not working with webpack command? 665 | #13 666 |

667 | 668 |
669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 |
682 |
683 | 684 | 685 | 686 | 687 | 688 | @mikechabot 689 | mikechabot 690 | 691 | merged commit 2772e92 into mikechabot:master Mar 9, 2016 692 | 693 |
694 | 695 |
696 | 697 | 698 | 699 |
700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 |
713 | 714 |
715 |
716 | 717 | 718 |
719 | 720 |
721 |
722 | Sign up for free 723 | to join this conversation on GitHub. 724 | Already have an account? 725 | Sign in to comment 726 |
727 | 728 | 729 |
730 |
731 | 732 |
733 |
734 | 735 |
736 | 737 |
738 | 739 |
740 | 741 | 742 |
743 |
744 | 745 |
746 | 747 | 772 | 773 | 774 | 775 | 776 | 777 |
778 | 779 | 782 | You can't perform that action at this time. 783 |
784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 |
794 | 795 | You signed in with another tab or window. Reload to refresh your session. 796 | You signed out in another tab or window. Reload to refresh your session. 797 |
798 | 807 | 808 | 809 | 810 | 811 | 812 | -------------------------------------------------------------------------------- /test/fixtures/gist/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Creating a webextension generator · GitHub 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | Skip to content 99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 150 | 151 | 152 | 153 |
154 | 155 |
156 | 157 |
158 |
159 | 160 | 161 | 162 |
163 | 164 |
165 |
166 |
167 |
168 | Create a gist now 169 |

170 | Instantly share code, notes, and snippets. 171 |

172 |
173 |
174 | 175 | 176 |
177 |
178 | 179 | 180 |
181 | 182 | 206 | 207 |

208 | @HaNdTriX 209 | /README.md 212 | 213 |
Last active Feb 7, 2017
214 |

215 |
216 | 217 |
218 |
219 | 220 |
221 | 222 | 223 |
224 |
225 |
226 | 229 |
230 | 231 |
232 | 233 |
234 | 235 |
236 | 237 |
238 | 330 |
331 |
332 |
333 | 334 | 335 |
336 | 337 | 338 | 339 |
340 | 341 | 342 | 350 |
351 | 352 | 374 |
375 | 376 | 377 |
378 |
379 | 380 |
381 |
382 | 383 | 384 | 385 | 386 |
387 |
388 |
389 | Creating a webextension generator 390 |
391 |
392 | 393 | 394 |
395 |
396 |
397 |
398 | 399 | Raw 400 |
401 |
402 | 403 | 404 | 405 | 406 | 407 | README.md 408 | 409 | 410 |
411 |
412 | 413 |
414 |

Hi

415 | 416 |

I am Henrik and I want to create a webextension generator that supports chrome, firefox, opera and safari. The stack I would like to use will contain:

417 | 418 |
    419 |
  • yeoman for scaffolding
  • 420 |
  • gulp as a build system
  • 421 |
  • webpack for script compilation (modules, env variables, polyfills)
  • 422 |
  • npm as a module system
  • 423 |
  • and babel for ES2015
  • 424 |
425 | 426 |

I already created a generator for chrome but now I am thinking to move it into a new repository and add tasks for other browser vendors.

427 | 428 |

The idea behind it, is to stick to the chrome extension api and automatically shim the missing apis in other browsers via webpacks provide plugin.

429 | 430 |

Example webpack polyfill config

431 | 432 |
var webpackPlugins = [];
433 | 
434 | if (vendor === 'safari') {
435 |   // Automatically add polyfills for safari
436 |   plugins.push(
437 |     new webpack.ProvidePlugin({
438 |       'chrome.i18n': 'polyfill-chrome-api-safari/i18n',
439 |       'chrome.runtime': 'polyfill-chrome-api-safari/runtime',
440 |       'chrome.extension': 'polyfill-chrome-api-safari/extension',
441 |       'chrome.webrequest': 'polyfill-chrome-api-safari/webrequest',
442 |       'chrome.commands': 'polyfill-chrome-api-safari/webrequest',
443 |       ...
444 |     })
445 |   )
446 | } else if (vendor === 'mozilla') {
447 |   // Automatically add polyfills for safari
448 |   plugins.push(
449 |     new webpack.ProvidePlugin({
450 |       'chrome.commands': 'polyfill-chrome-api-firefox/commands',
451 |       ...
452 |     })
453 |   )
454 | }
455 | ....
456 | 
457 | 458 |

The workflow for the developer will be pretty nice, since he just has to develop his extension for https://developer.chrome.com/extensions/api_index. The build tool will shim the missing apis automatically (so far possible).

459 | 460 |

Example

461 | 462 |
...
463 | chrome.i18n.getMessage('someKey');
464 | ...
465 | 
466 | 467 |

if you later run

468 | 469 |
gulp build --vendor=safari
470 | 
471 | 472 |

webpack will detect chrome.i18n and include the polyfill.

473 | 474 |

What needs to be done?

475 | 476 | 480 | 481 |

What I already got

482 | 483 |

As I mentioned I created a generator for chrome that already supports:

484 | 485 | 495 | 496 |

All the logic is hackable so the developer is able to easily extend the stack if he needs to (e.g.: add postcss).

497 | 498 |

Feedback

499 | 500 |

I know this project seem big. There is a lot to do to implement but I think it is worth it.

501 | 502 |

The reason why I am writing this is to collect early feedback and find developers who might participate.

503 | 504 |
    505 |
  • Would you use such a generator.
  • 506 |
  • Do you think using polyfills is the right approach?
  • 507 |
  • Do you like it?
  • 508 |
509 | 510 |

Negative and positive Feedback welcome!

511 | 512 |

Thanks a lot

513 | 514 |

Henrik

515 |
516 |
517 | 518 |
519 | 520 |
521 | 522 | 523 | 524 |
525 |
526 | 527 | 528 |
529 | @Mte90 530 | 531 | 532 |
537 | 538 | 539 |
540 |
541 | 542 | 543 |
544 | 545 | 546 | 547 | 548 | 549 |
550 | 551 | 552 | Mte90 553 | 554 | 555 | commented 556 | 557 | Nov 8, 2015 558 | 559 |
560 |
561 | 562 | 563 |
564 | 565 | 566 | 567 | 570 | 571 | 572 |
568 |

There any news on that?

569 |
573 | 574 |
575 | 576 |
577 | 578 |
579 |
580 | @HaNdTriX 581 | 582 | 583 |
588 | 589 | 590 |
591 |
592 | 593 | 594 |
595 | 596 | 597 | 598 | Owner 599 | 600 | 601 | 602 | 603 |
604 | 605 | 606 | HaNdTriX 607 | 608 | 609 | commented 610 | 611 | Mar 22, 2016 612 | 613 |
614 |
615 | 616 | 617 |
618 | 619 | 620 | 621 | 629 | 630 | 631 |
622 |

Oh sorry for my late response.
623 | I don't have an eye on gists ;)

624 |

I haven't developed it, since there was not enough response from other developers.
625 | But I have updated https://github.com/HaNdTriX/generator-chrome-extension-kickstart.

626 |

It supports multible vendors, but it doesn't add any polyfills.
627 | You'll have to write them yourself.

628 |
632 | 633 |
634 | 635 |
636 | 637 |
638 | 639 | 640 | 641 | 642 |
647 |
648 | 649 | 650 |
651 |
652 | Sign up for free 653 | to join this conversation on GitHub. 654 | Already have an account? 655 | Sign in to comment 656 |
657 | 658 |
659 |
660 |
661 |
662 |
663 | 664 | 665 |
666 | 667 |
668 |
669 | 670 |
671 | 672 | 697 | 698 | 699 | 700 | 701 | 702 |
703 | 704 | 707 | You can't perform that action at this time. 708 |
709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 |
719 | 720 | You signed in with another tab or window. Reload to refresh your session. 721 | You signed out in another tab or window. Reload to refresh your session. 722 |
723 | 732 | 733 | 734 | 735 | 736 | 737 | -------------------------------------------------------------------------------- /test/fixtures/gist_comments/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | tcsh Git completion rules · GitHub 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 | Skip to content 99 |
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 150 | 151 | 152 | 153 |
154 | 155 |
156 | 157 |
158 |
159 | 160 | 161 | 162 |
163 | 164 |
165 |
166 |
167 |
168 | Create a gist now 169 |

170 | Instantly share code, notes, and snippets. 171 |

172 |
173 |
174 | 175 | 176 |
177 |
178 | 179 | 180 |
181 | 182 | 206 | 207 |

208 | @nicwolff 209 | /gist:1663989 212 | 213 |
Last active Sep 29, 2015
214 |

215 |
216 | 217 |
218 |
219 | 220 |
221 | 222 | 223 |
224 |
225 |
226 | 229 |
230 | 231 |
232 | 233 |
234 | 235 |
236 | 237 |
238 | 330 |
331 |
332 |
333 | 334 | 335 |
336 | 337 | 338 | 339 |
340 | 341 | 342 | 350 |
351 | 352 |
353 | 368 | 369 |
370 |
371 | 372 | 373 |
374 |
375 | 376 |
377 |
378 | 379 | 380 | 381 | 382 |
383 |
384 |
385 | tcsh Git completion rules 386 |
387 |
388 | 389 | 390 |
391 |
392 |
393 |
394 | 395 | Raw 396 |
397 |
398 | 399 | 400 | 401 | 402 | 403 | gistfile1.tcsh 404 | 405 | 406 |
407 |
408 | 409 | 410 |
411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 |
set complete = enhance
complete git "p/1/(commit branch remote merge pull push amend clone grep rebase reset bisect diff clone blame log checkout fetch status wdiff symbolic-ref reflog delete-merged)/" \
'n/br/`git branch|cut -c 3-|xargs echo --contains`/' 'N/br/`git branch|cut -c 3-`/' \
'n/cb/`git branch|cut -c 3-`/' \
'n/branch/`git branch|cut -c 3-`/' 'N/branch/`git branch|cut -c 3-`/' \
'n/co/`git branch|cut -c 3-`/' \
'N/symbolic-ref/`git branch|cut -c 3-`/' \
'n/merge/`git branch|cut -c 3-|xargs echo --no-ff --ff-only --squash`/' \
'N/merge/`git branch|cut -c 3-`/' \
'n/rebase/`git branch|cut -c 3-| xargs echo --continue --abort --onto --skip --interactive`/' \
'N/rebase/`git branch|cut -c 3-`/' \
'n/log/(--stat --author)/' \
'n/lh/(--stat --author)/' \
'n/commit/(--amend)/' \
'n/diff/(--cached --no-prefix --color-words --name-only)/' \
'n/pull/(--rebase --no-ff --squash)/' \
'n/fetch/`git remote`/' \
'n/reset/(HEAD^)/' \
'N/reset/(HEAD^)/' \
'n/stash/(apply list save pop clear)/' \
'n/push/`git remote`/' 'N/push/`git branch|cut -c 3-`/' \
'n/remote/(show add rm prune update)/' 'N/remote/`git remote`/'
501 | 502 |
503 | 504 |
505 | 506 |
507 | 508 | 509 | 510 |
511 |
512 | 513 | 514 |
515 | @marckhouzam 516 | 517 | 518 |
523 | 524 | 525 |
526 |
527 | 528 | 529 |
530 | 531 | 532 | 533 | 534 | 535 |
536 | 537 | 538 | marckhouzam 539 | 540 | 541 | commented 542 | 543 | Dec 5, 2012 544 | 545 |
546 |
547 | 548 | 549 |
550 | 551 | 552 | 553 | 561 | 562 | 563 |
554 |

Note that git 1.8.1 will have a first version of support for tcsh-completion as mentioned below:
555 | http://marc.info/?l=git&m=135456897811682&w=2

556 |

You can find the script, git-completion.tcsh, and the necessary accompanying bash script, git-completion.bash, here:
557 | https://github.com/git/git/tree/master/contrib/completion

558 |

Some improvements are still being worked on.

559 |

Marc

560 |
564 | 565 |
566 | 567 |
568 | 569 |
570 | 571 | 572 | 573 | 574 |
579 |
580 | 581 | 582 |
583 |
584 | Sign up for free 585 | to join this conversation on GitHub. 586 | Already have an account? 587 | Sign in to comment 588 |
589 | 590 |
591 |
592 |
593 |
594 |
595 | 596 | 597 |
598 | 599 |
600 |
601 | 602 |
603 | 604 | 629 | 630 | 631 | 632 | 633 | 634 |
635 | 636 | 639 | You can't perform that action at this time. 640 |
641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 |
651 | 652 | You signed in with another tab or window. Reload to refresh your session. 653 | You signed out in another tab or window. Reload to refresh your session. 654 |
655 | 664 | 665 | 666 | 667 | 668 | 669 | -------------------------------------------------------------------------------- /test/fixtures/markdown_blob/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | engine-api/README.md at 4290f40c056686fcaa5c9caf02eac1dde9315adf · docker/engine-api · GitHub 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 | Skip to content 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 150 | 151 | 152 |
153 | 154 |
155 | 156 |
157 |
158 | 159 | 160 | 161 |
162 |
163 |
164 | 165 | 166 | 167 | 168 |
169 |
170 | 171 | 172 | 216 | 217 |

218 | 219 | /engine-api 222 | 223 |

224 | 225 |
226 | 276 |
277 | 278 |
279 |
280 | 281 | 282 | 283 | Permalink 284 | 285 | 286 | 287 |
288 | 289 |
290 | 296 | 297 | 512 |
513 | 514 |
515 | 519 | Find file 520 | 521 | 522 |
523 | 526 |
527 | 528 | 529 |
530 | 531 | 532 | d1862da 533 | 534 | Sep 8, 2016 535 | 536 |
537 | @crosbymichael 538 | crosbymichael 539 | Add deprecation notice 542 |
543 | 544 |
545 | 549 | @calavera 550 | @crosbymichael 551 | 552 | 553 |
554 | 555 | 568 |
569 | 570 | 571 |
572 |
573 |
574 | 575 |
576 | Raw 577 | Blame 578 | History 579 |
580 | 581 | 583 | 584 | 585 | 586 | 590 | 594 |
595 | 596 |
597 | 18 lines (11 sloc) 598 | 599 | 738 Bytes 600 |
601 |
602 | 603 | 604 |
605 |

GoDoc

606 | 607 |

Deprecated

608 | 609 |

For new projects please do not use this project anymore. The Docker API client and types have been moved 610 | to the main docker repo under the following import paths:

611 | 612 | 616 | 617 |

All pull requests and issues should be filed under the docker/docker repository. 618 | This repo will not receive any future updates and you should update your existing import 619 | paths to the new package paths.

620 | 621 |

License

622 | 623 |

engine-api is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

624 |
625 |
626 | 627 |
628 | 629 | 630 | 635 | 636 |
637 | 638 |
639 | 640 | 641 |
642 |
643 | 644 |
645 | 646 | 671 | 672 | 673 | 674 | 675 | 676 |
677 | 678 | 681 | You can't perform that action at this time. 682 |
683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 |
693 | 694 | You signed in with another tab or window. Reload to refresh your session. 695 | You signed out in another tab or window. Reload to refresh your session. 696 |
697 | 706 | 707 | 708 | 709 | 710 | 711 | -------------------------------------------------------------------------------- /test/permalinker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable id-length, no-shadow */ 2 | import test from 'ava' 3 | import fse from 'fs-extra' 4 | import pify from 'pify' 5 | import got from 'got' 6 | import { JSDOM } from 'jsdom' 7 | import permalink from '../lib/permalinker' 8 | 9 | require('dotenv-safe').config() 10 | 11 | checkLink({ 12 | name: 'eslump readme', 13 | pageUrl: 'https://github.com/lydell/eslump#examples', 14 | archiveUrl: 'https://web.archive.org/web/20170413182903id_/https://github.com/lydell/eslump#examples', 15 | linkHref: 'https://github.com/prettier/prettier/', 16 | permalinkHref: 'https://github.com/prettier/prettier/tree/7702a2b1e30118471b00f7ba4ca4f47450c54ebb' 17 | }) 18 | 19 | checkLink({ 20 | name: 'milestones url', 21 | pageUrl: 'https://github.com/slevithan/xregexp/issues/108#issuecomment-293076389', 22 | archiveUrl: 'https://web.archive.org/web/20170410213100id_/https://github.com/slevithan/xregexp/issues/108#issuecomment-293076389', 23 | linkHref: 'https://github.com/slevithan/xregexp/milestones', 24 | permalinkHref: 'https://github.com/slevithan/xregexp/milestones/tree/a5e5f43a65c17547cb7be7c2859d1022f2bcbc7f', 25 | shouldPermalink: false 26 | }) 27 | 28 | checkLink({ 29 | name: 'search links', 30 | pageUrl: 'https://github.com/moll/vim-node/issues/24#issuecomment-132334681', 31 | archiveUrl: 'https://web.archive.org/web/20170322175057id_/https://github.com/moll/vim-node/issues/24#issuecomment-132334681', 32 | linkHref: 'https://github.com/moll/vim-node/search?utf8=%E2%9C%93&q=node_modules', 33 | permalinkHref: 'https://github.com/moll/vim-node/search/tree/e02ea6e4d9acab19fb7f598be08e5eec5d239999', 34 | shouldPermalink: false 35 | }) 36 | 37 | checkLink({ 38 | name: 'issue comment', 39 | pageUrl: 'https://github.com/isaacs/github/issues/625#issuecomment-203464167', 40 | archiveUrl: 'https://web.archive.org/web/20170310233459id_/https://github.com/isaacs/github/issues/625', 41 | linkHref: 'https://github.com/andrewthad/yesod-table/blob/master/src/Yesod/Table.hs', 42 | permalinkHref: 'https://github.com/andrewthad/yesod-table/blob/57a2b5b385612d67f76f19d5c6164e182dee4fcf/src/Yesod/Table.hs' 43 | }) 44 | 45 | checkLink({ 46 | name: 'PR review comment', 47 | pageUrl: 'https://github.com/thoughtbot/dotfiles/pull/513#discussion_r101161729', 48 | archiveUrl: 'https://web.archive.org/web/20170310233633id_/https://github.com/thoughtbot/dotfiles/pull/513', 49 | linkHref: 'https://github.com/thoughtbot/dotfiles/blob/master/rcrc#L2', 50 | permalinkHref: 'https://github.com/thoughtbot/dotfiles/blob/2fa36ccf9dab57597dc3296381474641a5cbd813/rcrc#L2' 51 | }) 52 | 53 | checkLink({ 54 | name: 'already permalink', 55 | pageUrl: 'https://github.com/OctoLinker/browser-extension/issues/113#issue-163053327', 56 | archiveUrl: 'https://web.archive.org/web/20170310233609id_/https://github.com/OctoLinker/browser-extension/issues/113', 57 | linkHref: 'https://github.com/OctoLinker/browser-extension/blob/71ae1d68526919bee2c6e8339a1cd87b5febff11/lib/click-handler.js#L54-L61', 58 | permalinkHref: 'https://github.com/OctoLinker/browser-extension/blob/71ae1d68526919bee2c6e8339a1cd87b5febff11/lib/click-handler.js#L54-L61' 59 | }) 60 | 61 | checkLink({ 62 | name: 'gist', 63 | pageUrl: 'https://gist.github.com/HaNdTriX/77916f3fcdd7d285f7c9#what-i-already-got', 64 | archiveUrl: 'https://web.archive.org/web/20170310233604id_/https://gist.github.com/HaNdTriX/77916f3fcdd7d285f7c9', 65 | linkHref: 'https://github.com/HaNdTriX/generator-chrome-extension-kickstart/blob/master/app/templates/tasks/compress.js', 66 | permalinkHref: 'https://github.com/HaNdTriX/generator-chrome-extension-kickstart/blob/780d02c18433983d8dd13ebc0b2d3ef9b7beaf06/app/templates/tasks/compress.js' 67 | }) 68 | 69 | checkLink({ 70 | name: 'markdown blob', 71 | pageUrl: 'https://github.com/docker/engine-api/blob/4290f40c056686fcaa5c9caf02eac1dde9315adf/README.md#deprecated', 72 | archiveUrl: 'https://web.archive.org/web/20170310233559id_/https://github.com/docker/engine-api/blob/4290f40c056686fcaa5c9caf02eac1dde9315adf/README.md', 73 | linkHref: 'https://github.com/docker/docker/tree/master/client', 74 | permalinkHref: 'https://github.com/docker/docker/tree/fdce2a7775ec80d769f585c0a400c6cf6615776b/client' 75 | }) 76 | 77 | checkLink({ 78 | name: 'markdown internal link', 79 | pageUrl: 'https://github.com/docker/engine-api/tree/4290f40c056686fcaa5c9caf02eac1dde9315adf', 80 | archiveUrl: 'https://web.archive.org/web/20170310233555id_/https://github.com/docker/engine-api/tree/4290f40c056686fcaa5c9caf02eac1dde9315adf', 81 | linkHref: '#deprecated', 82 | permalinkHref: '#deprecated' 83 | }) 84 | 85 | checkLink({ 86 | name: 'badge', 87 | pageUrl: 'https://github.com/kentcdodds/cross-env/tree/b59473a62777c6e03c3fe60e685a20df8e63c3f9#cross-env', 88 | archiveUrl: 'https://web.archive.org/web/20170310233550id_/https://github.com/kentcdodds/cross-env/tree/b59473a62777c6e03c3fe60e685a20df8e63c3f9', 89 | linkHref: 'https://github.com/kentcdodds/cross-env/blob/master/other/LICENSE', 90 | permalinkHref: 'https://github.com/kentcdodds/cross-env/blob/b59473a62777c6e03c3fe60e685a20df8e63c3f9/other/LICENSE', 91 | shouldPermalink: false 92 | }) 93 | 94 | checkLink({ 95 | name: 'fall back to master branch', 96 | pageUrl: 'https://github.com/mikechabot/cross-env-example/pull/1#issue-139341424', 97 | archiveUrl: 'https://web.archive.org/web/20170310233547id_/https://github.com/mikechabot/cross-env-example/pull/1', 98 | linkHref: 'https://github.com/kentcdodds/cross-env#known-limitations', 99 | permalinkHref: 'https://github.com/kentcdodds/cross-env/tree/20d35cd6c16961c7205273b7214c3c6de0ed5497#known-limitations' 100 | }) 101 | 102 | checkLink({ 103 | name: 'do not permalink issue comment self-links', 104 | pageUrl: 'https://github.com/mikechabot/cross-env-example/pull/1#issue-139341424', 105 | archiveUrl: 'https://web.archive.org/web/20170310233547id_/https://github.com/mikechabot/cross-env-example/pull/1', 106 | linkHref: '#issue-139341424', 107 | permalinkHref: 'https://github.com/mikechabot/cross-env-example/pull/1/tree/8c08f73f4b7faf0911320da5ec06f918651740f8#issue-139341424', 108 | shouldPermalink: false 109 | }) 110 | 111 | checkLink({ 112 | name: 'gist comments', 113 | pageUrl: 'https://gist.github.com/nicwolff/1663989/f7e7761a1cad124d5aa5fdf17be43b4249e5b529#gistcomment-616611', 114 | archiveUrl: 'https://web.archive.org/web/20170310233547id_/https://gist.github.com/nicwolff/1663989/f7e7761a1cad124d5aa5fdf17be43b4249e5b529', 115 | linkHref: 'https://github.com/git/git/tree/master/contrib/completion', 116 | permalinkHref: 'https://github.com/git/git/tree/fb4c62235fee8008d99ef55c4adcb1f7ea9508a3/contrib/completion' 117 | }) 118 | 119 | function checkLink ({ name, pageUrl, archiveUrl, linkHref, permalinkHref, shouldPermalink = true }) { 120 | test(`${name}: ${pageUrl}`, async (t) => { 121 | const fixturePath = `${__dirname}/fixtures/${name.replace(/ /g, '_')}/page.html` 122 | if (!fse.existsSync(fixturePath)) { 123 | const { body } = await got(archiveUrl) 124 | await pify(fse.outputFile)(fixturePath, body) 125 | } 126 | const { window: { document } } = await JSDOM.fromFile(fixturePath) 127 | await permalink({ token: process.env.GITHUB_TOKEN }, document) 128 | const fragileLink = document.querySelector(`[href="${linkHref}"]`) 129 | if (shouldPermalink && permalinkHref !== linkHref) { 130 | // a permalink should have been inserted 131 | t.is(fragileLink.title, linkHref, 'set fragile link title') 132 | t.is(fragileLink.nextSibling.textContent.length, 2, 'text separator present') // can't seem to check for ' (' 133 | t.is(fragileLink.nextElementSibling.href, permalinkHref, 'href is permalink') 134 | t.is(fragileLink.nextElementSibling.title, permalinkHref, 'title is permalink') 135 | t.is(fragileLink.nextElementSibling.lastChild.title, permalinkHref, 'title is permalink') 136 | } else if (fragileLink.nextElementSibling) { 137 | // a permalink should not have been inserted 138 | t.falsy(fragileLink.nextElementSibling.href === permalinkHref, `${fragileLink.nextElementSibling.href} === ${permalinkHref}`) 139 | } else { 140 | t.pass('a permalink should not have been inserted, and there was no next link') 141 | } 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /test/user-repo-branch.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable id-length, no-shadow */ 2 | import test from 'ava' 3 | import userRepoBranch from '../lib/user-repo-branch' 4 | 5 | test('https://github.com/OctoLinker/browser-extension/compare/v4.10.0...v4.11.0', (t) => { 6 | t.deepEqual(userRepoBranch('https://github.com/OctoLinker/browser-extension/compare/v4.10.0...v4.11.0'), { 7 | user: 'OctoLinker', 8 | repo: 'browser-extension' 9 | }) 10 | }) 11 | 12 | test('https://github.com/prettier/prettier/pulls?q=is%3Apr+is%3Aclosed', (t) => { 13 | t.deepEqual(userRepoBranch('https://github.com/prettier/prettier/pulls?q=is%3Apr+is%3Aclosed'), { 14 | user: 'prettier', 15 | repo: 'prettier' 16 | }) 17 | }) 18 | 19 | test('https://github.com/prettier/prettier/', (t) => { 20 | t.deepEqual(userRepoBranch('https://github.com/prettier/prettier/'), { 21 | user: 'prettier', 22 | repo: 'prettier', 23 | branch: 'master', 24 | href: 'https://github.com/prettier/prettier/tree/master' 25 | }) 26 | }) 27 | 28 | test('https://github.com/akhodakivskiy/VimFx', (t) => { 29 | t.deepEqual(userRepoBranch('https://github.com/akhodakivskiy/VimFx'), { 30 | user: 'akhodakivskiy', 31 | repo: 'VimFx', 32 | branch: 'master', 33 | href: 'https://github.com/akhodakivskiy/VimFx/tree/master' 34 | }) 35 | }) 36 | 37 | test('https://github.com/sindresorhus/pageres/commit/663be15acb3dd2eb0f71b1956ef28c2cd3fdeed0', (t) => { 38 | t.deepEqual(userRepoBranch('https://github.com/sindresorhus/pageres/commit/663be15acb3dd2eb0f71b1956ef28c2cd3fdeed0'), { 39 | user: 'sindresorhus', 40 | repo: 'pageres' 41 | }) 42 | }) 43 | 44 | test('https://github.com/mozilla/web-ext/releases/new', (t) => { 45 | t.deepEqual(userRepoBranch('https://github.com/mozilla/web-ext/releases/new'), { 46 | user: 'mozilla', 47 | repo: 'web-ext' 48 | }) 49 | }) 50 | 51 | test('https://github.com/mozilla/web-ext/issues?utf8=%E2%9C%93&q=is%3Aclosed%20label%3A%22needs%3A%20docs%22%20', (t) => { 52 | t.deepEqual(userRepoBranch('https://github.com/mozilla/web-ext/issues?utf8=%E2%9C%93&q=is%3Aclosed%20label%3A%22needs%3A%20docs%22%20'), { 53 | user: 'mozilla', 54 | repo: 'web-ext' 55 | }) 56 | }) 57 | 58 | test('https://github.com/x0rz/EQGRP/edit/master/README.md', (t) => { 59 | t.deepEqual(userRepoBranch('https://github.com/x0rz/EQGRP/edit/master/README.md'), { 60 | user: 'x0rz', 61 | repo: 'EQGRP' 62 | }) 63 | }) 64 | 65 | test('https://github.com/gorhill/uBlock/graphs/contributors', (t) => { 66 | t.deepEqual(userRepoBranch('https://github.com/gorhill/uBlock/graphs/contributors'), { 67 | user: 'gorhill', 68 | repo: 'uBlock' 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CopyWebpackPlugin = require('copy-webpack-plugin'); // eslint-disable-line 3 | 4 | module.exports = { 5 | entry: { 6 | app: './lib/app', 7 | options: './lib/options/page' 8 | }, 9 | devtool: 'source-map', 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: '[name].js' 13 | }, 14 | plugins: [ 15 | new CopyWebpackPlugin([ 16 | { from: 'assets' } 17 | ], { 18 | ignore: ['manifest.json'] 19 | }) 20 | ], 21 | module: { 22 | loaders: [ 23 | { 24 | test: /\.js$/, 25 | exclude: /(node_modules)/, 26 | loader: 'babel-loader' 27 | } 28 | ] 29 | } 30 | } 31 | --------------------------------------------------------------------------------