├── .gitignore
├── LICENSE
├── README.md
├── gatsby-node.js
├── package-lock.json
├── package.json
├── src
├── index.ts
├── on-create-node.ts
└── utils.ts
├── tsconfig.json
└── types.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # dotenv environment variable files
55 | .env*
56 | .vscode
57 |
58 | # gatsby files
59 | .cache/
60 | public
61 |
62 | # Mac files
63 | .DS_Store
64 |
65 | # Build directory
66 | dist/
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The BSD Zero Clause License (0BSD)
2 |
3 | Copyright (c) 2020 Gatsby Inc.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 | PERFORMANCE OF THIS SOFTWARE.
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🛠 Hello, I need a maintainer 👩💻 🧑💻 🛠
2 |
3 | As you can probably see, I have not maintained, or used this package (or Gatsby for that matter) in a number of years. Frankly, I'm surprised this package still needs to exist. I have no plans to continue working on this so if you would like to take over this repo please let me know.
4 |
5 | # gatsby-remark-relative-images
6 |
7 | Convert image src(s) in markdown/html/frontmatter to be relative to their node's parent directory. This will help [gatsby-remark-images](https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-images) match images outside the node folder. This was built for use with NetlifyCMS and should be considered a temporary solution until relative paths are supported. If it works for other use cases then great!
8 |
9 | ### Features
10 |
11 | - [x] Converts markdown/mdx images
12 | - [x] Converts `src` in markdown/mdx html `
` tags
13 | - [x] Converts frontmatter fields, supports nested fields
14 | - [x] Suports Unicode characters
15 | - [x] Frontmatter field filters (include/exclude)
16 |
17 | ## v2 Breaking Changes:
18 |
19 | The `fmImagesToRelative()` function has been removed, it is no longer needed.
20 |
21 | NOTE: v2 greatly simplifies things and seems to work well for my use-case (NetlifyCMS), if you were previously using this plugin for something else that no longer works with v2, please open an issue and let me know and I will try to accomodate your use-case. Thanks.
22 |
23 | ## Install
24 |
25 | ```bash
26 | # Install v2 (Recommended)
27 | yarn add gatsby-remark-relative-images
28 |
29 | # Install v1 (TS refactor, but quickly found more things to simplfy, skip)
30 | npm i gatsby-remark-relative-images@1.1.1
31 |
32 | # Install original (a bit hacky but have previously worked for most)
33 | npm i gatsby-remark-relative-images@0.3.0
34 | npm i gatsby-remark-relative-images@0.2.0
35 | ```
36 |
37 | ## Usage Example
38 |
39 | This usage example is for v2 of this plugin.
40 |
41 | /gatsby-config.js
42 |
43 | ```javascript
44 | module.exports = {
45 | plugins: [
46 | // Add static assets before markdown files
47 | {
48 | resolve: 'gatsby-source-filesystem',
49 | options: {
50 | path: `${__dirname}/static/uploads`,
51 | name: 'uploads',
52 | },
53 | },
54 | {
55 | resolve: 'gatsby-source-filesystem',
56 | options: {
57 | path: `${__dirname}/src/pages`,
58 | name: 'pages',
59 | },
60 | },
61 | {
62 | resolve: `gatsby-transformer-remark`,
63 | options: {
64 | plugins: [
65 | // gatsby-remark-relative-images must go before gatsby-remark-images
66 | {
67 | resolve: `gatsby-remark-relative-images`,
68 | options: {
69 | // [Optional] The root of "media_folder" in your config.yml
70 | // Defaults to "static"
71 | staticFolderName: 'static',
72 | // [Optional] Include the following fields, use dot notation for nested fields
73 | // All fields are included by default
74 | include: ['featured'],
75 | // [Optional] Exclude the following fields, use dot notation for nested fields
76 | // No fields are excluded by default
77 | exclude: ['featured.skip'],
78 | },
79 | },
80 | {
81 | resolve: `gatsby-remark-images`,
82 | options: { maxWidth: 1024 },
83 | },
84 | ],
85 | },
86 | },
87 | ],
88 | };
89 | ```
90 |
91 | /static/admin/config.yml
92 |
93 | ```yml
94 | # ...
95 | media_folder: static/img
96 | public_folder: /img
97 | # ...
98 | ```
99 |
100 | /src/pages/blog-post.md
101 |
102 | ```md
103 | ---
104 | templateKey: blog-post
105 | title: A beginners’ guide to brewing with Chemex
106 | date: 2017-01-04T15:04:10.000Z
107 | featured: { image: /img/chémex.jpg, skip: /img/chémex.jpg }
108 |
109 | description: Brewing with a Chemex probably seems like a complicated, time-consuming ordeal, but once you get used to the process, it becomes a soothing ritual that's worth the effort every time.
110 | ---
111 |
112 | 
113 |
114 |
115 |
116 | This week we’ll **take** a look at all the steps required to make astonishing coffee with a Chemex at home. The Chemex Coffeemaker is a manual, pour-over style glass-container coffeemaker that Peter Schlumbohm invented in 1941, and which continues to be manufactured by the Chemex Corporation in Chicopee, Massachusetts.
117 |
118 | In 1958, designers at the [Illinois Institute of Technology](https://www.spacefarm.digital) said that the Chemex Coffeemaker is _"one of the best-designed products of modern times"_, and so is included in the collection of the Museum of Modern Art in New York City.
119 |
120 | ## The little secrets of Chemex brewing
121 |
122 |
123 |
124 |
125 | The Chemex Coffeemaker consists of an hourglass-shaped glass flask with a conical funnel-like neck (rather than the cylindrical neck of an Erlenmeyer flask) and uses proprietary filters, made of bonded paper (thicker-gauge paper than the standard paper filters for a drip-method coffeemaker) that removes most of the coffee oils, brewing coffee with a taste that is different than coffee brewed in other coffee-making systems; also, the thicker paper of the Chemex coffee filters may assist in removing cafestol, a cholesterol-containing compound found in coffee oils.
126 | ```
127 |
128 | ## FAQs
129 |
130 | ### I'm getting the error: Field "image" must not have a selection since type "String" has no subfields
131 |
132 | This is a common error when working with Netlify CMS (see issue [gatsby/gatsby#5990](https://github.com/gatsbyjs/gatsby/issues/5990)).
133 |
134 | The application must include the `media` with `gatsby-source-filesystem` to include all the uploaded media and to make it available on build time. **Note:** The media folder must be included **before** the other content.
135 |
136 | For example, an application that is using NetlifyCMS and this plugin, and has a content folder with markdown that comes from Netlify. Here's how the `gatsby-config.js` should look like:
137 |
138 | ```js
139 | module.exports = {
140 | plugins: [
141 | {
142 | resolve: `gatsby-source-filesystem`,
143 | options: {
144 | path: `${__dirname}/static/assets`,
145 | name: 'assets',
146 | },
147 | },
148 | {
149 | resolve: `gatsby-source-filesystem`,
150 | options: {
151 | path: `${__dirname}/src/content`,
152 | name: 'content',
153 | },
154 | },
155 | `gatsby-transformer-sharp`,
156 | `gatsby-plugin-sharp`,
157 | {
158 | resolve: `gatsby-transformer-remark`,
159 | options: {
160 | plugins: [
161 | `gatsby-remark-relative-images`,
162 | {
163 | resolve: `gatsby-remark-images`,
164 | options: {},
165 | },
166 | ],
167 | },
168 | },
169 | `gatsby-plugin-netlify-cms`,
170 | ],
171 | };
172 | ```
173 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | exports.onCreateNode = require('./dist/on-create-node').onCreateNode;
2 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-remark-relative-images",
3 | "version": "2.0.3",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "gatsby-remark-relative-images",
9 | "version": "2.0.3",
10 | "license": "0BSD",
11 | "dependencies": {
12 | "cheerio": "^1.0.0-rc.12",
13 | "is-invalid-path": "^1.0.2",
14 | "lodash": "^4.17.21",
15 | "traverse": "^0.6.6",
16 | "unist-util-select": "^3.0.4"
17 | },
18 | "devDependencies": {
19 | "@types/cheerio": "^0.22.21",
20 | "@types/lodash": "^4.14.160",
21 | "@types/node": "^14.6.0",
22 | "@types/slash": "^3.0.0",
23 | "@types/traverse": "^0.6.32",
24 | "@types/unist": "^2.0.3",
25 | "rimraf": "^3.0.2",
26 | "typescript": "^4.0.2"
27 | }
28 | },
29 | "node_modules/@types/cheerio": {
30 | "version": "0.22.31",
31 | "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.31.tgz",
32 | "integrity": "sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==",
33 | "dev": true,
34 | "dependencies": {
35 | "@types/node": "*"
36 | }
37 | },
38 | "node_modules/@types/lodash": {
39 | "version": "4.14.194",
40 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.194.tgz",
41 | "integrity": "sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==",
42 | "dev": true
43 | },
44 | "node_modules/@types/node": {
45 | "version": "14.18.45",
46 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.45.tgz",
47 | "integrity": "sha512-Nd+FPp60jEaJpm4LAxuLT3wIhB4k0Jdj9DAP4ydqGyMg8DhE+7oM1we+QkwOkpMySTjcqcNfPOWY5kBuAOhkeg==",
48 | "dev": true
49 | },
50 | "node_modules/@types/slash": {
51 | "version": "3.0.0",
52 | "resolved": "https://registry.npmjs.org/@types/slash/-/slash-3.0.0.tgz",
53 | "integrity": "sha512-bmUaw0IUPUVldtj4YwU7tbzxllQQgsWdnB45bwTI0f1Lq2Yg8lT2yxV4OGZrMTrP/G9v8eVIhX130xPe/RfPfw==",
54 | "deprecated": "This is a stub types definition. slash provides its own type definitions, so you do not need this installed.",
55 | "dev": true,
56 | "dependencies": {
57 | "slash": "*"
58 | }
59 | },
60 | "node_modules/@types/traverse": {
61 | "version": "0.6.32",
62 | "resolved": "https://registry.npmjs.org/@types/traverse/-/traverse-0.6.32.tgz",
63 | "integrity": "sha512-RBz2uRZVCXuMg93WD//aTS5B120QlT4lR/gL+935QtGsKHLS6sCtZBaKfWjIfk7ZXv/r8mtGbwjVIee6/3XTow==",
64 | "dev": true
65 | },
66 | "node_modules/@types/unist": {
67 | "version": "2.0.6",
68 | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
69 | "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
70 | "dev": true
71 | },
72 | "node_modules/balanced-match": {
73 | "version": "1.0.2",
74 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
75 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
76 | "dev": true
77 | },
78 | "node_modules/boolbase": {
79 | "version": "1.0.0",
80 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
81 | "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
82 | },
83 | "node_modules/brace-expansion": {
84 | "version": "1.1.11",
85 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
86 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
87 | "dev": true,
88 | "dependencies": {
89 | "balanced-match": "^1.0.0",
90 | "concat-map": "0.0.1"
91 | }
92 | },
93 | "node_modules/cheerio": {
94 | "version": "1.0.0-rc.12",
95 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
96 | "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
97 | "dependencies": {
98 | "cheerio-select": "^2.1.0",
99 | "dom-serializer": "^2.0.0",
100 | "domhandler": "^5.0.3",
101 | "domutils": "^3.0.1",
102 | "htmlparser2": "^8.0.1",
103 | "parse5": "^7.0.0",
104 | "parse5-htmlparser2-tree-adapter": "^7.0.0"
105 | },
106 | "engines": {
107 | "node": ">= 6"
108 | },
109 | "funding": {
110 | "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
111 | }
112 | },
113 | "node_modules/cheerio-select": {
114 | "version": "2.1.0",
115 | "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
116 | "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
117 | "dependencies": {
118 | "boolbase": "^1.0.0",
119 | "css-select": "^5.1.0",
120 | "css-what": "^6.1.0",
121 | "domelementtype": "^2.3.0",
122 | "domhandler": "^5.0.3",
123 | "domutils": "^3.0.1"
124 | },
125 | "funding": {
126 | "url": "https://github.com/sponsors/fb55"
127 | }
128 | },
129 | "node_modules/concat-map": {
130 | "version": "0.0.1",
131 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
132 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
133 | "dev": true
134 | },
135 | "node_modules/css-select": {
136 | "version": "5.1.0",
137 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
138 | "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
139 | "dependencies": {
140 | "boolbase": "^1.0.0",
141 | "css-what": "^6.1.0",
142 | "domhandler": "^5.0.2",
143 | "domutils": "^3.0.1",
144 | "nth-check": "^2.0.1"
145 | },
146 | "funding": {
147 | "url": "https://github.com/sponsors/fb55"
148 | }
149 | },
150 | "node_modules/css-selector-parser": {
151 | "version": "1.4.1",
152 | "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
153 | "integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
154 | },
155 | "node_modules/css-what": {
156 | "version": "6.1.0",
157 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
158 | "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
159 | "engines": {
160 | "node": ">= 6"
161 | },
162 | "funding": {
163 | "url": "https://github.com/sponsors/fb55"
164 | }
165 | },
166 | "node_modules/dom-serializer": {
167 | "version": "2.0.0",
168 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
169 | "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
170 | "dependencies": {
171 | "domelementtype": "^2.3.0",
172 | "domhandler": "^5.0.2",
173 | "entities": "^4.2.0"
174 | },
175 | "funding": {
176 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
177 | }
178 | },
179 | "node_modules/domelementtype": {
180 | "version": "2.3.0",
181 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
182 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
183 | "funding": [
184 | {
185 | "type": "github",
186 | "url": "https://github.com/sponsors/fb55"
187 | }
188 | ]
189 | },
190 | "node_modules/domhandler": {
191 | "version": "5.0.3",
192 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
193 | "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
194 | "dependencies": {
195 | "domelementtype": "^2.3.0"
196 | },
197 | "engines": {
198 | "node": ">= 4"
199 | },
200 | "funding": {
201 | "url": "https://github.com/fb55/domhandler?sponsor=1"
202 | }
203 | },
204 | "node_modules/domutils": {
205 | "version": "3.1.0",
206 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
207 | "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
208 | "dependencies": {
209 | "dom-serializer": "^2.0.0",
210 | "domelementtype": "^2.3.0",
211 | "domhandler": "^5.0.3"
212 | },
213 | "funding": {
214 | "url": "https://github.com/fb55/domutils?sponsor=1"
215 | }
216 | },
217 | "node_modules/entities": {
218 | "version": "4.5.0",
219 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
220 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
221 | "engines": {
222 | "node": ">=0.12"
223 | },
224 | "funding": {
225 | "url": "https://github.com/fb55/entities?sponsor=1"
226 | }
227 | },
228 | "node_modules/fs.realpath": {
229 | "version": "1.0.0",
230 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
231 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
232 | "dev": true
233 | },
234 | "node_modules/glob": {
235 | "version": "7.2.3",
236 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
237 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
238 | "dev": true,
239 | "dependencies": {
240 | "fs.realpath": "^1.0.0",
241 | "inflight": "^1.0.4",
242 | "inherits": "2",
243 | "minimatch": "^3.1.1",
244 | "once": "^1.3.0",
245 | "path-is-absolute": "^1.0.0"
246 | },
247 | "engines": {
248 | "node": "*"
249 | },
250 | "funding": {
251 | "url": "https://github.com/sponsors/isaacs"
252 | }
253 | },
254 | "node_modules/htmlparser2": {
255 | "version": "8.0.2",
256 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
257 | "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
258 | "funding": [
259 | "https://github.com/fb55/htmlparser2?sponsor=1",
260 | {
261 | "type": "github",
262 | "url": "https://github.com/sponsors/fb55"
263 | }
264 | ],
265 | "dependencies": {
266 | "domelementtype": "^2.3.0",
267 | "domhandler": "^5.0.3",
268 | "domutils": "^3.0.1",
269 | "entities": "^4.4.0"
270 | }
271 | },
272 | "node_modules/inflight": {
273 | "version": "1.0.6",
274 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
275 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
276 | "dev": true,
277 | "dependencies": {
278 | "once": "^1.3.0",
279 | "wrappy": "1"
280 | }
281 | },
282 | "node_modules/inherits": {
283 | "version": "2.0.4",
284 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
285 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
286 | "dev": true
287 | },
288 | "node_modules/is-invalid-path": {
289 | "version": "1.0.2",
290 | "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-1.0.2.tgz",
291 | "integrity": "sha512-6KLcFrPCEP3AFXMfnWrIFkZpYNBVzZAoBJJDEZKtI3LXkaDjM3uFMJQjxiizUuZTZ9Oh9FNv/soXbx5TcpaDmA==",
292 | "engines": {
293 | "node": ">=6.0"
294 | }
295 | },
296 | "node_modules/lodash": {
297 | "version": "4.17.21",
298 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
299 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
300 | },
301 | "node_modules/minimatch": {
302 | "version": "3.1.2",
303 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
304 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
305 | "dev": true,
306 | "dependencies": {
307 | "brace-expansion": "^1.1.7"
308 | },
309 | "engines": {
310 | "node": "*"
311 | }
312 | },
313 | "node_modules/not": {
314 | "version": "0.1.0",
315 | "resolved": "https://registry.npmjs.org/not/-/not-0.1.0.tgz",
316 | "integrity": "sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA=="
317 | },
318 | "node_modules/nth-check": {
319 | "version": "2.1.1",
320 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
321 | "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
322 | "dependencies": {
323 | "boolbase": "^1.0.0"
324 | },
325 | "funding": {
326 | "url": "https://github.com/fb55/nth-check?sponsor=1"
327 | }
328 | },
329 | "node_modules/once": {
330 | "version": "1.4.0",
331 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
332 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
333 | "dev": true,
334 | "dependencies": {
335 | "wrappy": "1"
336 | }
337 | },
338 | "node_modules/parse5": {
339 | "version": "7.1.2",
340 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
341 | "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
342 | "dependencies": {
343 | "entities": "^4.4.0"
344 | },
345 | "funding": {
346 | "url": "https://github.com/inikulin/parse5?sponsor=1"
347 | }
348 | },
349 | "node_modules/parse5-htmlparser2-tree-adapter": {
350 | "version": "7.0.0",
351 | "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
352 | "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
353 | "dependencies": {
354 | "domhandler": "^5.0.2",
355 | "parse5": "^7.0.0"
356 | },
357 | "funding": {
358 | "url": "https://github.com/inikulin/parse5?sponsor=1"
359 | }
360 | },
361 | "node_modules/path-is-absolute": {
362 | "version": "1.0.1",
363 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
364 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
365 | "dev": true,
366 | "engines": {
367 | "node": ">=0.10.0"
368 | }
369 | },
370 | "node_modules/rimraf": {
371 | "version": "3.0.2",
372 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
373 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
374 | "dev": true,
375 | "dependencies": {
376 | "glob": "^7.1.3"
377 | },
378 | "bin": {
379 | "rimraf": "bin.js"
380 | },
381 | "funding": {
382 | "url": "https://github.com/sponsors/isaacs"
383 | }
384 | },
385 | "node_modules/slash": {
386 | "version": "5.0.1",
387 | "resolved": "https://registry.npmjs.org/slash/-/slash-5.0.1.tgz",
388 | "integrity": "sha512-ywNzUOiXwetmLvTUiCBZpLi+vxqN3i+zDqjs2HHfUSV3wN4UJxVVKWrS1JZDeiJIeBFNgB5pmioC2g0IUTL+rQ==",
389 | "dev": true,
390 | "engines": {
391 | "node": ">=14.16"
392 | },
393 | "funding": {
394 | "url": "https://github.com/sponsors/sindresorhus"
395 | }
396 | },
397 | "node_modules/traverse": {
398 | "version": "0.6.7",
399 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz",
400 | "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==",
401 | "funding": {
402 | "url": "https://github.com/sponsors/ljharb"
403 | }
404 | },
405 | "node_modules/typescript": {
406 | "version": "4.9.5",
407 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
408 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
409 | "dev": true,
410 | "bin": {
411 | "tsc": "bin/tsc",
412 | "tsserver": "bin/tsserver"
413 | },
414 | "engines": {
415 | "node": ">=4.2.0"
416 | }
417 | },
418 | "node_modules/unist-util-is": {
419 | "version": "4.1.0",
420 | "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
421 | "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==",
422 | "funding": {
423 | "type": "opencollective",
424 | "url": "https://opencollective.com/unified"
425 | }
426 | },
427 | "node_modules/unist-util-select": {
428 | "version": "3.0.4",
429 | "resolved": "https://registry.npmjs.org/unist-util-select/-/unist-util-select-3.0.4.tgz",
430 | "integrity": "sha512-xf1zCu4okgPqGLdhCDpRnjwBNyv3EqjiXRUbz2SdK1+qnLMB7uXXajfzuBvvbHoQ+JLyp4AEbFCGndmc6S72sw==",
431 | "dependencies": {
432 | "css-selector-parser": "^1.0.0",
433 | "not": "^0.1.0",
434 | "nth-check": "^2.0.0",
435 | "unist-util-is": "^4.0.0",
436 | "zwitch": "^1.0.0"
437 | },
438 | "funding": {
439 | "type": "opencollective",
440 | "url": "https://opencollective.com/unified"
441 | }
442 | },
443 | "node_modules/wrappy": {
444 | "version": "1.0.2",
445 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
446 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
447 | "dev": true
448 | },
449 | "node_modules/zwitch": {
450 | "version": "1.0.5",
451 | "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz",
452 | "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==",
453 | "funding": {
454 | "type": "github",
455 | "url": "https://github.com/sponsors/wooorm"
456 | }
457 | }
458 | }
459 | }
460 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-remark-relative-images",
3 | "version": "2.0.5",
4 | "description": "Convert image src(s) in markdown to be relative to their node's parent directory. This will help gatsby-remark-images match images outside the node folder. For example, use with NetlifyCMS.",
5 | "main": "dist/index.js",
6 | "files": [
7 | "dist",
8 | "*.js"
9 | ],
10 | "scripts": {
11 | "test": "echo \"Error: no test specified\" && exit 1",
12 | "build": "npm run clean && tsc -d",
13 | "watch": "npm run clean && tsc --watch",
14 | "clean": "rimraf ./dist",
15 | "prepare": "npm run build"
16 | },
17 | "keywords": [
18 | "gatsby",
19 | "gatsby-plugin",
20 | "image",
21 | "markdown",
22 | "relative",
23 | "netlifycms",
24 | "remark",
25 | "responsive images"
26 | ],
27 | "author": "Daniel Mahon ",
28 | "license": "0BSD",
29 | "dependencies": {
30 | "cheerio": "^1.0.0-rc.12",
31 | "is-invalid-path": "^1.0.2",
32 | "lodash": "^4.17.21",
33 | "traverse": "^0.6.6",
34 | "unist-util-select": "^3.0.4"
35 | },
36 | "devDependencies": {
37 | "@types/cheerio": "^0.22.21",
38 | "@types/lodash": "^4.14.160",
39 | "@types/node": "^14.6.0",
40 | "@types/slash": "^3.0.0",
41 | "@types/traverse": "^0.6.32",
42 | "@types/unist": "^2.0.3",
43 | "rimraf": "^3.0.2",
44 | "typescript": "^4.0.2"
45 | },
46 | "repository": {
47 | "type": "git",
48 | "url": "git+https://github.com/danielmahon/gatsby-remark-relative-images.git"
49 | },
50 | "bugs": {
51 | "url": "https://github.com/danielmahon/gatsby-remark-relative-images/issues"
52 | },
53 | "homepage": "https://github.com/danielmahon/gatsby-remark-relative-images#readme"
54 | }
55 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { selectAll } from 'unist-util-select';
3 | import { defaults, isString, find } from 'lodash';
4 | import cheerio from 'cheerio';
5 | import { slash } from './utils';
6 |
7 | export type GatsbyNodePluginArgs = {
8 | files: GatsbyFile[];
9 | markdownNode: MarkdownNode;
10 | markdownAST: any;
11 | reporter: {
12 | info: (msg: string, error?: Error) => void;
13 | };
14 | };
15 |
16 | export type GatsbyFile = {
17 | absolutePath: string;
18 | };
19 |
20 | export type PluginOptions = {
21 | staticFolderName: string;
22 | include: string[];
23 | exclude: string[];
24 | };
25 |
26 | export type FrontMatterOptions = {
27 | staticFolderName: string;
28 | include: string[];
29 | exclude: string[];
30 | };
31 |
32 | export type MarkdownNode = {
33 | id: string;
34 | parent: string;
35 | url: string;
36 | frontmatter?: object;
37 | internal: {
38 | type: string;
39 | };
40 | fileAbsolutePath: string;
41 | };
42 |
43 | export type Node = {
44 | dir: string;
45 | };
46 |
47 | export type HtmlNode = {
48 | value: string;
49 | } & MarkdownNode;
50 |
51 | export const defaultPluginOptions = {
52 | staticFolderName: 'static',
53 | include: [],
54 | exclude: [],
55 | };
56 |
57 | export const findMatchingFile = (
58 | src: string,
59 | files: GatsbyFile[],
60 | options: PluginOptions
61 | ) => {
62 | const result = find(files, (file) => {
63 | const staticPath = slash(path.join(options.staticFolderName, src));
64 | return slash(path.normalize(file.absolutePath)).endsWith(staticPath);
65 | });
66 | if (!result) {
67 | throw new Error(
68 | `No matching file found for src "${src}" in static folder "${options.staticFolderName}". Please check static folder name and that file exists at "${options.staticFolderName}${src}". This error will probably cause a "GraphQLDocumentError" later in build. All converted field paths MUST resolve to a matching file in the "static" folder.`
69 | );
70 | }
71 | return result;
72 | };
73 |
74 | export default async (
75 | { files, markdownNode, markdownAST }: GatsbyNodePluginArgs,
76 | pluginOptions: PluginOptions
77 | ) => {
78 | // Default options
79 | const options = defaults(pluginOptions, defaultPluginOptions);
80 |
81 | if (!markdownNode.fileAbsolutePath) return;
82 |
83 | const directory = path.dirname(markdownNode.fileAbsolutePath);
84 |
85 | // Process all markdown image nodes
86 | selectAll('image', markdownAST).forEach((_node: any) => {
87 | const node = _node as MarkdownNode;
88 | if (!isString(node.url)) return;
89 | if (!path.isAbsolute(node.url) || !path.extname(node.url)) return;
90 |
91 | const file = findMatchingFile(node.url, files, options);
92 |
93 | // Update node.url to be relative to its parent file
94 | node.url = path.relative(directory, file.absolutePath);
95 | });
96 |
97 | // Process all HTML images in markdown body
98 | selectAll('html', markdownAST).forEach((_node: any) => {
99 | const node = _node as HtmlNode;
100 |
101 | const $ = cheerio.load(node.value);
102 |
103 | if ($(`img`).length === 0) return;
104 |
105 | $(`img`).each((_, element) => {
106 | // Get the details we need.
107 | const url = $(element).attr(`src`);
108 |
109 | // Only handle absolute (local) urls
110 | if (!isString(url)) return;
111 | if (!path.isAbsolute(url) || !path.extname(url)) return;
112 |
113 | const file = findMatchingFile(url, files, options);
114 |
115 | // Make the image src relative to its parent node
116 | const src = path.relative(directory, file.absolutePath);
117 | $(element).attr('src', src);
118 |
119 | node.value = $(`body`).html() ?? ''; // fix for cheerio v1
120 | });
121 | });
122 | };
123 |
--------------------------------------------------------------------------------
/src/on-create-node.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { defaults, isString } from 'lodash';
3 | import traverse from 'traverse';
4 | import {
5 | defaultPluginOptions,
6 | PluginOptions,
7 | GatsbyFile,
8 | MarkdownNode,
9 | findMatchingFile,
10 | } from '.';
11 | import { slash } from './utils';
12 |
13 | export type GatsbyPluginArgs = {
14 | node: MarkdownNode;
15 | getNodesByType: (type: string) => GatsbyFile[];
16 | reporter: {
17 | info: (msg: string, error?: Error) => void;
18 | };
19 | };
20 |
21 | export const onCreateNode = (
22 | { node, getNodesByType }: GatsbyPluginArgs,
23 | pluginOptions: PluginOptions
24 | ) => {
25 | const options = defaults(pluginOptions, defaultPluginOptions);
26 |
27 | if (node.fileAbsolutePath && node.internal.type === `MarkdownRemark` || node.internal.type === `Mdx`) {
28 | const files = getNodesByType(`File`);
29 |
30 | const directory = path.dirname(node.fileAbsolutePath);
31 |
32 | // Deeply iterate through frontmatter data for absolute paths
33 | traverse(node.frontmatter).forEach(function (value) {
34 | if (!isString(value)) return;
35 | if (!path.isAbsolute(value) || !path.extname(value)) return;
36 |
37 | const paths = this.path.reduce((acc, current) => {
38 | acc.push(acc.length > 0 ? [acc, current].join('.') : current);
39 | return acc;
40 | }, []);
41 |
42 | let shouldTransform = options.include.length < 1;
43 |
44 | if (options.include.some((a) => paths.includes(a))) {
45 | shouldTransform = true;
46 | }
47 |
48 | if (options.exclude.some((a) => paths.includes(a))) {
49 | shouldTransform = false;
50 | }
51 |
52 | if (!shouldTransform) return;
53 |
54 | const file = findMatchingFile(value, files, options);
55 |
56 | const newValue = slash(path.relative(directory, file.absolutePath));
57 |
58 | this.update(newValue);
59 | });
60 | }
61 | };
62 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export const slash = (path: string): string => {
2 | const isExtendedLengthPath = /^\\\\\?\\/.test(path);
3 |
4 | if (isExtendedLengthPath) {
5 | return path;
6 | }
7 | return path.replace(/\\/g, `/`);
8 | };
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "target": "es6",
5 | "module": "commonjs",
6 | "noImplicitAny": false,
7 | "moduleResolution": "node",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "allowSyntheticDefaultImports": true,
11 | "outDir": "dist"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/types.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'is-invalid-path';
2 |
--------------------------------------------------------------------------------