-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathremark-plugin.ts
More file actions
102 lines (92 loc) · 2.96 KB
/
Copy pathremark-plugin.ts
File metadata and controls
102 lines (92 loc) · 2.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { type Node, select, selectAll } from 'unist-util-select';
import blueskyMatcher from '@astro-community/astro-embed-bluesky/matcher';
import gistMatcher from '@astro-community/astro-embed-gist/matcher';
import spotifyMatcher from '@astro-community/astro-embed-spotify/matcher';
import twitterMatcher from '@astro-community/astro-embed-twitter/matcher';
import vimeoMatcher from '@astro-community/astro-embed-vimeo/matcher';
import youtubeMatcher from '@astro-community/astro-embed-youtube/matcher';
import linkPreviewMatcher from '@astro-community/astro-embed-link-preview/matcher';
import mastodonMatcher from '@astro-community/astro-embed-mastodon/matcher';
const matchers = [
[blueskyMatcher, 'BlueskyPost'],
[gistMatcher, 'Gist'],
[spotifyMatcher, 'Spotify'],
[twitterMatcher, 'Tweet'],
[vimeoMatcher, 'Vimeo'],
[youtubeMatcher, 'YouTube'],
[mastodonMatcher, 'MastodonPost'],
/** The generic link matcher must be last otherwise it will override other URLs. */
[linkPreviewMatcher, 'LinkPreview'],
] as const;
export const componentNames = matchers.map(([, name]) => name);
interface CreatePluginOptions {
importNamespace: string;
enabledComponents: typeof componentNames;
}
export default function createPlugin({
importNamespace,
enabledComponents,
}: CreatePluginOptions) {
const enabledMatchers = matchers.filter(([, name]) =>
enabledComponents.includes(name)
);
/**
* Get the name of the embed component for this URL
* @param {string} url URL to test
* @returns Component node for this URL or undefined if none matched
*/
function getComponent(url: string) {
for (const [matcher, componentName] of enabledMatchers) {
const id = matcher(url);
if (id) {
// MDX custom component node.
return {
type: 'mdxJsxFlowElement',
name: `${importNamespace}_${componentName}`,
attributes: [{ type: 'mdxJsxAttribute', name: 'id', value: id }],
children: [],
};
}
}
return undefined;
}
type Link = Node & {
url?: string;
value?: string;
children?: Node[];
};
function transformer(tree: Node) {
const paragraphs = selectAll('paragraph', tree);
paragraphs.forEach((paragraph) => {
const link: Link | undefined = select(
':scope > link:only-child',
paragraph
);
if (!link) return;
const { url, children } = link;
// We’re only interested in HTTP links
if (!url || !url.startsWith('http')) return;
// URLs should have a single child
if (!children || children.length !== 1) return;
// The child should be a text node with a value matching the URL
const child = children[0];
if (
!child ||
child.type !== 'text' ||
!('value' in child) ||
child.value !== url
) {
return;
}
const component = getComponent(url);
if (component) {
// @ts-expect-error We’re overriding the initial node type with arbitrary data.
for (const key in component) paragraph[key] = component[key];
}
});
return tree;
}
return function attacher() {
return transformer;
};
}