├── .editorconfig ├── .eslintignore ├── .gitattributes ├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── eslint.config.js ├── jest.config.js ├── package.json ├── prettier.config.js ├── specs ├── __mocks__ │ ├── 1.json │ ├── 2.json │ ├── 3.json │ └── request-promise.js ├── __snapshots__ │ └── index.specs.js.snap ├── gatsby-ssr.specs.js └── index.specs.js ├── src ├── gatsby-ssr.js └── index.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{ts,tsx,js,jsx,json}] 11 | charset = utf-8 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [.*{ignore,attributes}] 17 | insert_final_newline = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm 2 | dist 3 | build 4 | coverage 5 | node_modules 6 | __mocks__ 7 | __snapshots__ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.gif binary 4 | *.icns binary 5 | *.ico binary 6 | *.png binary 7 | *.woff2 binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | logs 26 | *.log 27 | npm-debug.log* 28 | .bak 29 | .sql 30 | .sqlite 31 | 32 | # OS generated files # 33 | ###################### 34 | .DS_Store 35 | .DS_Store? 36 | ._* 37 | .Spotlight-V100 38 | .Trashes 39 | ehthumbs.db 40 | Thumbs.db 41 | 42 | # Text editor files # 43 | ##################### 44 | .idea 45 | .classpath 46 | .project 47 | .settings 48 | .metadata 49 | .vscode 50 | .ipr 51 | 52 | # Package Managers # 53 | ############## 54 | node_modules 55 | bower_components 56 | jspm_packages 57 | pids 58 | .npm 59 | .node_repl_history 60 | .pid 61 | .seed 62 | 63 | # Frameworks # 64 | ############## 65 | .coveralls.yml 66 | .nyc_output 67 | .grunt 68 | .lock-wscript 69 | 70 | # Folders # 71 | ########### 72 | dist 73 | build 74 | lib-cov 75 | coverage 76 | 77 | # Build # 78 | ######### 79 | /index.js 80 | /gatsby-ssr.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !gatsby-ssr.js 3 | !index.js 4 | !package.json 5 | !README.md 6 | !LICENSE -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "12" 5 | 6 | sudo: false 7 | 8 | install: 9 | - yarn install 10 | - yarn global add codecov 11 | 12 | script: 13 | - yarn run test:coverage 14 | - codecov 15 | 16 | cache: 17 | yarn: true 18 | directories: 19 | - node_modules 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.2.1](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.2.0...v1.2.1) (2020-08-25) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * broken gist with files starting with . ([#38](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/38)) ([a3855e5](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/a3855e52709760edd71646c501f366559549eba2)) 11 | * default options not properly merged ([#34](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/34)) ([3764d6c](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/3764d6cf5d7889d720808b1fcc94646ba136a5a6)) 12 | 13 | ## [1.2.0](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.10...v1.2.0) (2020-06-28) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * issues with async load) ([#31](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/31)) ([339d817](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/339d81706545e582f19fb80328691b85cbab08b4)) 19 | 20 | ### [1.1.10](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.9...v1.1.10) (2020-06-26) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * **tests:** fixes broken tests ([#28](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/28)) ([c086c3a](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/c086c3a)) 26 | 27 | 28 | 29 | ### [1.1.9](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.8...v1.1.9) (2019-07-22) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * **babel:** babel export default module ([#19](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/19)) ([b67460f](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/b67460f)) 35 | 36 | 37 | 38 | ### [1.1.8](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.7...v1.1.8) (2019-07-05) 39 | 40 | 41 | ### Build System 42 | 43 | * update travis configuration ([#17](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/17)) ([81c5a5f](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/81c5a5f)) 44 | 45 | 46 | 47 | 48 | ## [1.1.7](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.6...v1.1.7) (2019-03-13) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * **index:** trim the output html to fix support in mdx ([#13](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/13)) ([39d982d](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/39d982d)) 54 | 55 | 56 | 57 | 58 | ## [1.1.6](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.5...v1.1.6) (2019-02-17) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * **gatsby-ssr:** fix URL of embedded default CSS ([#8](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/8)) ([dad5464](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/dad5464)) 64 | 65 | 66 | 67 | 68 | ## [1.1.5](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.4...v1.1.5) (2019-01-16) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * **gatsby-ssr:** fixes missing key warning in react ([#6](https://github.com/weirdpattern/gatsby-remark-embed-gist/issues/6)) ([1220f17](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/1220f17)) 74 | 75 | 76 | 77 | 78 | ## [1.1.4](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.3...v1.1.4) (2018-12-04) 79 | 80 | 81 | 82 | 83 | ## [1.1.3](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.2...v1.1.3) (2018-10-08) 84 | 85 | 86 | 87 | 88 | ## [1.1.2](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.1...v1.1.2) (2018-07-20) 89 | 90 | 91 | 92 | 93 | ## [1.1.1](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.1.0...v1.1.1) (2018-07-20) 94 | 95 | 96 | 97 | 98 | # [1.1.0](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.0.1...v1.1.0) (2018-07-20) 99 | 100 | 101 | ### Features 102 | 103 | * Add support for querystring-like in 'gist:' directives ([2ceb562](https://github.com/weirdpattern/gatsby-remark-embed-gist/commit/2ceb562)) 104 | 105 | 106 | 107 | 108 | ## [1.0.1](https://github.com/weirdpattern/gatsby-remark-embed-gist/compare/v1.0.0...v1.0.1) (2018-07-04) 109 | 110 | 111 | 112 | 113 | # 1.0.0 (2018-07-01) 114 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 Patricio Trevino 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 | # gatsby-remark-embed-gist 2 | 3 | [![NPM badge](https://img.shields.io/npm/v/gatsby-remark-embed-gist.svg?style=flat-square)](https://www.npmjs.com/package/gatsby-remark-embed-gist) 4 | [![Travis badge](https://img.shields.io/travis/weirdpattern/gatsby-remark-embed-gist.svg?branch=master&style=flat-square)](https://travis-ci.org/weirdpattern/gatsby-remark-embed-gist) 5 | [![codecov](https://codecov.io/gh/weirdpattern/gatsby-remark-embed-gist/branch/master/graph/badge.svg)](https://codecov.io/gh/weirdpattern/gatsby-remark-embed-gist) 6 | 7 | This plugin allows content authors to embed [Gist](https://gist.github.com/) 8 | snippets. 9 | 10 | ## Getting started 11 | 12 | To embed a Gist snippet in you markdown/remark content, simply add an inline code 13 | block using the `gist:` protocol. 14 | 15 | ```md 16 | `gist:[/]#` 17 | `gist:[/]#?lines=` 18 | `gist:[/]#?highlights=` 19 | `gist:[/]#?highlights=&lines=` 20 | `gist:[/]?file=` 21 | `gist:[/]?file=?lines=` 22 | `gist:[/]?file=?highlights=` 23 | `gist:[/]?file=?highlights=&lines=` 24 | ``` 25 | 26 | Where: 27 | 28 | - **username**, represents the github handler whose gist is to be accessed. 29 | Can be defaulted via configuration. 30 | - **gist_id**, is the id of the gist to be accessed. 31 | This is the hash value in the gist url, e.g. https://gist.github.com//`ce54fdb1e5621b5966e146026995b974`). 32 | - **gist_file**, is the name of the file in the gist to be accessed. 33 | 34 | Highlights and lines can be defined using: 35 | 36 | - A single number (e.g. `highlights=1`, `lines=1`) 37 | - A list of numbers (e.g. `highlights=1,4`, `lines=1,4`) 38 | - A range of numbers (e.g. `highlights=1-4`, `lines=1-4`) 39 | - A combination of all above (e.g. `highlights=1,3,7-9`, `lines=1,3,7-9`) 40 | 41 | ## Installation 42 | 43 | `yarn add gatsby-remark-embed-gist` 44 | 45 | ## Usage 46 | 47 | ```javascript 48 | // In your gatsby-config.js 49 | { 50 | resolve: "gatsby-transformer-remark", 51 | options: { 52 | plugins: [ 53 | { 54 | resolve: "gatsby-remark-embed-gist", 55 | options: { 56 | // Optional: 57 | 58 | // the github handler whose gists are to be accessed 59 | username: "", 60 | 61 | // a flag indicating whether the github default gist css should be included or not 62 | // default: true 63 | // DEPRECATED (PLEASE USE gistDefaultCssInclude) 64 | includeDefaultCss: true || false, 65 | 66 | // a flag indicating whether the github default gist css should be included or not 67 | // default: true 68 | gistDefaultCssInclude: true || false, 69 | 70 | // a flag indicating whether the github default gist css should be preloaded or not 71 | // use this if you want to load the default css asynchronously. 72 | // default: false 73 | gistCssPreload: true || false, 74 | 75 | // a string that represents the github default gist css url. 76 | // defaults: "https://github.githubassets.com/assets/gist-embed-b3b573358bfc66d89e1e95dbf8319c09.css" 77 | gistCssUrlAddress: "" 78 | } 79 | } 80 | ] 81 | } 82 | } 83 | ``` 84 | 85 | ## Example 86 | 87 | Turns this... 88 | 89 | ```md 90 | `gist:weirdpattern/ce54fdb1e5621b5966e146026995b974#syntax.text` 91 | ``` 92 | 93 | Into this... 94 | 95 | ```html 96 |
97 |
98 |
99 |
102 |
103 |
104 | 108 | 109 | 110 | 115 | 121 | 122 | 123 |
119 | <operation> [n]> /dev/null [options] 120 |
124 |
125 |
126 |
127 |
128 |
129 | view raw 134 | syntax.text 138 | hosted with ❤ by GitHub 139 |
140 |
141 |
142 | ``` 143 | 144 | ## Notes 145 | 146 | The order of the plugins only matters when used in conjunction with 147 | `gatsby-remark-prismjs`, because this plugin transforms the inline code blocks, 148 | so add `gatsby-remark-embed-gist` somewhere above this plugin. 149 | 150 | ## License 151 | 152 | MIT, by Patricio Trevino 153 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api, options, cwd) => { 2 | api.assertVersion(7); 3 | 4 | let caller = ""; 5 | 6 | api.caller((instance) => { 7 | if (instance != null) { 8 | caller = instance.name; 9 | } 10 | 11 | return true; 12 | }); 13 | 14 | const env = api.env(); 15 | api.cache.using(() => 16 | JSON.stringify({ 17 | env, 18 | caller, 19 | cwd, 20 | options, 21 | NODE_ENV: process.env.NODE_ENV, 22 | BABEL_ENV: process.env.BABEL_ENV 23 | }) 24 | ); 25 | 26 | return { 27 | presets: [ 28 | [ 29 | "@babel/preset-env", 30 | { 31 | forceAllTransforms: true, 32 | targets: { 33 | node: true 34 | } 35 | } 36 | ], 37 | "@babel/preset-react" 38 | ], 39 | plugins: [ 40 | "add-module-exports", 41 | "@babel/plugin-transform-runtime", 42 | "@babel/plugin-proposal-object-rest-spread" 43 | ] 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const prettier = require("./prettier.config"); 2 | 3 | module.exports = { 4 | parser: "babel-eslint", 5 | parserOptions: { 6 | sourceType: "module" 7 | }, 8 | extends: ["eslint:recommended", "plugin:react/recommended", "prettier"], 9 | plugins: ["react", "jest", "json", "prettier", "filenames"], 10 | env: { 11 | es6: true, 12 | node: true, 13 | jest: true 14 | }, 15 | rules: { 16 | "semi": "error", 17 | "require-jsdoc": "error", 18 | "valid-jsdoc": [ 19 | "error", 20 | { 21 | preferType: { 22 | any: "*", 23 | Boolean: "boolean", 24 | Number: "number", 25 | Object: "object", 26 | String: "string", 27 | return: "returns" 28 | }, 29 | requireReturnType: true, 30 | requireParamDescription: true, 31 | requireReturnDescription: true 32 | } 33 | ], 34 | "quotes": [ 35 | "error", 36 | "double", 37 | { 38 | avoidEscape: true 39 | } 40 | ], 41 | "filenames/no-index": "off", 42 | "filenames/match-exported": ["error", [null, "kebab", "camel"]], 43 | "prettier/prettier": ["error", prettier] 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ["js", "jsx"], 3 | testMatch: ["**/specs/**/*[.-][Ss]pec{,s}.{j}s"], 4 | rootDir: ".", 5 | verbose: false, 6 | resetMocks: true, 7 | resetModules: true, 8 | collectCoverageFrom: [ 9 | "src/*.js", 10 | "!**/*.min.js", 11 | "!**/static/**", 12 | "!**/specs/**", 13 | "!**/node_modules/**" 14 | ] 15 | }; 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "MIT", 3 | "version": "1.2.1", 4 | "name": "gatsby-remark-embed-gist", 5 | "description": "Gatsby remark gists preprocessor", 6 | "author": { 7 | "name": "Patricio Trevino", 8 | "email": "patricio@weirdpattern.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/weirdpattern/gatsby-remark-embed-gist" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/weirdpattern/gatsby-remark-embed-gist/issues" 16 | }, 17 | "main": "index.js", 18 | "keywords": [ 19 | "gatsby", 20 | "gatsbyjs", 21 | "gatsby-plugin", 22 | "gatsby-remark", 23 | "remark", 24 | "gist", 25 | "embedded", 26 | "plugin" 27 | ], 28 | "scripts": { 29 | "build": "rimraf gatsby-ssr.js index.js && cross-env BABEL_ENV=production babel src --out-dir .", 30 | "clean": "npx rimraf node_modules", 31 | "format": "prettier --write ./{src,specs}/{,**}/*.js", 32 | "lint": "eslint ./{src,specs}/{,**}/*.js --config eslint.config.js", 33 | "test": "jest", 34 | "test:verbose": "jest --verbose", 35 | "test:coverage": "jest --coverage --runInBand --no-cache", 36 | "release": "yarn run build && ./node_modules/.bin/standard-version" 37 | }, 38 | "devDependencies": { 39 | "@babel/cli": "^7.10.5", 40 | "@babel/core": "^7.11.4", 41 | "@babel/preset-env": "^7.11.0", 42 | "@babel/preset-react": "^7.10.4", 43 | "@babel/plugin-proposal-object-rest-spread": "^7.11.0", 44 | "@babel/plugin-transform-modules-commonjs": "^7.10.4", 45 | "@babel/plugin-transform-runtime": "^7.11.0", 46 | "babel-eslint": "^10.1.0", 47 | "babel-plugin-add-module-exports": "^1.0.2", 48 | "cross-env": "^7.0.2", 49 | "eslint": "^7.7.0", 50 | "eslint-config-prettier": "^6.11.0", 51 | "eslint-plugin-filenames": "^1.3.2", 52 | "eslint-plugin-jest": "^23.20.0", 53 | "eslint-plugin-json": "^2.1.2", 54 | "eslint-plugin-prettier": "^3.1.4", 55 | "eslint-plugin-react": "^7.20.6", 56 | "husky": "^4.2.5", 57 | "jest": "^26.4.2", 58 | "lint-staged": "^10.2.12", 59 | "prettier": "^2.1.0", 60 | "react": "^16.13.1", 61 | "remark": "^12.0.1", 62 | "rimraf": "^3.0.2", 63 | "standard-version": "^9.0.0" 64 | }, 65 | "dependencies": { 66 | "@babel/runtime": "^7.11.2", 67 | "async-unist-util-visit": "^1.0.0", 68 | "cheerio": "^1.0.0-rc.3", 69 | "parse-numeric-range": "^1.2.0", 70 | "node-fetch": "^2.6.0" 71 | }, 72 | "peerDependencies": { 73 | "gatsby": "*", 74 | "gatsby-transformer-remark": "*", 75 | "react": "*" 76 | }, 77 | "husky": { 78 | "hooks": { 79 | "pre-commit": "lint-staged && npm test" 80 | } 81 | }, 82 | "lint-staged": { 83 | "*.js{,x}": [ 84 | "prettier --write", 85 | "git add" 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: false, 7 | quoteProps: "consistent", 8 | trailingComma: "none", 9 | bracketSpacing: true, 10 | arrowParens: "always", 11 | insertPragma: false, 12 | endOfLine: "lf", 13 | proseWrap: "preserve", 14 | htmlWhitespaceSensitivity: "css", 15 | jsxSingleQuote: false, 16 | jsxBracketSameLine: false 17 | }; 18 | -------------------------------------------------------------------------------- /specs/__mocks__/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "2016.08.31.stream-redirection", 3 | "public": true, 4 | "created_at": "2018-06-28T21:56:26.000-05:00", 5 | "files": ["example.sh", "syntax.text"], 6 | "owner": "weirdpattern", 7 | "div": "
\n
\n
\n
\n
\n \n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
# no redirection
$ echo 'hello'
hello
# redirects standard error (no error in this case)
$ echo 'hello' 2> /dev/null
hello
# redirects standard out to /dev/null (so no output)
$ echo 'hello' 1> /dev/null
# redirects everything to /dev/null (so no output)
$ echo 'hello' &> /dev/null
\n
# no redirection (causing error)
$ unlink unexisting-file.sh
unlink: unexisting-file.sh: No such file or directory
\n
# redirects standard error to /dev/null (so no error)
$ unlink unexisting-file.sh 2> /dev/null
\n
# redirects everything to /dev/null (so no error)
$ unlink unexisting-file.sh &> /dev/null
\n\n\n
\n\n
\n
\n\n
\n
\n view raw\n example.sh\n hosted with ❤ by GitHub\n
\n
\n
\n
\n
\n
\n \n\n
\n \n \n \n \n \n
<operation> [n]> /dev/null [options]
\n\n\n
\n\n
\n
\n\n
\n
\n view raw\n syntax.text\n hosted with ❤ by GitHub\n
\n
\n
\n", 8 | "stylesheet": "https://assets-cdn.github.com/assets/gist-embed-87673c31a5b37b5e6556b63e1081ebbc.css" 9 | } 10 | -------------------------------------------------------------------------------- /specs/__mocks__/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "2016.08.31.stream-redirection", 3 | "public": true, 4 | "created_at": "2018-06-28T21:56:26.000-05:00", 5 | "files": ["syntax.text"], 6 | "owner": "weirdpattern", 7 | "div": "
\n
\n
\n
\n
\n \n\n
\n \n \n \n \n \n
<operation> [n]> /dev/null [options]
\n\n\n
\n\n
\n
\n\n
\n
\n view raw\n syntax.text\n hosted with ❤ by GitHub\n
\n
\n
\n", 8 | "stylesheet": "https://assets-cdn.github.com/assets/gist-embed-87673c31a5b37b5e6556b63e1081ebbc.css" 9 | } 10 | -------------------------------------------------------------------------------- /specs/__mocks__/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "description":".2016.08.31.stream-redirection", 3 | "public":true, 4 | "created_at":"2018-06-28T21:56:26.000-05:00", 5 | "files":[".2016.08.31.stream-redirection","example.sh","syntax.text"], 6 | "owner":"weirdpattern", 7 | "div":"
\n
\n
\n
\n
\n \n\n
\n \n \n \n \n \n
2016.08.31.stream-redirection
\n\n\n
\n\n
\n
\n\n
\n
\n view raw\n .2016.08.31.stream-redirection\n hosted with ❤ by GitHub\n
\n
\n
\n
\n
\n
\n \n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
# no redirection
$ echo 'hello'
hello
# redirects standard error (no error in this case)
$ echo 'hello' 2> /dev/null
hello
# redirects standard out to /dev/null (so no output)
$ echo 'hello' 1> /dev/null
# redirects everything to /dev/null (so no output)
$ echo 'hello' &> /dev/null
\n
# no redirection (causing error)
$ unlink unexisting-file.sh
unlink: unexisting-file.sh: No such file or directory
\n
# redirects standard error to /dev/null (so no error)
$ unlink unexisting-file.sh 2> /dev/null
\n
# redirects everything to /dev/null (so no error)
$ unlink unexisting-file.sh &> /dev/null
\n\n\n
\n\n
\n
\n\n
\n
\n view raw\n example.sh\n hosted with ❤ by GitHub\n
\n
\n
\n
\n
\n
\n \n\n
\n \n \n \n \n \n
<operation> [n]> /dev/null [options]
\n\n\n
\n\n
\n
\n\n
\n
\n view raw\n syntax.text\n hosted with ❤ by GitHub\n
\n
\n
\n", 8 | "stylesheet":"https://assets-cdn.github.com/assets/gist-embed-87673c31a5b37b5e6556b63e1081ebbc.css" 9 | } 10 | -------------------------------------------------------------------------------- /specs/__mocks__/request-promise.js: -------------------------------------------------------------------------------- 1 | export default url => { 2 | if ( 3 | url === 4 | "https://gist.github.com/weirdpattern/ce54fdb1e5621b5966e146026995b974.json" 5 | ) { 6 | return JSON.stringify(require("./1.json")); 7 | } else if ( 8 | url === 9 | "https://gist.github.com/weirdpattern/ce54fdb1e5621b5966e146026995b974.json?file=syntax.text" 10 | ) { 11 | return JSON.stringify(require("./2.json")); 12 | } else if ( 13 | url === 14 | "https://gist.github.com/weirdpattern/ce54fdb1e5621b5966e146026995b974.json?file=example.sh" 15 | ) { 16 | return JSON.stringify(require("./3.json")); 17 | } 18 | return false; 19 | }; 20 | -------------------------------------------------------------------------------- /specs/gatsby-ssr.specs.js: -------------------------------------------------------------------------------- 1 | import { onRenderBody } from "../src/gatsby-ssr"; 2 | 3 | const setHeadComponents = jest.fn(); 4 | 5 | describe("gatsby-ssr", () => { 6 | beforeEach(() => { 7 | setHeadComponents.mockReset(); 8 | }); 9 | 10 | it("executes when includeDefaultCss is default", () => { 11 | onRenderBody({ setHeadComponents }, { plugins: [] }); 12 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 13 | }); 14 | 15 | it("executes when gistDefaultCssInclude is default", () => { 16 | onRenderBody({ setHeadComponents }, { plugins: [] }); 17 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 18 | }); 19 | 20 | it("executes when includeDefaultCss is true", () => { 21 | onRenderBody( 22 | { setHeadComponents }, 23 | { plugins: [], includeDefaultCss: true } 24 | ); 25 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 26 | }); 27 | 28 | it("executes when gistDefaultCssInclude is true", () => { 29 | onRenderBody( 30 | { setHeadComponents }, 31 | { plugins: [], gistDefaultCssInclude: true } 32 | ); 33 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 34 | }); 35 | 36 | it("doesn't execute when includeDefaultCss is false", () => { 37 | onRenderBody( 38 | { setHeadComponents }, 39 | { plugins: [], includeDefaultCss: false } 40 | ); 41 | expect(setHeadComponents).toHaveBeenCalledTimes(0); 42 | }); 43 | 44 | it("doesn't execute when gistDefaultCssInclude is false", () => { 45 | onRenderBody( 46 | { setHeadComponents }, 47 | { plugins: [], gistDefaultCssInclude: false } 48 | ); 49 | expect(setHeadComponents).toHaveBeenCalledTimes(0); 50 | }); 51 | 52 | it("executes when gistCssPreload is missing", () => { 53 | onRenderBody({ setHeadComponents }, { plugins: [] }); 54 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 55 | }); 56 | 57 | it("executes when gistCssPreload is false", () => { 58 | onRenderBody({ setHeadComponents }, { plugins: [], gistCssPreload: false }); 59 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 60 | }); 61 | 62 | it("executes when gistCssPreload is true", () => { 63 | onRenderBody({ setHeadComponents }, { plugins: [], gistCssPreload: true }); 64 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 65 | expect(setHeadComponents.mock.calls[0][0].length).toBe(3); 66 | }); 67 | 68 | it("executes when gistCssPreload is true", () => { 69 | onRenderBody({ setHeadComponents }, { plugins: [], gistCssPreload: true }); 70 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 71 | expect(setHeadComponents.mock.calls[0][0].length).toBe(3); 72 | }); 73 | 74 | it("updates the url when one is provided", () => { 75 | onRenderBody( 76 | { setHeadComponents }, 77 | { plugins: [], gistCssPreload: true, gistCssUrlAddress: "https://test" } 78 | ); 79 | expect(setHeadComponents).toHaveBeenCalledTimes(1); 80 | 81 | const value = setHeadComponents.mock.calls[0][0][0]; 82 | expect(value.props.href === "https://test").toBeTruthy(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /specs/index.specs.js: -------------------------------------------------------------------------------- 1 | jest.mock("request-promise"); 2 | 3 | import remark from "remark"; 4 | import plugin from "../src/index"; 5 | 6 | const getNodeContent = (node) => node.children[0].children[0]; 7 | 8 | describe("gatsby-remark-embedded-gist", () => { 9 | it("generates an embedded gist with username", async () => { 10 | const markdownAST = remark.parse( 11 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974`" 12 | ); 13 | 14 | const processed = await plugin({ markdownAST }); 15 | expect(processed).toBeTruthy(); 16 | 17 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 18 | }); 19 | 20 | it("generates an embedded gist with username and file", async () => { 21 | const markdownAST = remark.parse( 22 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974#syntax.text`" 23 | ); 24 | 25 | const processed = await plugin({ markdownAST }); 26 | expect(processed).toBeTruthy(); 27 | 28 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 29 | }); 30 | 31 | it("generates an embedded gist with username and file in query with .", async () => { 32 | const markdownAST = remark.parse( 33 | "`gist:weirdpattern/0831fd65d3f8ee6ab11a23dff65abc68?file=.inputrc`" 34 | ); 35 | 36 | const processed = await plugin({ markdownAST }); 37 | expect(processed).toBeTruthy(); 38 | 39 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 40 | }); 41 | 42 | it("generates an embedded gist with username and file in query with . and a single line highlighted", async () => { 43 | const markdownAST = remark.parse( 44 | "`gist:weirdpattern/0831fd65d3f8ee6ab11a23dff65abc68?file=.inputrc&highlights=7`" 45 | ); 46 | 47 | const processed = await plugin({ markdownAST }); 48 | expect(processed).toBeTruthy(); 49 | 50 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 51 | }); 52 | 53 | it("generates an embedded gist with username and file in query with . and multiple line highlighted", async () => { 54 | const markdownAST = remark.parse( 55 | "`gist:weirdpattern/0831fd65d3f8ee6ab11a23dff65abc68?file=.inputrc&highlights=7,9`" 56 | ); 57 | 58 | const processed = await plugin({ markdownAST }); 59 | expect(processed).toBeTruthy(); 60 | 61 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 62 | }); 63 | 64 | it("generates an embedded gist with username and file in query with . a single line highlighted and multiple line selected", async () => { 65 | const markdownAST = remark.parse( 66 | "`gist:weirdpattern/0831fd65d3f8ee6ab11a23dff65abc68?file=.inputrc&highlights=7&lines=6-8`" 67 | ); 68 | 69 | const processed = await plugin({ markdownAST }); 70 | expect(processed).toBeTruthy(); 71 | 72 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 73 | }); 74 | 75 | it("generates an embedded gist with username and file in query", async () => { 76 | const markdownAST = remark.parse( 77 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=syntax.text`" 78 | ); 79 | 80 | const processed = await plugin({ markdownAST }); 81 | expect(processed).toBeTruthy(); 82 | 83 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 84 | }); 85 | 86 | it("generates an embedded gist with username and file in query with single line highlighted", async () => { 87 | const markdownAST = remark.parse( 88 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1`" 89 | ); 90 | 91 | const processed = await plugin({ markdownAST }); 92 | expect(processed).toBeTruthy(); 93 | 94 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 95 | }); 96 | 97 | it("generates an embedded gist with username and file in query with multiple lines highlighted", async () => { 98 | const markdownAST = remark.parse( 99 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1,3,5,7`" 100 | ); 101 | 102 | const processed = await plugin({ markdownAST }); 103 | expect(processed).toBeTruthy(); 104 | 105 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 106 | }); 107 | 108 | it("generates an embedded gist with username and file in query with range of lines highlighted", async () => { 109 | const markdownAST = remark.parse( 110 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1-7`" 111 | ); 112 | 113 | const processed = await plugin({ markdownAST }); 114 | expect(processed).toBeTruthy(); 115 | 116 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 117 | }); 118 | 119 | it("generates an embedded gist with username and file in query with mixed lines highlighted", async () => { 120 | const markdownAST = remark.parse( 121 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1,3,4-7`" 122 | ); 123 | 124 | const processed = await plugin({ markdownAST }); 125 | expect(processed).toBeTruthy(); 126 | 127 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 128 | }); 129 | 130 | it("generates an embedded gist with username and file in query with single line highlighted and single line selected", async () => { 131 | const markdownAST = remark.parse( 132 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1&lines=1`" 133 | ); 134 | 135 | const processed = await plugin({ markdownAST }); 136 | expect(processed).toBeTruthy(); 137 | 138 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 139 | }); 140 | 141 | it("generates an embedded gist with username and file in query with multiple lines highlighted and multiple lines selected", async () => { 142 | const markdownAST = remark.parse( 143 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1,3,5&lines=1,2,3,5,6,7`" 144 | ); 145 | 146 | const processed = await plugin({ markdownAST }); 147 | expect(processed).toBeTruthy(); 148 | 149 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 150 | }); 151 | 152 | it("generates an embedded gist with username and file in query with range of lines highlighted and selected", async () => { 153 | const markdownAST = remark.parse( 154 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1-5&lines=1-6`" 155 | ); 156 | 157 | const processed = await plugin({ markdownAST }); 158 | expect(processed).toBeTruthy(); 159 | 160 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 161 | }); 162 | 163 | it("generates an embedded gist with username and file in query with mixed lines highlighted and mixed lines selected", async () => { 164 | const markdownAST = remark.parse( 165 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1-3,4,6&lines=1-6`" 166 | ); 167 | 168 | const processed = await plugin({ markdownAST }); 169 | expect(processed).toBeTruthy(); 170 | 171 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 172 | }); 173 | 174 | it("generates an embedded gist with default username", async () => { 175 | const markdownAST = remark.parse("`gist:ce54fdb1e5621b5966e146026995b974`"); 176 | 177 | const processed = await plugin( 178 | { markdownAST }, 179 | { username: "weirdpattern" } 180 | ); 181 | expect(processed).toBeTruthy(); 182 | 183 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 184 | }); 185 | 186 | it("generates an embedded gist with default username and file", async () => { 187 | const markdownAST = remark.parse("`gist:ce54fdb1e5621b5966e146026995b974`"); 188 | 189 | const processed = await plugin( 190 | { markdownAST }, 191 | { username: "weirdpattern" } 192 | ); 193 | expect(processed).toBeTruthy(); 194 | 195 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 196 | }); 197 | 198 | it("generates an embedded gist with default username and file in query", async () => { 199 | const markdownAST = remark.parse( 200 | "`gist:ce54fdb1e5621b5966e146026995b974?file=syntax.text`" 201 | ); 202 | 203 | const processed = await plugin( 204 | { markdownAST }, 205 | { username: "weirdpattern" } 206 | ); 207 | expect(processed).toBeTruthy(); 208 | 209 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 210 | }); 211 | 212 | it("generates an embedded gist with default username and file in query with single line highlighted", async () => { 213 | const markdownAST = remark.parse( 214 | "`gist:ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=2`" 215 | ); 216 | 217 | const processed = await plugin( 218 | { markdownAST }, 219 | { username: "weirdpattern" } 220 | ); 221 | expect(processed).toBeTruthy(); 222 | 223 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 224 | }); 225 | 226 | it("generates an embedded gist with default username and file in query with muplie lines highlighted", async () => { 227 | const markdownAST = remark.parse( 228 | "`gist:ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1,2,8`" 229 | ); 230 | 231 | const processed = await plugin( 232 | { markdownAST }, 233 | { username: "weirdpattern" } 234 | ); 235 | expect(processed).toBeTruthy(); 236 | 237 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 238 | }); 239 | 240 | it("generates an embedded gist with default username and file in query with range of lines highlighted", async () => { 241 | const markdownAST = remark.parse( 242 | "`gist:ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=2-6`" 243 | ); 244 | 245 | const processed = await plugin( 246 | { markdownAST }, 247 | { username: "weirdpattern" } 248 | ); 249 | expect(processed).toBeTruthy(); 250 | 251 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 252 | }); 253 | 254 | it("generates an embedded gist with default username and file in query with mixed lines highlighted", async () => { 255 | const markdownAST = remark.parse( 256 | "`gist:ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=2,8-10,12-14`" 257 | ); 258 | 259 | const processed = await plugin( 260 | { markdownAST }, 261 | { username: "weirdpattern" } 262 | ); 263 | expect(processed).toBeTruthy(); 264 | 265 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 266 | }); 267 | 268 | it("generates an embedded gist with default username and file in query with muplie lines highlighted and multiple lines selected", async () => { 269 | const markdownAST = remark.parse( 270 | "`gist:ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1,2,8&lines=1-5,8`" 271 | ); 272 | 273 | const processed = await plugin( 274 | { markdownAST }, 275 | { username: "weirdpattern" } 276 | ); 277 | expect(processed).toBeTruthy(); 278 | 279 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 280 | }); 281 | 282 | it("generates an embedded gist with default username and file in query with range of lines highlighted and range of lines selected", async () => { 283 | const markdownAST = remark.parse( 284 | "`gist:ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=2-6&lines=2-7`" 285 | ); 286 | 287 | const processed = await plugin( 288 | { markdownAST }, 289 | { username: "weirdpattern" } 290 | ); 291 | expect(processed).toBeTruthy(); 292 | 293 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 294 | }); 295 | 296 | it("generates an embedded gist with default username and file in query with mixed lines highlighted and selected", async () => { 297 | const markdownAST = remark.parse( 298 | "`gist:ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=2,8-10,12-14&lines=1-10,12-16`" 299 | ); 300 | 301 | const processed = await plugin( 302 | { markdownAST }, 303 | { username: "weirdpattern" } 304 | ); 305 | expect(processed).toBeTruthy(); 306 | 307 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 308 | }); 309 | 310 | it("generates an embedded gist with #file and ?querystring", async () => { 311 | const markdownAST = remark.parse( 312 | "`gist:ce54fdb1e5621b5966e146026995b974#example.sh?highlights=2,8-10,12-14&lines=1-10,12-16`" 313 | ); 314 | 315 | const processed = await plugin( 316 | { markdownAST }, 317 | { username: "weirdpattern" } 318 | ); 319 | expect(processed).toBeTruthy(); 320 | 321 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 322 | }); 323 | 324 | it("generates an embedded gist with #file and ?querystring just hightlights", async () => { 325 | const markdownAST = remark.parse( 326 | "`gist:ce54fdb1e5621b5966e146026995b974#example.sh?highlights=2,8-10,12-14`" 327 | ); 328 | 329 | const processed = await plugin( 330 | { markdownAST }, 331 | { username: "weirdpattern" } 332 | ); 333 | expect(processed).toBeTruthy(); 334 | 335 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 336 | }); 337 | 338 | it("generates an embedded gist with #file and ?querystring just lines", async () => { 339 | const markdownAST = remark.parse( 340 | "`gist:ce54fdb1e5621b5966e146026995b974#example.sh?lines=1-10,12-16`" 341 | ); 342 | 343 | const processed = await plugin( 344 | { markdownAST }, 345 | { username: "weirdpattern" } 346 | ); 347 | expect(processed).toBeTruthy(); 348 | 349 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 350 | }); 351 | 352 | it("inline username overrides configuration", async () => { 353 | const markdownAST = remark.parse( 354 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974`" 355 | ); 356 | 357 | const processed = await plugin({ markdownAST }, { username: "john" }); 358 | expect(processed).toBeTruthy(); 359 | 360 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 361 | }); 362 | 363 | it("inline username overrides configuration with file", async () => { 364 | const markdownAST = remark.parse( 365 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974#syntax.text`" 366 | ); 367 | 368 | const processed = await plugin({ markdownAST }, { username: "john" }); 369 | expect(processed).toBeTruthy(); 370 | 371 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 372 | }); 373 | 374 | it("inline username overrides configuration with file in query", async () => { 375 | const markdownAST = remark.parse( 376 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=syntax.text`" 377 | ); 378 | 379 | const processed = await plugin({ markdownAST }, { username: "john" }); 380 | expect(processed).toBeTruthy(); 381 | 382 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 383 | }); 384 | 385 | it("inline username overrides configuration with file in query with single line highlighted", async () => { 386 | const markdownAST = remark.parse( 387 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=5`" 388 | ); 389 | 390 | const processed = await plugin({ markdownAST }, { username: "john" }); 391 | expect(processed).toBeTruthy(); 392 | 393 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 394 | }); 395 | 396 | it("inline username overrides configuration with file in query with multiple lines highlighted", async () => { 397 | const markdownAST = remark.parse( 398 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=5,11`" 399 | ); 400 | 401 | const processed = await plugin({ markdownAST }, { username: "john" }); 402 | expect(processed).toBeTruthy(); 403 | 404 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 405 | }); 406 | 407 | it("inline username overrides configuration with file in query with range of lines highlighted", async () => { 408 | const markdownAST = remark.parse( 409 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=5-10`" 410 | ); 411 | 412 | const processed = await plugin({ markdownAST }, { username: "john" }); 413 | expect(processed).toBeTruthy(); 414 | 415 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 416 | }); 417 | 418 | it("inline username overrides configuration with file in query with mixed lines highlighted", async () => { 419 | const markdownAST = remark.parse( 420 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1-3,5,10-13`" 421 | ); 422 | 423 | const processed = await plugin({ markdownAST }, { username: "john" }); 424 | expect(processed).toBeTruthy(); 425 | 426 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 427 | }); 428 | 429 | it("inline username overrides configuration with file in query with single line highlighted and single line selected", async () => { 430 | const markdownAST = remark.parse( 431 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=5&lines=5`" 432 | ); 433 | 434 | const processed = await plugin({ markdownAST }, { username: "john" }); 435 | expect(processed).toBeTruthy(); 436 | 437 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 438 | }); 439 | 440 | it("inline username overrides configuration with file in query with multiple lines highlighted and multiple lines selected", async () => { 441 | const markdownAST = remark.parse( 442 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=5,11&lines=1,5,6,10,11`" 443 | ); 444 | 445 | const processed = await plugin({ markdownAST }, { username: "john" }); 446 | expect(processed).toBeTruthy(); 447 | 448 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 449 | }); 450 | 451 | it("inline username overrides configuration with file in query with range of lines highlighted and range of lines selected", async () => { 452 | const markdownAST = remark.parse( 453 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=5-10&lines=4-15`" 454 | ); 455 | 456 | const processed = await plugin({ markdownAST }, { username: "john" }); 457 | expect(processed).toBeTruthy(); 458 | 459 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 460 | }); 461 | 462 | it("inline username overrides configuration with file in query with mixed lines highlighted and selected", async () => { 463 | const markdownAST = remark.parse( 464 | "`gist:weirdpattern/ce54fdb1e5621b5966e146026995b974?file=example.sh&highlights=1-3,5,10-13&lines=1-6,10,11,12,13`" 465 | ); 466 | 467 | const processed = await plugin({ markdownAST }, { username: "john" }); 468 | expect(processed).toBeTruthy(); 469 | 470 | expect(getNodeContent(markdownAST)).toMatchSnapshot(); 471 | }); 472 | 473 | it("fails when no username is provided", async () => { 474 | const markdownAST = remark.parse("`gist:ce54fdb1e5621b5966e146026995b974`"); 475 | 476 | await expect(plugin({ markdownAST })).rejects.toEqual( 477 | new Error("Missing username information") 478 | ); 479 | }); 480 | 481 | it("fails when no id is provided", async () => { 482 | const markdownAST = remark.parse("`gist:weirdpattern/#syntax.text`"); 483 | await expect(plugin({ markdownAST })).rejects.toEqual( 484 | new Error("Missing gist id information") 485 | ); 486 | }); 487 | 488 | it("fails when no id is provided and file is provided in query", async () => { 489 | const markdownAST = remark.parse("`gist:weirdpattern/?file=syntax.text`"); 490 | await expect(plugin({ markdownAST })).rejects.toEqual( 491 | new Error("Missing gist id information") 492 | ); 493 | }); 494 | 495 | it("fails with malformed queries", async () => { 496 | const markdownAST = remark.parse("`gist:weirdpattern/?syntax.text`"); 497 | await expect(plugin({ markdownAST })).rejects.toEqual( 498 | new Error("Malformed query. Check your 'gist:' imports") 499 | ); 500 | }); 501 | 502 | it("fails with queries that don't contain a file or highlights", async () => { 503 | const markdownAST = remark.parse("`gist:weirdpattern/?other=test`"); 504 | await expect(plugin({ markdownAST })).rejects.toEqual( 505 | new Error("Malformed query. Check your 'gist:' imports") 506 | ); 507 | }); 508 | 509 | it("ignores everything that is not inline code", async () => { 510 | const markdownAST = remark.parse("# Syntax"); 511 | const originalMarkdownAST = markdownAST; 512 | 513 | const processed = await plugin({ markdownAST }); 514 | expect(processed).toBeTruthy(); 515 | 516 | expect(markdownAST).toBe(originalMarkdownAST); 517 | }); 518 | }); 519 | -------------------------------------------------------------------------------- /src/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | /** 4 | * @typedef {object} PluginOptions 5 | * @property {string} username the default gist user. 6 | * @property {boolean} includeDefaultCss a flag indicating the default css should be included 7 | */ 8 | 9 | /** 10 | * Adds a link to the Github Gist default css. 11 | * @param {{ setHeadComponents }} setHeadComponents adds components to . 12 | * @param {PluginOptions} options the options of the plugin. 13 | * @returns {*} rendered body. 14 | */ 15 | export function onRenderBody({ setHeadComponents }, options = {}) { 16 | options = { 17 | ...{ 18 | gistCssPreload: false, 19 | gistCssUrlAddress: 20 | "https://github.githubassets.com/assets/gist-embed-b3b573358bfc66d89e1e95dbf8319c09.css" 21 | }, 22 | ...options 23 | }; 24 | 25 | let includeCss = true; 26 | if (options.gistDefaultCssInclude != null) { 27 | includeCss = options.gistDefaultCssInclude; 28 | } else if (options.includeDefaultCss != null) { 29 | includeCss = options.includeDefaultCss; 30 | } 31 | 32 | const key = "gist-embed-b3b573358bfc66d89e1e95dbf8319c09"; 33 | 34 | if (includeCss) { 35 | setHeadComponents( 36 | options.gistCssPreload 37 | ? [ 38 | , 45 | , 48 | 58 | ] 59 | : [] 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import querystring from "querystring"; 2 | 3 | import cheerio from "cheerio"; 4 | import parse from "parse-numeric-range"; 5 | import fetch from "node-fetch"; 6 | import visit from "async-unist-util-visit"; 7 | 8 | // default base url 9 | const baseUrl = "https://gist.github.com"; 10 | 11 | /** 12 | * @typedef {object} PluginOptions 13 | * @property {string} username the default gist user. 14 | * @property {boolean} includeDefaultCss a flag indicating the default css should be included 15 | */ 16 | 17 | /** 18 | * @typedef {object} GistQuery 19 | * @property {string} file the file name. 20 | * @property {string|Array} highlights the numbers to be highlighted. 21 | */ 22 | 23 | /** 24 | * Validates the query object is valid. 25 | * @param {GistQuery} query the query to be validated. 26 | * @returns {boolean} true if the query is valid; false otherwise. 27 | */ 28 | function isValid(query) { 29 | if (query == null) return false; 30 | if (query.file == null && query.highlights == null && query.lines == null) { 31 | return false; 32 | } 33 | 34 | // leaving this for future enhancements to the query object 35 | 36 | return true; 37 | } 38 | 39 | /** 40 | * Builds the query object. 41 | * This methods looks for anything that is after ? or # in the gist: directive. 42 | * ? is interpreted as a query string. 43 | * # is interpreted as a filename. 44 | * @param {string} value the value of the inlineCode block. 45 | * @returns {object} the query object. 46 | */ 47 | function getQuery(value) { 48 | const hash = value.includes("#"); 49 | 50 | // this will give us 51 | // a) qs with length 0 - no file, no querystring 52 | // b) qs with length 1 - either a #file or a ?querystring 53 | // c) qs with length 2 - a #file and a ?querystring 54 | // d) qs with length > 2 - malformed 55 | const [, ...qs] = value.split(/[?#]/); 56 | 57 | // a) and d) are easy 58 | if (qs.length === 0) return { highlights: [], lines: [] }; 59 | if (qs.length > 2) { 60 | throw new Error("Malformed query. Check your 'gist:' imports"); 61 | } 62 | 63 | let query; 64 | 65 | // b) is tricky, could be a #file or a ?querystring 66 | if (qs.length === 1) { 67 | if (hash) { 68 | query = { file: qs[0] }; 69 | } else if (qs[0].includes("=")) { 70 | query = querystring.parse(qs[0]); 71 | } else { 72 | throw new Error("Malformed query. Check your 'gist:' imports"); 73 | } 74 | } else { 75 | query = { file: qs[0], ...querystring.parse(qs[1]) }; 76 | } 77 | 78 | // at this point we have an object such as 79 | // { 80 | // file?: string, 81 | // highlights?: string || string[], 82 | // lines?: string || string[] 83 | // } 84 | // so we validate 85 | if (!isValid(query)) { 86 | throw new Error("Malformed query. Check your 'gist:' imports"); 87 | } 88 | 89 | // get the range of highlights to render 90 | let highlights = []; 91 | if (typeof query.highlights === "string") { 92 | highlights = parse(query.highlights); 93 | } else if (Array.isArray(query.highlights)) { 94 | highlights = query.highlights; 95 | } 96 | query.highlights = highlights; 97 | 98 | // get the range of lines to display 99 | let lines = []; 100 | if (typeof query.lines === "string") { 101 | lines = parse(query.lines); 102 | } else if (Array.isArray(query.lines)) { 103 | lines = query.lines; 104 | } 105 | query.lines = lines; 106 | 107 | return query; 108 | } 109 | 110 | /** 111 | * Builds the gist url. 112 | * @param {string} value the value of the inlineCode block. 113 | * @param {PluginOptions} options the options of the plugin. 114 | * @param {string} file the file to be loaded. 115 | * @returns {string} the gist url. 116 | */ 117 | function buildUrl(value, options, file) { 118 | const [gist] = value.split(/[?#]/); 119 | 120 | const [inlineUsername, id] = 121 | gist.indexOf("/") > 0 ? gist.split("/") : [null, gist]; 122 | 123 | // username can come from inline code or options 124 | const username = inlineUsername || options.username; 125 | 126 | // checks for a valid username 127 | if (username == null || username.trim().length === 0) { 128 | throw new Error("Missing username information"); 129 | } 130 | 131 | // checks for a valid id 132 | if (id == null || id.trim().length === 0) { 133 | throw new Error("Missing gist id information"); 134 | } 135 | 136 | // builds the url and completes it with the file if any 137 | let url = `${baseUrl}/${username}/${id}.json`; 138 | if (file != null) { 139 | url += `?file=${file}`; 140 | } 141 | 142 | return url; 143 | } 144 | 145 | /** 146 | * Handles the markdown AST. 147 | * @param {{ markdownAST }} markdownAST the markdown abstract syntax tree. 148 | * @param {PluginOptions} options the options of the plugin. 149 | * @returns {*} the markdown ast. 150 | */ 151 | export default async ({ markdownAST }, options = {}) => { 152 | // this returns a promise that will fulfill immediately for everything 153 | // that is not an inlineCode that starts with `gist:` 154 | return await visit(markdownAST, "inlineCode", async (node) => { 155 | // validate pre-requisites. 156 | if (!node.value.startsWith("gist:")) return; 157 | 158 | // get the query string and build the url 159 | const query = getQuery(node.value.substring(5)); 160 | const url = buildUrl(node.value.substring(5), options, query.file); 161 | 162 | // call the gist and update the node type and value 163 | const response = await fetch(url); 164 | const content = await response.json(); 165 | 166 | let html = content.div; 167 | const hasLines = query.lines.length > 0; 168 | const hasHighlights = query.highlights.length > 0; 169 | 170 | if (hasHighlights || hasLines) { 171 | const $ = cheerio.load(html); 172 | const file = query.file 173 | ? query.file 174 | .replace(/^\./, "") 175 | .replace(/[^a-zA-Z0-9_]+/g, "-") 176 | .toLowerCase() 177 | : ""; 178 | 179 | // highlight the specify lines, if any 180 | if (hasHighlights) { 181 | query.highlights.forEach((line) => { 182 | $(`#file-${file}-LC${line}`).addClass("highlighted"); 183 | }); 184 | } 185 | 186 | // remove the specific lines, if any 187 | if (hasLines) { 188 | const codeLines = parse(`1-${$("table tr").length}`); 189 | codeLines.forEach((line) => { 190 | if (query.lines.includes(line)) return; 191 | 192 | $(`#file-${file}-LC${line}`).parent().remove(); 193 | }); 194 | } 195 | 196 | html = $.html(); 197 | } 198 | 199 | node = Object.assign(node, { 200 | type: "html", 201 | value: html.trim() 202 | }); 203 | 204 | return markdownAST; 205 | }); 206 | }; 207 | --------------------------------------------------------------------------------