Skip to content

Commit

Permalink
Replace transformImageUri, transformLinkUri w/ urlTransform
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Sep 27, 2023
1 parent ec2b134 commit eca5e6b
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 121 deletions.
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes will be documented in this file.

## 9.0.0 - unreleased

### Add `urlTransform`

The `transformImageUri` and `transformLinkUri` were removed.
Having two functions is a bit much, particularly because there are more URLs
you might want to change (or which might be unsafe so *we* make them safe).
And their name and APIs were a bit weird.
You can use the new `urlTransform` prop instead to change all your URLs.

### Remove `includeElementIndex` option

The `includeElementIndex` option was removed, so `index` is never passed to
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
* @typedef {import('./lib/index.js').Options} Options
*/

export {Markdown as default, uriTransformer} from './lib/index.js'
export {Markdown as default, defaultUrlTransform} from './lib/index.js'
87 changes: 30 additions & 57 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,44 +59,28 @@
* Options to pass through to `remark-rehype`.
* @property {boolean | null | undefined} [skipHtml=false]
* Ignore HTML in markdown completely (default: `false`).
* @property {TransformLink | false | null | undefined} [transformLinkUri]
* Change URLs on images (default: `uriTransformer`);
* pass `false` to allow all URLs, which is unsafe
* @property {TransformImage | false | null | undefined} [transformImageUri]
* Change URLs on links (default: `uriTransformer`);
* pass `false` to allow all URLs, which is unsafe
* @property {boolean | null | undefined} [unwrapDisallowed=false]
* Extract (unwrap) the children of not allowed elements (default: `false`);
* normally when say `strong` is disallowed, it and it’s children are dropped,
* with `unwrapDisallowed` the element itself is replaced by its children.
* @property {UrlTransform | null | undefined} [urlTransform]
* Change URLs (default: `defaultUrlTransform`)
*
* @callback TransformImage
* Transform URLs on images.
* @param {string} src
* @callback UrlTransform
* Transform URLs.
* @param {string} url
* URL to transform.
* @param {string} alt
* Alt text.
* @param {string | null} title
* Title.
* To do: pass `undefined`.
* @param {string} key
* Property name (example: `'href'`).
* @param {Readonly<Element>} node
* Node.
* @returns {string | null | undefined}
* Transformed URL (optional).
*
* @callback TransformLink
* Transform URLs on links.
* @param {string} href
* URL to transform.
* @param {ReadonlyArray<ElementContent>} children
* Content.
* @param {string | null} title
* Title.
* To do: pass `undefined`.
* @returns {string}
* Transformed URL (optional).
*/

import {unreachable} from 'devlop'
import {toJsxRuntime} from 'hast-util-to-jsx-runtime'
import {urlAttributes} from 'html-url-attributes'
// @ts-expect-error: untyped.
import {Fragment, jsx, jsxs} from 'react/jsx-runtime'
import remarkParse from 'remark-parse'
Expand Down Expand Up @@ -146,7 +130,9 @@ const deprecations = [
{from: 'rawSourcePos', id: '#remove-rawsourcepos-option'},
{from: 'renderers', id: 'change-renderers-to-components', to: 'components'},
{from: 'source', id: 'change-source-to-children', to: 'children'},
{from: 'sourcePos', id: '#remove-sourcepos-option'}
{from: 'sourcePos', id: '#remove-sourcepos-option'},
{from: 'transformImageUri', id: '#add-urltransform', to: 'urlTransform'},
{from: 'transformLinkUri', id: '#add-urltransform', to: 'urlTransform'}
]

/**
Expand All @@ -171,15 +157,8 @@ export function Markdown(options) {
? {...options.remarkRehypeOptions, ...emptyRemarkRehypeOptions}
: emptyRemarkRehypeOptions
const skipHtml = options.skipHtml
const transformImageUri =
options.transformImageUri === undefined
? uriTransformer
: options.transformImageUri
const transformLinkUri =
options.transformLinkUri === undefined
? uriTransformer
: options.transformLinkUri
const unwrapDisallowed = options.unwrapDisallowed
const urlTransform = options.urlTransform || defaultUrlTransform

const processor = unified()
.use(remarkParse)
Expand Down Expand Up @@ -265,26 +244,19 @@ export function Markdown(options) {
return index
}

if (transformLinkUri && node.type === 'element' && node.tagName === 'a') {
node.properties.href = transformLinkUri(
String(node.properties.href || ''),
node.children,
// To do: pass `undefined`.
typeof node.properties.title === 'string' ? node.properties.title : null
)
}

if (
transformImageUri &&
node.type === 'element' &&
node.tagName === 'img'
) {
node.properties.src = transformImageUri(
String(node.properties.src || ''),
String(node.properties.alt || ''),
// To do: pass `undefined`.
typeof node.properties.title === 'string' ? node.properties.title : null
)
if (node.type === 'element') {
/** @type {string} */
let key

