remark-directive-sugar
Version:
Remark plugin built on remark-directive, providing predefined directives for image captions, video embedding, styled GitHub links, badges, and more.
146 lines (145 loc) • 4.95 kB
JavaScript
import { createIfNeeded, processUrl, mergeProps } from '../utils.js';
const customUrlRegex = /^(?:https?:\/\/)?(?:[\w-]+\.)+[a-z]{2,}(?:\/\S*)?$/;
const githubAcctRegex = /^@[a-zA-Z\d](?!.*--)[\w-]{0,37}[a-zA-Z\d]$/;
const githubRepoRegex = /^([a-zA-Z\d](?!.*--)[\w-]{0,37}[a-zA-Z\d])\/.*$/;
const npmPkgRegex = /^(?=.{1,214}$)(?:@[a-z\d][a-z\d._-]*\/)?[a-z\d][a-z\d._-]*$/;
const tabOrgRegex = /^org-(\w+)$/;
const githubTab = new Set([
'repositories',
'projects',
'packages',
'stars',
'sponsoring',
'sponsors',
'org-repositories',
'org-projects',
'org-packages',
'org-sponsoring',
'org-people',
]);
const npmTab = new Set([
'readme',
'code',
'dependencies',
'dependents',
'versions',
]);
/**
* Handles the `link` directive.
* Inspired by {@link https://github.com/antfu/markdown-it-magic-link markdown-it-magic-link}.
*/
export function handleLinkDirective(node, config) {
if (node.type === 'leafDirective')
throw new Error('Unexpected leaf directive. Use single colon (`:`) for a `link` text directive.');
if (node.type === 'containerDirective')
throw new Error('Unexpected container directive. Use single colon (`:`) for a `link` text directive.');
const defaultLinkProps = { className: ['rds-link'] };
const { aProps, imgProps, faviconSourceUrl } = config;
const faviconUrl = faviconSourceUrl ?? 'https://icons.duckduckgo.com/ip3/{domain}.ico';
const data = (node.data ||= {});
const attributes = node.attributes || {};
const { children } = node;
const { id, url, img, tab, ...attrs } = attributes;
// check if the id is missing & get type
let linkType;
if (!id) {
throw new Error('Invalid `link` directive. The `id` is missing.');
}
else if (githubAcctRegex.test(id)) {
linkType = 'github-acct';
}
else if (githubRepoRegex.test(id)) {
linkType = 'github-repo';
}
else if (npmPkgRegex.test(id)) {
linkType = 'npm-pkg';
}
else if (customUrlRegex.test(id)) {
linkType = 'custom-url';
}
else {
throw new Error('Invalid `link` directive. The `id` is invalid.');
}
// check if the tab is valid & get tab
let isGithubOrg = false;
let resolvedTab = '';
if (tab) {
if (!githubTab.has(tab) && !npmTab.has(tab)) {
throw new Error('Invalid `link` directive. The `tab` is invalid.');
}
else {
const match = tabOrgRegex.exec(tab);
isGithubOrg = Boolean(match);
resolvedTab = match ? match[1] : tab;
}
}
// get text
let resolvedText = '';
if (children.length > 0 && children[0].type === 'text') {
resolvedText = children[0].value;
}
else {
resolvedText =
linkType === 'github-acct'
? id.slice(1)
: linkType === 'custom-url'
? processUrl(id)
: id;
}
// get url & img
let resolvedUrl = '';
let resolvedImg = '';
if (linkType === 'github-acct') {
const acct = id.slice(1);
resolvedUrl =
url ??
(tab
? isGithubOrg
? `https://github.com/orgs/${acct}/${resolvedTab}`
: `https://github.com/${acct}?tab=${resolvedTab}`
: `https://github.com/${acct}`);
resolvedImg = img || `https://github.com/${acct}.png`;
}
if (linkType === 'github-repo') {
const match = githubRepoRegex.exec(id);
resolvedUrl = url || `https://github.com/${id}`;
resolvedImg = img || `https://github.com/${match?.[1]}.png`;
}
if (linkType === 'npm-pkg') {
resolvedUrl =
url || tab
? `https://www.npmjs.com/package/${id}?activeTab=${resolvedTab}`
: `https://www.npmjs.com/package/${id}`;
resolvedImg =
img ||
faviconUrl.replace('{domain}', new URL('https://www.npmjs.com').hostname);
}
if (linkType === 'custom-url') {
resolvedUrl = url || id;
resolvedImg =
img || faviconUrl.replace('{domain}', new URL(resolvedUrl).hostname);
}
// handle props
const aProperties = createIfNeeded(aProps, node);
const aNewProperties = mergeProps({ ...defaultLinkProps, ...aProperties }, { 'data-link': linkType }, attrs);
const imgProperties = createIfNeeded(imgProps, node);
// update node
data.hName = 'a';
data.hProperties = { href: resolvedUrl, ...aNewProperties };
data.hChildren = [
{
type: 'element',
tagName: 'img',
properties: {
src: resolvedImg,
alt: '',
...imgProperties,
},
children: [],
},
{
type: 'text',
value: resolvedText,
},
];
}