reallysimpledocs 1.0.3 → 1.0.5

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reallysimpledocs",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.js",
@@ -1,12 +1,14 @@
1
1
  import rehypeParse from "rehype-parse";
2
2
  import rehypeStringify from "rehype-stringify";
3
3
  import { unified } from "unified";
4
+ import GithubSlugger from "github-slugger";
4
5
 
5
6
  const containerClass = ["table-container", "scrollbar", "my-6"];
6
7
  const tableClass = ["table"];
7
8
 
8
9
  export function rehypeBasecoatTables() {
9
10
  return (tree) => {
11
+ transformHeadings(tree);
10
12
  transformTables(tree);
11
13
  transformCodeBlocks(tree);
12
14
  };
@@ -41,6 +43,42 @@ function transformTables(parent) {
41
43
  }
42
44
  }
43
45
 
46
+ function transformHeadings(parent, slugger = new GithubSlugger()) {
47
+ if (!Array.isArray(parent?.children)) return;
48
+
49
+ for (const child of parent.children) {
50
+ if (isHeading(child)) {
51
+ addHeadingAnchor(child, slugger);
52
+ continue;
53
+ }
54
+ transformHeadings(child, slugger);
55
+ }
56
+ }
57
+
58
+ function addHeadingAnchor(node, slugger) {
59
+ if (node.children?.some((child) => isElement(child, "a") && classList(child).includes("header-anchor"))) {
60
+ return;
61
+ }
62
+
63
+ node.properties ||= {};
64
+ const id = String(node.properties.id || "") || slugger.slug(nodeText(node));
65
+ if (!id) return;
66
+ node.properties.id = id;
67
+ node.properties.tabIndex ??= -1;
68
+ node.children = [
69
+ {
70
+ type: "element",
71
+ tagName: "a",
72
+ properties: { className: ["header-anchor"], href: `#${id}` },
73
+ children: node.children || [],
74
+ },
75
+ ];
76
+ }
77
+
78
+ function isHeading(node) {
79
+ return /^h[1-6]$/.test(node?.tagName || "");
80
+ }
81
+
44
82
  function transformCodeBlocks(parent) {
45
83
  if (!Array.isArray(parent?.children)) return;
46
84
 
@@ -53,6 +91,12 @@ function transformCodeBlocks(parent) {
53
91
  }
54
92
  }
55
93
 
94
+ function nodeText(node) {
95
+ if (node?.type === "text") return node.value || "";
96
+ if (!Array.isArray(node?.children)) return "";
97
+ return node.children.map(nodeText).join("");
98
+ }
99
+
56
100
  function isElement(node, tagName) {
57
101
  return node?.type === "element" && node.tagName === tagName;
58
102
  }
@@ -12,7 +12,7 @@
12
12
  > h4 {
13
13
  @apply sm:text-lg my-6 scroll-m-18 font-semibold tracking-tight;
14
14
 
15
- a {
15
+ > a.header-anchor {
16
16
  @apply no-underline;
17
17
  }
18
18
  }
@@ -2,9 +2,11 @@
2
2
  import * as lucideIcons from "lucide-static";
3
3
  import { addSvgClass } from "../../lib/html.js";
4
4
 
5
- const { type, title = "Note", icon: iconName, class: className = "" } = Astro.props;
5
+ const { type, title = "Note", icon: iconName, action, class: className = "" } = Astro.props;
6
6
  const normalizedType = String(type || "").toLowerCase();
7
7
  const calloutType = normalizedType === "warning" || normalizedType === "destructive" ? normalizedType : "";
8
+ const actionLabel = typeof action?.label === "string" ? action.label.trim() : "";
9
+ const actionHref = typeof action?.href === "string" ? action.href.trim() : "";
8
10
 
9
11
  function resolveNamedIcon(name) {
10
12
  if (!name || typeof name !== "string") return "";
@@ -31,4 +33,13 @@ const heading = title;
31
33
  <section>
32
34
  <slot />
33
35
  </section>
36
+ {
37
+ actionLabel && actionHref && (
38
+ <footer>
39
+ <a class="btn" data-variant="outline" data-size="sm" href={actionHref}>
40
+ {actionLabel}
41
+ </a>
42
+ </footer>
43
+ )
44
+ }
34
45
  </div>
@@ -3,7 +3,7 @@ import { codeToHtml } from "shiki";
3
3
  export async function highlightCode(code, lang = "text") {
4
4
  const value = String(code || "");
5
5
  const language = String(lang || "text").toLowerCase() === "mdx" ? "tsx" : String(lang || "text");
6
- return codeToHtml(value, {
6
+ const html = await codeToHtml(value, {
7
7
  lang: language,
8
8
  themes: {
9
9
  light: "github-light",
@@ -11,4 +11,15 @@ export async function highlightCode(code, lang = "text") {
11
11
  },
12
12
  defaultColor: false,
13
13
  });
14
+ return addPreClass(html, "scrollbar");
15
+ }
16
+
17
+ function addPreClass(html, className) {
18
+ return String(html).replace(/<pre(\s[^>]*)?>/, (match, attrs = "") => {
19
+ const classMatch = attrs.match(/\sclass=(["'])(.*?)\1/s);
20
+ if (!classMatch) return `<pre class="${className}"${attrs}>`;
21
+ const classes = new Set(classMatch[2].split(/\s+/).filter(Boolean));
22
+ classes.add(className);
23
+ return match.replace(classMatch[0], ` class=${classMatch[1]}${[...classes].join(" ")}${classMatch[1]}`);
24
+ });
14
25
  }
@@ -94,12 +94,14 @@ function serializePreview(attrs, children, context = new Map()) {
94
94
 
95
95
  function serializeCallout(attrs, children, context = new Map()) {
96
96
  const title = getStringAttr(attrs, "title") || calloutTitle(getStringAttr(attrs, "type"));
97
+ const action = getObjectAttr(attrs, "action");
97
98
  const body = normalizeMdxComponents(children, context)
98
99
  .trim()
99
100
  .split("\n")
100
101
  .map((line) => `> ${line}`)
101
102
  .join("\n");
102
- return `\n\n> **${title}**\n${body ? `${body}\n` : ""}\n`;
103
+ const footer = action?.label && action?.href ? `> [${action.label}](${action.href})\n` : "";
104
+ return `\n\n> **${title}**\n${body ? `${body}\n` : ""}${footer}`;
103
105
  }
104
106
 
105
107
  function serializeSteps(_attrs, children, context = new Map()) {
@@ -137,6 +139,14 @@ function getExpressionAttr(attrs, name) {
137
139
  return String(attrs || "").match(new RegExp(`${name}=\\{([A-Za-z_$][\\w$]*)\\}`))?.[1] || "";
138
140
  }
139
141
 
142
+ function getObjectAttr(attrs, name) {
143
+ const value = String(attrs || "").match(new RegExp(`${name}=\\{\\{([\\s\\S]*?)\\}\\}`))?.[1] || "";
144
+ if (!value) return null;
145
+ return Object.fromEntries(
146
+ [...value.matchAll(/([A-Za-z_$][\w$]*)\s*:\s*["']([^"']*)["']/g)].map(([, key, item]) => [key, item]),
147
+ );
148
+ }
149
+
140
150
  function getCodeAttr(attrs, context = new Map()) {
141
151
  const direct = String(attrs || "").match(/\bcode=\{`([\s\S]*?)`\}/)?.[1] || "";
142
152
  if (direct) return direct.replace(/\\n/g, "\n").replace(/\\`/g, "`");