radiant-docs 0.1.21 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import path from "path";
|
|
|
9
9
|
import { getConfig, validateMdxContent } from "./src/lib/validation";
|
|
10
10
|
import remarkCodeBlockComponent from "./src/lib/mdx/remark-code-block-component";
|
|
11
11
|
import remarkDemoteH1 from "./src/lib/mdx/remark-demote-h1";
|
|
12
|
+
import rehypeExternalLinks from "./src/lib/mdx/rehype-external-links";
|
|
12
13
|
import remarkGfm from "remark-gfm";
|
|
13
14
|
import rehypeSlug from "rehype-slug";
|
|
14
15
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
|
@@ -273,6 +274,7 @@ export default defineConfig({
|
|
|
273
274
|
remarkPlugins: [remarkGfm, remarkDemoteH1, remarkCodeBlockComponent],
|
|
274
275
|
rehypePlugins: [
|
|
275
276
|
rehypeSlug,
|
|
277
|
+
rehypeExternalLinks,
|
|
276
278
|
[
|
|
277
279
|
rehypeAutolinkHeadings,
|
|
278
280
|
{
|
|
@@ -103,9 +103,15 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
103
103
|
const filename =
|
|
104
104
|
itemElement.getAttribute("data-rd-tab-filename") ??
|
|
105
105
|
`file-name-${index + 1}.txt`;
|
|
106
|
+
const hideTabIcon =
|
|
107
|
+
itemElement.getAttribute("data-rd-tab-hide-icon") === "true";
|
|
106
108
|
const tabIconTemplate = itemElement.querySelector(
|
|
107
109
|
"template[data-rd-code-group-tab-icon]",
|
|
108
110
|
);
|
|
111
|
+
const hasTabIcon =
|
|
112
|
+
!hideTabIcon &&
|
|
113
|
+
!!tabIconTemplate &&
|
|
114
|
+
tabIconTemplate.innerHTML.trim().length > 0;
|
|
109
115
|
|
|
110
116
|
const tabWrapper = document.createElement("div");
|
|
111
117
|
tabWrapper.className = "relative z-10 text-xs font-medium";
|
|
@@ -113,25 +119,25 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
113
119
|
const tabButton = document.createElement("button");
|
|
114
120
|
tabButton.type = "button";
|
|
115
121
|
tabButton.className =
|
|
116
|
-
|
|
122
|
+
`relative inline-flex h-9 items-center border-0 bg-transparent px-3 py-1.5 text-xs font-medium text-neutral-600 transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer ${hasTabIcon ? "gap-2" : ""}`;
|
|
117
123
|
tabButton.setAttribute("aria-label", filename);
|
|
118
124
|
tabButton.setAttribute("data-rd-code-group-tab", String(index));
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
let iconContainer = null;
|
|
127
|
+
if (hasTabIcon) {
|
|
128
|
+
iconContainer = document.createElement("span");
|
|
129
|
+
iconContainer.className =
|
|
130
|
+
"pointer-events-none inline-flex shrink-0 items-center rounded-[4px] transition-opacity duration-150";
|
|
125
131
|
iconContainer.innerHTML = tabIconTemplate.innerHTML;
|
|
126
|
-
} else {
|
|
127
|
-
iconContainer.classList.add("hidden");
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
const labelElement = document.createElement("span");
|
|
131
135
|
labelElement.className = "whitespace-pre leading-none";
|
|
132
136
|
labelElement.textContent = filename;
|
|
133
137
|
|
|
134
|
-
|
|
138
|
+
if (iconContainer) {
|
|
139
|
+
tabButton.appendChild(iconContainer);
|
|
140
|
+
}
|
|
135
141
|
tabButton.appendChild(labelElement);
|
|
136
142
|
tabWrapper.appendChild(tabButton);
|
|
137
143
|
tabsElement.appendChild(tabWrapper);
|
|
@@ -169,7 +175,7 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
169
175
|
tabButton.classList.toggle("text-neutral-900", isActive);
|
|
170
176
|
tabButton.classList.toggle("text-neutral-600", !isActive);
|
|
171
177
|
|
|
172
|
-
if (iconContainer
|
|
178
|
+
if (!iconContainer) return;
|
|
173
179
|
iconContainer.classList.toggle("opacity-100", isActive);
|
|
174
180
|
iconContainer.classList.toggle("opacity-80", !isActive);
|
|
175
181
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from "astro/types";
|
|
3
3
|
import { validateProps } from "../../lib/component-error";
|
|
4
|
+
import { renderMarkdown } from "../../lib/utils";
|
|
4
5
|
|
|
5
6
|
interface Props extends HTMLAttributes<"img"> {
|
|
6
7
|
src: string;
|
|
@@ -8,6 +9,11 @@ interface Props extends HTMLAttributes<"img"> {
|
|
|
8
9
|
|
|
9
10
|
const { title, ...attrs } = Astro.props;
|
|
10
11
|
|
|
12
|
+
const captionHtml =
|
|
13
|
+
typeof title === "string" && title.trim().length > 0
|
|
14
|
+
? (await renderMarkdown(title)).replace(/^<p>([\s\S]*)<\/p>\n?$/, "$1")
|
|
15
|
+
: "";
|
|
16
|
+
|
|
11
17
|
validateProps(
|
|
12
18
|
"Image",
|
|
13
19
|
Astro.props as Record<string, any>,
|
|
@@ -96,7 +102,7 @@ validateProps(
|
|
|
96
102
|
{
|
|
97
103
|
title && (
|
|
98
104
|
<figcaption class="mt-2 text-center text-sm text-neutral-500 dark:text-neutral-400 font-medium whitespace-pre-line leading-relaxed px-2">
|
|
99
|
-
{
|
|
105
|
+
<span set:html={captionHtml} />
|
|
100
106
|
</figcaption>
|
|
101
107
|
)
|
|
102
108
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Plugin } from "unified";
|
|
2
|
+
|
|
3
|
+
type HastNode = {
|
|
4
|
+
type?: string;
|
|
5
|
+
tagName?: string;
|
|
6
|
+
properties?: Record<string, unknown>;
|
|
7
|
+
children?: HastNode[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function isExternalHttpLink(href: unknown): href is string {
|
|
11
|
+
return typeof href === "string" && /^https?:\/\//i.test(href);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeRelTokens(value: unknown): string[] {
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value
|
|
17
|
+
.flatMap((entry) =>
|
|
18
|
+
typeof entry === "string" ? entry.split(/\s+/) : [],
|
|
19
|
+
)
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value === "string") {
|
|
24
|
+
return value.split(/\s+/).filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function visitNodes(node: HastNode, visitor: (node: HastNode) => void): void {
|
|
31
|
+
visitor(node);
|
|
32
|
+
if (!Array.isArray(node.children)) return;
|
|
33
|
+
for (const child of node.children) {
|
|
34
|
+
visitNodes(child, visitor);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const rehypeExternalLinks: Plugin<[], HastNode> = () => {
|
|
39
|
+
return (tree) => {
|
|
40
|
+
visitNodes(tree, (node) => {
|
|
41
|
+
if (node.type !== "element" || node.tagName !== "a") return;
|
|
42
|
+
|
|
43
|
+
const props = (node.properties ??= {});
|
|
44
|
+
const href = props.href;
|
|
45
|
+
if (!isExternalHttpLink(href)) return;
|
|
46
|
+
|
|
47
|
+
props.target = "_blank";
|
|
48
|
+
|
|
49
|
+
const relTokens = normalizeRelTokens(props.rel);
|
|
50
|
+
if (!relTokens.includes("noopener")) relTokens.push("noopener");
|
|
51
|
+
if (!relTokens.includes("noreferrer")) relTokens.push("noreferrer");
|
|
52
|
+
props.rel = relTokens.join(" ");
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default rehypeExternalLinks;
|
|
@@ -3,6 +3,7 @@ import remarkParse from "remark-parse";
|
|
|
3
3
|
import remarkGfm from "remark-gfm";
|
|
4
4
|
import remarkRehype from "remark-rehype";
|
|
5
5
|
import rehypeStringify from "rehype-stringify";
|
|
6
|
+
import rehypeExternalLinks from "./mdx/rehype-external-links";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
|
|
8
9
|
export function slugify(text: string): string {
|
|
@@ -25,6 +26,7 @@ export async function renderMarkdown(markdown: string): Promise<string> {
|
|
|
25
26
|
.use(remarkParse)
|
|
26
27
|
.use(remarkGfm) // GitHub Flavored Markdown (for lists, etc.)
|
|
27
28
|
.use(remarkRehype, { allowDangerousHtml: false })
|
|
29
|
+
.use(rehypeExternalLinks)
|
|
28
30
|
.use(rehypeStringify)
|
|
29
31
|
.process(markdown);
|
|
30
32
|
|