-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
150 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,117 @@ | ||
/** | ||
* @typedef {import('mdast').Root} Root | ||
* @typedef {import('mdast').Image} Image | ||
* @typedef {import('mdast').Link} Link | ||
* | ||
* @typedef {import('mdast').Root} Root | ||
* @typedef {import('mdast').RootContent} RootContent | ||
*/ | ||
|
||
/** | ||
* @typedef Options | ||
* Configuration (optional). | ||
* @property {Array<string> | undefined} [imageExtensions] | ||
* File extensions (without dot) to treat as images. | ||
* @property {ReadonlyArray<string> | null | undefined} [imageExtensions] | ||
* File extensions (without dot) to treat as images (default: | ||
* `defaultImageExtensions`). | ||
*/ | ||
|
||
import {collapseWhiteSpace} from 'collapse-white-space' | ||
import isUrl from 'is-url' | ||
import {position} from 'unist-util-position' | ||
import {visitParents} from 'unist-util-visit-parents' | ||
import {is} from 'unist-util-is' | ||
|
||
const isImgPath = (/** @type {string} */ value) => | ||
value.startsWith('/') || value.startsWith('./') || value.startsWith('../') | ||
|
||
/** | ||
* Extensions recognized as images by default | ||
*/ | ||
export const defaultImageExtensions = [ | ||
'svg', | ||
'png', | ||
'jpg', | ||
'jpeg', | ||
'gif', | ||
'webp', | ||
'avif' | ||
] | ||
/** @type {Readonly<Options>} */ | ||
const emptyOptions = {} | ||
|
||
/** | ||
* Plugin to add a simpler image syntax. | ||
* Add a simpler image syntax. | ||
* | ||
* @type {import('unified').Plugin<[Options?]|void[], Root>} | ||
* @param {Readonly<Options> | null | undefined} [options] | ||
* Configuration (optional). | ||
* @returns | ||
* Transform. | ||
*/ | ||
export default function remarkImages({ | ||
imageExtensions = defaultImageExtensions | ||
} = {}) { | ||
const imgExtRegex = new RegExp(`\\.(${imageExtensions.join('|')})$`) | ||
const isImgExt = (/** @type {string} */ value) => imgExtRegex.test(value) | ||
export default function remarkImages(options) { | ||
const settings = options || emptyOptions | ||
const imageExtensions = settings.imageExtensions || defaultImageExtensions | ||
const imageExtensionRegex = new RegExp(`\\.(${imageExtensions.join('|')})$`) | ||
|
||
return (tree) => { | ||
visitParents(tree, 'text', (node, parents) => { | ||
const value = String(node.value).trim() | ||
/** | ||
* Transform. | ||
* | ||
* @param {Root} tree | ||
* Tree. | ||
* @returns {undefined} | ||
* Nothing. | ||
*/ | ||
return function (tree) { | ||
visitParents(tree, 'text', function (node, parents) { | ||
const value = collapseWhiteSpace(node.value, { | ||
style: 'html', | ||
trim: true | ||
}) | ||
|
||
if ((isUrl(value) || isImgPath(value)) && isImgExt(value)) { | ||
if ( | ||
// Cannot contain whitespace (collapsed, so there can only be spaces): | ||
!value.includes(' ') && | ||
// Looks like a URL or path: | ||
(isUrl(value) || | ||
value.startsWith('/') || | ||
value.startsWith('./') || | ||
value.startsWith('../')) && | ||
// Ends in known extension: | ||
imageExtensionRegex.test(value) | ||
) { | ||
let interactive = false | ||
let length = parents.length | ||
const parent = parents[length - 1] | ||
const siblings = parent.children | ||
// @ts-expect-error: too many possible parents. | ||
const index = siblings.indexOf(node) | ||
|
||
// Check if we’re in interactive content. | ||
while (length--) { | ||
if (is(parents[length], ['link', 'linkReference'])) { | ||
const parent = parents[length] | ||
if (parent.type === 'link' || parent.type === 'linkReference') { | ||
interactive = true | ||
break | ||
} | ||
} | ||
|
||
/** @type {Image} */ | ||
const image = { | ||
/** @type {Image | Link} */ | ||
let replacement = { | ||
type: 'image', | ||
url: value, | ||
title: null, | ||
alt: '', | ||
position: node.position | ||
position: position(node) | ||
} | ||
/** @type {Image|Link} */ | ||
let next = image | ||
|
||
// Add a link if we’re not already in one. | ||
if (!interactive) { | ||
next = { | ||
replacement = { | ||
type: 'link', | ||
url: value, | ||
title: null, | ||
children: [image], | ||
position: node.position | ||
children: [replacement], | ||
position: position(node) | ||
} | ||
} | ||
|
||
siblings[index] = next | ||
const parent = parents[parents.length - 1] | ||
/** @type {Array<RootContent>} */ | ||
const siblings = parent.children | ||
siblings[siblings.indexOf(node)] = replacement | ||
} | ||
}) | ||
} | ||
} | ||
|
||
/** | ||
* Extensions recognized as images by default. | ||
* | ||
* @type {ReadonlyArray<string>} | ||
*/ | ||
export const defaultImageExtensions = [ | ||
'avif', | ||
'gif', | ||
'jpeg', | ||
'jpg', | ||
'png', | ||
'svg', | ||
'webp' | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.