├── .all-contributorsrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── validate.yml ├── .gitignore ├── .huskyrc.js ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── other ├── MAINTAINING.md ├── USERS.md └── manual-releases.md ├── package.json ├── src ├── __tests__ │ └── index.ts └── index.ts ├── tsconfig.json └── vitest.config.js /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "transformer-oembed", 3 | "projectOwner": "remark-embedder", 4 | "imageSize": 100, 5 | "commit": false, 6 | "commitConvention": "none", 7 | "contributorsPerLine": 7, 8 | "repoHost": "https://github.com", 9 | "repoType": "github", 10 | "skipCi": false, 11 | "files": [ 12 | "README.md" 13 | ], 14 | "contributors": [ 15 | { 16 | "login": "kentcdodds", 17 | "name": "Kent C. Dodds", 18 | "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", 19 | "profile": "https://kentcdodds.com", 20 | "contributions": [ 21 | "code", 22 | "doc", 23 | "infra", 24 | "test" 25 | ] 26 | }, 27 | { 28 | "login": "MichaelDeBoey", 29 | "name": "Michaël De Boey", 30 | "avatar_url": "https://avatars3.githubusercontent.com/u/6643991?v=4", 31 | "profile": "https://michaeldeboey.be", 32 | "contributions": [ 33 | "code", 34 | "doc", 35 | "maintenance" 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | - `@remark-embedder/transformer-oembed` version: 15 | - `node` version: 16 | - `npm` version: 17 | 18 | Relevant code or config 19 | 20 | ```js 21 | 22 | ``` 23 | 24 | What you did: 25 | 26 | What happened: 27 | 28 | 29 | 30 | Reproduction repository: 31 | 32 | 36 | 37 | Problem description: 38 | 39 | Suggested solution: 40 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | **What**: 20 | 21 | 22 | 23 | **Why**: 24 | 25 | 26 | 27 | **How**: 28 | 29 | 30 | 31 | **Checklist**: 32 | 33 | 34 | 35 | 36 | - [ ] Documentation 37 | - [ ] Tests 38 | - [ ] Ready to be merged 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: daily 12 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: validate 2 | on: 3 | push: 4 | branches: 5 | - '+([0-9])?(.{+([0-9]),x}).x' 6 | - 'main' 7 | - 'next' 8 | - 'next-major' 9 | - 'beta' 10 | - 'alpha' 11 | - '!all-contributors/**' 12 | pull_request: {} 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | main: 19 | # ignore all-contributors PRs 20 | if: ${{ !contains(github.head_ref, 'all-contributors') }} 21 | strategy: 22 | matrix: 23 | node: [18, 20, 22] 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: ⬇️ Checkout repo 27 | uses: actions/checkout@v4 28 | 29 | - name: ⎔ Setup node 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: ${{ matrix.node }} 33 | 34 | - name: 📥 Download deps 35 | uses: bahmutov/npm-install@v1 36 | with: 37 | useLockFile: false 38 | 39 | - name: ▶️ Run validate script 40 | run: npm run validate 41 | 42 | - name: ⬆️ Upload coverage report 43 | uses: codecov/codecov-action@v4 44 | 45 | release: 46 | needs: main 47 | runs-on: ubuntu-latest 48 | if: 49 | ${{ github.repository == 'remark-embedder/transformer-oembed' && 50 | contains('refs/heads/main,refs/heads/beta,refs/heads/next,refs/heads/alpha', 51 | github.ref) && github.event_name == 'push' }} 52 | steps: 53 | - name: ⬇️ Checkout repo 54 | uses: actions/checkout@v4 55 | 56 | - name: ⎔ Setup node 57 | uses: actions/setup-node@v4 58 | with: 59 | node-version: 20 60 | 61 | - name: 📥 Download deps 62 | uses: bahmutov/npm-install@v1 63 | with: 64 | useLockFile: false 65 | 66 | - name: 🏗 Run build script 67 | run: npm run build 68 | 69 | - name: 🚀 Release 70 | uses: cycjimmy/semantic-release-action@v4 71 | with: 72 | semantic_version: 17 73 | branches: | 74 | [ 75 | '+([0-9])?(.{+([0-9]),x}).x', 76 | 'main', 77 | 'next', 78 | 'next-major', 79 | {name: 'beta', prerelease: true}, 80 | {name: 'alpha', prerelease: true} 81 | ] 82 | env: 83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 84 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .DS_Store 5 | 6 | # these cause more harm than good 7 | # when working with contributors 8 | package-lock.json 9 | yarn.lock 10 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('kcd-scripts/husky') 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('kcd-scripts/prettier') 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | The changelog is automatically updated using 4 | [semantic-release](https://github.com/semantic-release/semantic-release). You 5 | can see it on the [releases page](../../releases). 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | me+coc@kentcdodds.com. All complaints will be reviewed and investigated promptly 64 | and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for being willing to contribute! 4 | 5 | **Working on your first Pull Request?** You can learn how from this _free_ 6 | series [How to Contribute to an Open Source Project on GitHub][egghead] 7 | 8 | ## Project setup 9 | 10 | 1. Fork and clone the repo 11 | 2. Run `npm run setup -s` to install dependencies and run validation 12 | 3. Create a branch for your PR with `git checkout -b pr/your-branch-name` 13 | 14 | > Tip: Keep your `main` branch pointing at the original repository and make pull 15 | > requests from branches on your fork. To do this, run: 16 | > 17 | > ``` 18 | > git remote add upstream https://github.com/remark-embedder/transformer-oembed 19 | > git fetch upstream 20 | > git branch --set-upstream-to=upstream/main main 21 | > ``` 22 | > 23 | > This will add the original repository as a "remote" called "upstream," Then 24 | > fetch the git information from that remote, then set your local `main` branch 25 | > to use the upstream main branch whenever you run `git pull`. Then you can make 26 | > all of your pull request branches based on this `main` branch. Whenever you 27 | > want to update your version of `main`, do a regular `git pull`. 28 | 29 | ## Committing and Pushing changes 30 | 31 | Please make sure to run the tests before you commit your changes. You can run 32 | `npm run test:update` which will update any snapshots that need updating. Make 33 | sure to include those changes (if they exist) in your commit. 34 | 35 | ## Help needed 36 | 37 | Please checkout the [the open issues][issues] 38 | 39 | Also, please watch the repo and respond to questions/bug reports/feature 40 | requests! Thanks! 41 | 42 | 43 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 44 | [issues]: https://github.com/remark-embedder/transformer-oembed/issues 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2020 Kent C. Dodds 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
@remark-embedder transformer for oEmbed supported links
5 |101 | This is a great 102 | YouTube video. Watch it here: 103 |
104 | 112 |Isn't it great!?
113 | ``` 114 | 115 | ### Config 116 | 117 | Some oembed providers offer special configuration via query parameters. You can 118 | provide those via config: 119 | 120 | ```ts 121 | // ... 122 | import type {Config} from '@remark-embedder/transformer-oembed' 123 | 124 | // ... 125 | 126 | async function go() { 127 | const result = await remark() 128 | .use(remarkEmbedder, { 129 | transformers: [ 130 | [ 131 | oembedTransformer, 132 | {params: {theme: 'dark', dnt: true, omit_script: true}} as Config, 133 | ], 134 | ], 135 | }) 136 | .use(html) 137 | .process(`https://twitter.com/kentcdodds/status/783161196945944580`) 138 | 139 | console.log(result.toString()) 140 | } 141 | 142 | // ... 143 | ``` 144 | 145 | That results in (notice the `data-` attributes which are specific to [twitter's 146 | oEmbed API][twitter-oembed-docs]): 147 | 148 | ```html 149 |150 |161 | ``` 162 | 163 | This could also be used to provide an access token for providers that require 164 | this (like [Instagram][instagram-oembed-docs]). 165 | 166 | ### Config as a function 167 | 168 | You can also provide configuration as a function so you can determine what 169 | configuration to give based on the provider and/or the URL. Like so: 170 | 171 | ```ts 172 | const oembedConfig: Config = ({url, provider}) => { 173 | if (provider.provider_name === 'Instagram') { 174 | return { 175 | params: {access_token: '{app-id}|{client-token}'}, 176 | } 177 | } 178 | } 179 | const remarkEmbedderConfig = { 180 | transformers: [[oembedTransformer, oembedConfig]], 181 | } 182 | // ... etc... 183 | ``` 184 | 185 | ## Inspiration 186 | 187 | It's a long story... Check out the inspiration on 188 | [`@remark-embedder/core`][@remark-embedder/core] 189 | 190 | ## Other Solutions 191 | 192 | - [`remark-oembed`][remark-oembed]: This one requires client-side JS to work 193 | which was unacceptable for our use cases. 194 | 195 | ## Issues 196 | 197 | _Looking to contribute? Look for the [Good First Issue][good-first-issue] 198 | label._ 199 | 200 | ### 🐛 Bugs 201 | 202 | Please file an issue for bugs, missing documentation, or unexpected behavior. 203 | 204 | [**See Bugs**][bugs] 205 | 206 | ### 💡 Feature Requests 207 | 208 | Please file an issue to suggest new features. Vote on feature requests by adding 209 | a 👍. This helps maintainers prioritize what to work on. 210 | 211 | [**See Feature Requests**][requests] 212 | 213 | ## Contributors ✨ 214 | 215 | Thanks goes to these people ([emoji key][emojis]): 216 | 217 | 218 | 219 | 220 |151 | I spent a few minutes working on this, just for you all. I promise, it wont 152 | disappoint. Though it may surprise 🎉
155 | — Kent C. Dodds (@kentcdodds) 156 | October 4, 2016 160 |
🙏 153 | https://t.co/wgTJYYHOzD 154 |
Kent C. Dodds 💻 📖 🚇 ⚠️ |
223 | Michaël De Boey 💻 📖 🚧 |
224 |
', 52 | }), 53 | ), 54 | ) 55 | 56 | // enable API mocking in test runs using the same request handlers 57 | // as for the client-side mocking. 58 | beforeAll(() => server.listen()) 59 | afterEach(() => server.resetHandlers()) 60 | afterAll(() => server.close()) 61 | 62 | test('smoke test', async () => { 63 | const result = await remark() 64 | .use(remarkEmbedder, { 65 | transformers: [ 66 | [ 67 | transformer, 68 | {params: {dnt: true, omit_script: true, theme: 'dark'}} as Config, 69 | ], 70 | ], 71 | }) 72 | .use(remarkHTML, {sanitize: false}) 73 | .process( 74 | ` 75 | Here's a great tweet: 76 | 77 | https://twitter.com/kentcdodds/status/783161196945944580 78 | 79 | And here's an example of no provider: 80 | 81 | https://example.com 82 | `.trim(), 83 | ) 84 | 85 | expect(result.toString()).toMatchInlineSnapshot(` 86 |I spent a few minutes working on this, just for you all. I promise, it wont disappoint. Though it may surprise 🎉
— Kent C. Dodds (@kentcdodds) October 4, 2016
🙏 https://t.co/wgTJYYHOzD
Here's a great tweet:
87 |88 |I spent a few minutes working on this, just for you all. I promise, it wont disappoint. Though it may surprise 🎉
— Kent C. Dodds (@kentcdodds) October 4, 2016
🙏 https://t.co/wgTJYYHOzD
And here's an example of no provider:
89 |https://example.com
90 | `) 91 | }) 92 | 93 | test('no config required', async () => { 94 | const result = await remark() 95 | .use(remarkEmbedder, { 96 | transformers: [transformer], 97 | }) 98 | .use(remarkHTML, {sanitize: false}) 99 | .process(`https://twitter.com/kentcdodds/status/783161196945944580`) 100 | 101 | expect(result.toString()).toMatchInlineSnapshot( 102 | ``, 103 | ) 104 | }) 105 | 106 | test('config can be a function', async () => { 107 | const config: Config = () => ({ 108 | params: {dnt: true}, 109 | }) 110 | const result = await remark() 111 | .use(remarkEmbedder, { 112 | transformers: [[transformer, config]], 113 | }) 114 | .use(remarkHTML, {sanitize: false}) 115 | .process(`https://twitter.com/kentcdodds/status/783161196945944580`) 116 | 117 | expect(result.toString()).toMatchInlineSnapshot( 118 | `I spent a few minutes working on this, just for you all. I promise, it wont disappoint. Though it may surprise 🎉
— Kent C. Dodds (@kentcdodds) October 4, 2016
🙏 https://t.co/wgTJYYHOzD
`, 119 | ) 120 | }) 121 | 122 | test('config function does not need to return anything', async () => { 123 | const config: Config = () => null 124 | const result = await remark() 125 | .use(remarkEmbedder, { 126 | transformers: [[transformer, config]], 127 | }) 128 | .use(remarkHTML, {sanitize: false}) 129 | .process(`https://twitter.com/kentcdodds/status/783161196945944580`) 130 | 131 | expect(result.toString()).toMatchInlineSnapshot( 132 | `I spent a few minutes working on this, just for you all. I promise, it wont disappoint. Though it may surprise 🎉
— Kent C. Dodds (@kentcdodds) October 4, 2016
🙏 https://t.co/wgTJYYHOzD
`, 133 | ) 134 | }) 135 | 136 | test('sends the correct search params', async () => { 137 | let request: Request 138 | 139 | server.use( 140 | http.get('https://publish.twitter.com/oembed', ({request: req}) => { 141 | request = req 142 | return HttpResponse.json({ 143 | html: 'whatever', 144 | }) 145 | }), 146 | ) 147 | 148 | await remark() 149 | .use(remarkEmbedder, { 150 | transformers: [ 151 | [ 152 | transformer, 153 | {params: {dnt: true, omit_script: true, theme: 'dark'}} as Config, 154 | ], 155 | ], 156 | }) 157 | .use(remarkHTML, {sanitize: false}) 158 | .process(`https://twitter.com/kentcdodds/status/783161196945944580`) 159 | 160 | // @ts-expect-error it doesn't think request will be assigned by now... But it will! 161 | expect(request.url.toString()).toMatchInlineSnapshot( 162 | `https://publish.twitter.com/oembed?url=https%3A%2F%2Ftwitter.com%2Fkentcdodds%2Fstatus%2F783161196945944580&dnt=true&omit_script=true&theme=dark&format=json`, 163 | ) 164 | }) 165 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {URL} from 'url' 2 | import {type Transformer} from '@remark-embedder/core' 3 | 4 | type Provider = { 5 | provider_name: string 6 | provider_url: string 7 | endpoints: Array<{ 8 | schemes?: string[] 9 | discovery?: boolean 10 | url: string 11 | }> 12 | } 13 | type Providers = ArrayI spent a few minutes working on this, just for you all. I promise, it wont disappoint. Though it may surprise 🎉
— Kent C. Dodds (@kentcdodds) October 4, 2016
🙏 https://t.co/wgTJYYHOzD