for (key in urlAttributes) {
if (own.call(urlAttributes, key) && own.call(node.properties, key)) {
const value = node.properties[key]
const test = urlAttributes[key]
if (test === null || test.includes(node.tagName)) {
node.properties[key] = urlTransform(String(value || ''), key, node)
}
}
}
}

if (node.type === 'element') {
Expand Down Expand Up @@ -316,13 +288,14 @@ export function Markdown(options) {
/**
* Make a URL safe.
*
* @satisfies {UrlTransform}
* @param {string} value
* URL.
* @returns {string}
* Safe URL.
*/
export function uriTransformer(value) {
const url = (value || '').trim()
export function defaultUrlTransform(value) {
const url = value.trim()
const first = url.charAt(0)

if (first === '#' || first === '/') {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"@types/hast": "^3.0.0",
"devlop": "^1.0.0",
"hast-util-to-jsx-runtime": "^2.0.0",
"html-url-attributes": "^3.0.0",
"mdast-util-to-hast": "^13.0.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.0.0",
Expand Down
79 changes: 16 additions & 63 deletions test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test('react-markdown', async function (t) {
await t.test('should expose the public api', async function () {
assert.deepEqual(Object.keys(await import('./index.js')).sort(), [
'default',
'uriTransformer'
'defaultUrlTransform'
])
})

Expand Down Expand Up @@ -345,15 +345,15 @@ test('react-markdown', async function (t) {
)
})

await t.test('should support `transformLinkUri`', function () {
await t.test('should support `urlTransform` (`href` on `a`)', function () {
assert.equal(
asHtml(
<Markdown
children="[a](https://b.com 'c')"
transformLinkUri={function (src, children, title) {
assert.equal(src, 'https://b.com')
assert.equal(children.length, 1)
assert.equal(title, 'c')
urlTransform={function (url, key, node) {
assert.equal(url, 'https://b.com')
assert.equal(key, 'href')
assert.equal(node.tagName, 'a')
return ''
}}
/>
Expand All @@ -362,15 +362,15 @@ test('react-markdown', async function (t) {
)
})

await t.test('should support `transformLinkUri` w/ empty URLs', function () {
await t.test('should support `urlTransform` w/ empty URLs', function () {
assert.equal(
asHtml(
<Markdown
children="[]()"
transformLinkUri={function (href, children, title) {
assert.equal(href, '')
assert.equal(children.length, 0)
assert.equal(title, null)
urlTransform={function (url, key, node) {
assert.equal(url, '')
assert.equal(key, 'href')
assert.equal(node.tagName, 'a')
return ''
}}
/>
Expand All @@ -379,30 +379,15 @@ test('react-markdown', async function (t) {
)
})

await t.test(
'should support turning off `transformLinkUri` (dangerous)',
function () {
assert.equal(
asHtml(
<Markdown
children="[](javascript:alert(1))"
transformLinkUri={null}
/>
),
'<p><a href="javascript:alert(1)"></a></p>'
)
}
)

await t.test('should support `transformImageUri`', function () {
await t.test('should support `urlTransform` (`src` on `img`)', function () {
assert.equal(
asHtml(
<Markdown
children="![a](https://b.com 'c')"
transformImageUri={function (src, alt, title) {
assert.equal(src, 'https://b.com')
assert.equal(alt, 'a')
assert.equal(title, 'c')
urlTransform={function (url, key, node) {
assert.equal(url, 'https://b.com')
assert.equal(key, 'src')
assert.equal(node.tagName, 'img')
return ''
}}
/>
Expand All @@ -411,38 +396,6 @@ test('react-markdown', async function (t) {
)
})

await t.test('should support `transformImageUri` w/ empty URLs', function () {
assert.equal(
asHtml(
<Markdown
children="![]()"
transformImageUri={function (href, alt, title) {
assert.equal(href, '')
assert.equal(alt, '')
assert.equal(title, null)
return ''
}}
/>
),
'<p><img src="" alt=""/></p>'
)
})

await t.test(
'should support turning off `transformImageUri` (dangerous)',
function () {
assert.equal(
asHtml(
<Markdown
children="![](javascript:alert(1))"
transformImageUri={null}
/>
),
'<p><img src="javascript:alert(1)" alt=""/></p>'
)
}
)

await t.test('should support `skipHtml`', function () {
const actual = asHtml(<Markdown children="a<i>b</i>c" skipHtml />)
assert.equal(actual, '<p>abc</p>')
Expand Down

0 comments on commit eca5e6b

Please sign in to comment.