@brillout/docpress 0.15.10-commit-f89d08f → 0.15.10-commit-05c2883

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.
@@ -13,8 +13,7 @@ function useSelectCodeLang() {
13
13
  setCodeLangSelected(getCodeLangStorage())
14
14
  }
15
15
  const updateStateOnStorageEvent = (event: StorageEvent) => {
16
- if (event.key === storageKey) return
17
- updateState()
16
+ if (event.key === storageKey) updateState()
18
17
  }
19
18
 
20
19
  const getCodeLangStorage = () => {
@@ -1,61 +1,78 @@
1
- /* Wrapper */
2
- .code-snippet,
3
1
  .code-snippets {
4
2
  position: relative;
3
+
5
4
  &:hover {
6
- button,
7
- select {
5
+ .copy-button,
6
+ .code-lang-toggle {
8
7
  opacity: 1;
9
8
  }
10
9
  }
11
- }
12
10
 
13
- /* Language select */
14
- .code-lang-select {
15
- right: 42px;
16
- padding-left: 6px;
17
- padding-right: 21px;
18
- font-size: 14px;
19
- background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 1'><path fill='%23666' d='M0 0l1 1 1-1z'/></svg>");
20
- background-repeat: no-repeat;
21
- background-position: right 7px center;
22
- background-size: 10px;
23
- /* Remove default arrow in some browsers */
24
- appearance: none;
25
- -webkit-appearance: none;
26
- -moz-appearance: none;
27
- }
11
+ /* Hide language toggle for YAML */
12
+ &:has(code[data-language='yaml']) .code-lang-toggle {
13
+ display: none !important;
14
+ }
15
+
16
+ /* Language toggle styles */
17
+ .code-lang-toggle {
18
+ position: absolute !important;
19
+ top: 10px;
20
+ right: 42px;
21
+ z-index: 3;
22
+
23
+ /* Checkbox appearance reset */
24
+ appearance: none;
25
+ -webkit-appearance: none;
26
+ -moz-appearance: none;
27
+
28
+ margin: 0;
29
+ padding: 0 4px;
30
+ height: 25px;
31
+ display: flex;
32
+ background-color: #f7f7f7;
33
+ opacity: 0;
34
+ transition: opacity 0.5s ease-in-out, background-color 0.4s ease-in-out;
35
+
36
+ &:not(:hover) {
37
+ background-color: #eee;
38
+ }
39
+
40
+ /* Toggle Labels */
41
+ &::before,
42
+ &::after {
43
+ width: 24px;
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ }
48
+
49
+ &::before {
50
+ content: 'JS';
51
+ }
52
+
53
+ &::after {
54
+ content: 'TS';
55
+ border-left: none;
56
+ opacity: 0.3;
57
+ }
28
58
 
29
- /* Copy button */
30
- .copy-button {
31
- display: inline-flex;
32
- align-items: center;
33
- right: 10px;
34
- width: 30px;
35
- & svg {
36
- width: 100%;
37
- height: 100%;
38
- stroke-linecap: round;
39
- stroke-linejoin: round;
40
- fill: none;
59
+ &:checked {
60
+ &::before {
61
+ opacity: 0.3;
62
+ }
63
+
64
+ &::after {
65
+ opacity: 1;
66
+ }
67
+ }
68
+ }
69
+
70
+ /* Code block visibility based on toggle */
71
+ &:has(.code-lang-toggle:checked) figure:first-of-type {
72
+ display: none;
41
73
  }
42
- }
43
74
 
44
- /* Common style */
45
- .copy-button,
46
- .code-lang-select {
47
- position: absolute !important;
48
- height: 25px;
49
- top: 10px;
50
- z-index: 3;
51
- outline: none;
52
- cursor: pointer;
53
- border: 1px solid #ccc;
54
- border-radius: 5px;
55
- background-color: #f7f7f7;
56
- opacity: 0;
57
- transition: opacity 0.8s ease-in-out, background-color 0.4s ease-in-out;
58
- &:not(:hover) {
59
- background-color: #eee;
75
+ &:has(.code-lang-toggle:not(:checked)) figure:last-of-type {
76
+ display: none;
60
77
  }
61
78
  }
@@ -3,9 +3,8 @@ export { TypescriptOnly }
3
3
 
4
4
  // Internal
5
5
  export { CodeSnippets }
6
- export { CodeSnippet }
7
6
 
8
- import React, { useState } from 'react'
7
+ import React, { useEffect, useRef } from 'react'
9
8
  import { useSelectCodeLang } from './CodeSnippets/useSelectCodeLang'
10
9
  import './CodeSnippets.css'
11
10
 
@@ -17,91 +16,35 @@ function TypescriptOnly({ children }: { children: React.ReactNode }) {
17
16
 
18
17
  function CodeSnippets({ children }: { children: React.ReactNode }) {
19
18
  const [codeLangSelected, selectCodeLang] = useSelectCodeLang()
19
+ const prevPositionRef = useRef<null | { top: number; el: Element }>(null)
20
+
21
+ // Restores the scroll position of the toggle element after toggling languages.
22
+ useEffect(() => {
23
+ if (!prevPositionRef.current) return
24
+ const { top, el } = prevPositionRef.current
25
+ const delta = el.getBoundingClientRect().top - top
26
+ if (delta !== 0) {
27
+ window.scrollBy(0, delta)
28
+ }
29
+ prevPositionRef.current = null
30
+ }, [codeLangSelected])
31
+
20
32
  return (
21
33
  <div className="code-snippets">
22
- <form style={{ position: 'relative' }}>
23
- <select className="code-lang-select" onChange={onChange} value={codeLangSelected}>
24
- <option value="js">JavaScript</option>
25
- <option value="ts">TypeScript</option>
26
- </select>
27
- </form>
34
+ <input
35
+ type="checkbox"
36
+ name="code-lang-toggle"
37
+ className="code-lang-toggle raised"
38
+ checked={codeLangSelected === 'ts'}
39
+ onChange={onChange}
40
+ title="Toggle language"
41
+ />
28
42
  {children}
29
43
  </div>
30
44
  )
31
- function onChange(e: React.ChangeEvent<HTMLSelectElement>) {
32
- selectCodeLang(e.target.value)
45
+ function onChange(e: React.ChangeEvent<HTMLInputElement>) {
46
+ const element = e.target
47
+ prevPositionRef.current = { top: element.getBoundingClientRect().top, el: element }
48
+ selectCodeLang(element.checked ? 'ts' : 'js')
33
49
  }
34
50
  }
35
-
36
- function CodeSnippet({
37
- children,
38
- codeLang,
39
- tsOnly = false,
40
- }: { children: React.ReactNode; codeLang: string; tsOnly: boolean }) {
41
- const [codeLangSelected] = useSelectCodeLang()
42
-
43
- const displayStyle = tsOnly ? {} : { display: codeLangSelected === codeLang ? 'block' : 'none' }
44
-
45
- return (
46
- <div className="code-snippet" style={{ ...displayStyle }}>
47
- <CopyButton />
48
- {children}
49
- </div>
50
- )
51
- }
52
-
53
- function CopyButton() {
54
- const [isSuccess, setIsSuccess] = useState(null as null | boolean)
55
- const onCopy = (success: boolean) => {
56
- setIsSuccess(success)
57
- setTimeout(() => {
58
- setIsSuccess(null)
59
- }, 900)
60
- }
61
- const tooltip = isSuccess === null ? 'Copy to clipboard' : isSuccess ? 'Copied' : 'Failed'
62
- const text =
63
- isSuccess === null ? (
64
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
65
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
66
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
67
- </svg>
68
- ) : isSuccess ? (
69
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="#28a745" strokeWidth="3">
70
- <polyline points="20 6 9 17 4 12" />
71
- </svg>
72
- ) : (
73
- '❌'
74
- )
75
- return (
76
- <button className="copy-button" aria-label={tooltip} data-label-position="top" type="button" onClick={onClick}>
77
- {text}
78
- </button>
79
- )
80
- async function onClick(e: React.MouseEvent<HTMLButtonElement>) {
81
- let success: boolean
82
- try {
83
- await copyToClipboard(e)
84
- success = true
85
- } catch (error) {
86
- console.error(error)
87
- success = false
88
- }
89
- onCopy(success)
90
- }
91
- }
92
-
93
- async function copyToClipboard(e: React.MouseEvent<HTMLButtonElement>) {
94
- const figureEl = e.currentTarget.nextElementSibling
95
- if (figureEl?.tagName === 'FIGURE') {
96
- let text = figureEl.textContent ?? ''
97
- text = removeTrailingWhitespaces(text)
98
- await navigator.clipboard.writeText(text)
99
- }
100
- }
101
-
102
- function removeTrailingWhitespaces(text: string) {
103
- return text
104
- .split('\n')
105
- .map((line) => line.trimEnd())
106
- .join('\n')
107
- }
@@ -0,0 +1,51 @@
1
+ pre > code {
2
+ /*
3
+ background-color: #f4f4f4;
4
+ 0.043137255 = 1 - (#f4 / #ff)
5
+ */
6
+ background: rgba(0, 0, 0, 0.043137255);
7
+ font-size: 1em;
8
+ }
9
+
10
+ /* Copy button */
11
+ pre {
12
+ &:has(.copy-button) {
13
+ position: relative;
14
+ }
15
+
16
+ &:hover {
17
+ .copy-button {
18
+ opacity: 1;
19
+ }
20
+ }
21
+
22
+ .copy-button {
23
+ position: absolute !important;
24
+ top: 10px;
25
+ right: 10px;
26
+ z-index: 3;
27
+ margin: 0;
28
+ height: 25px;
29
+ width: 30px;
30
+ background-color: #f7f7f7;
31
+ opacity: 0;
32
+ transition: opacity 0.5s ease-in-out, background-color 0.4s ease-in-out;
33
+
34
+ &:not(:hover) {
35
+ background-color: #eee;
36
+ }
37
+
38
+ & svg {
39
+ width: 100%;
40
+ height: 100%;
41
+ fill: none;
42
+ stroke-linecap: round;
43
+ stroke-linejoin: round;
44
+ }
45
+ }
46
+ }
47
+
48
+ /* Workaround for shiki regression */
49
+ pre > code:not([data-language]) {
50
+ padding: 16px !important;
51
+ }
@@ -0,0 +1,79 @@
1
+ export { Pre }
2
+
3
+ import React, { ComponentPropsWithoutRef, useState } from 'react'
4
+ /* Importing it here chokes the tests. I don't know why.
5
+ import './Pre.css'
6
+ //*/
7
+
8
+ function Pre({ children, ...props }: ComponentPropsWithoutRef<'pre'> & { 'hide-menu'?: string }) {
9
+ return (
10
+ <pre {...props}>
11
+ {!props['hide-menu'] && <CopyButton />}
12
+ {children}
13
+ </pre>
14
+ )
15
+ }
16
+
17
+ function CopyButton() {
18
+ const [isSuccess, setIsSuccess] = useState(null as null | boolean)
19
+ const onCopy = (success: boolean) => {
20
+ setIsSuccess(success)
21
+ setTimeout(() => {
22
+ setIsSuccess(null)
23
+ }, 900)
24
+ }
25
+ const tooltip = isSuccess === null ? 'Copy to clipboard' : isSuccess ? 'Copied' : 'Failed'
26
+ const icon =
27
+ isSuccess === null ? (
28
+ // Copy icon
29
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
30
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
31
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
32
+ </svg>
33
+ ) : isSuccess ? (
34
+ // Green checkmark
35
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="#28a745" strokeWidth="3">
36
+ <polyline points="20 6 9 17 4 12" />
37
+ </svg>
38
+ ) : (
39
+ '❌'
40
+ )
41
+ return (
42
+ <button
43
+ className="copy-button raised"
44
+ aria-label={tooltip}
45
+ data-label-position="top"
46
+ type="button"
47
+ onClick={onClick}
48
+ >
49
+ {icon}
50
+ </button>
51
+ )
52
+ async function onClick(e: React.MouseEvent<HTMLButtonElement>) {
53
+ let success: boolean
54
+ try {
55
+ await copyToClipboard(e)
56
+ success = true
57
+ } catch (error) {
58
+ console.error(error)
59
+ success = false
60
+ }
61
+ onCopy(success)
62
+ }
63
+ }
64
+
65
+ async function copyToClipboard(e: React.MouseEvent<HTMLButtonElement>) {
66
+ const codeEl = e.currentTarget.nextElementSibling
67
+ if (codeEl?.tagName === 'CODE') {
68
+ let text = codeEl.textContent ?? ''
69
+ text = removeTrailingWhitespaces(text)
70
+ await navigator.clipboard.writeText(text)
71
+ }
72
+ }
73
+
74
+ function removeTrailingWhitespaces(text: string) {
75
+ return text
76
+ .split('\n')
77
+ .map((line) => line.trimEnd())
78
+ .join('\n')
79
+ }
@@ -0,0 +1,13 @@
1
+ export { useMDXComponents }
2
+
3
+ import React from 'react'
4
+ import type { UseMdxComponents } from '@mdx-js/mdx'
5
+ import { Pre } from './Pre.js'
6
+ import { CodeSnippets } from './CodeSnippets.js'
7
+
8
+ const useMDXComponents: UseMdxComponents = () => {
9
+ return {
10
+ CodeSnippets,
11
+ pre: (props) => <Pre {...props} />,
12
+ }
13
+ }
package/css/button.css CHANGED
@@ -5,3 +5,26 @@ button,
5
5
  border-radius: 5px;
6
6
  cursor: pointer;
7
7
  }
8
+
9
+ /* Raised button */
10
+ .raised {
11
+ cursor: pointer;
12
+ border-radius: 5px;
13
+ border-style: solid;
14
+ border-width: 1px 2px 2px 1px;
15
+ border-color: hsl(0, 0%, 75%) hsl(0, 0%, 72%) hsl(0, 0%, 72%) hsl(0, 0%, 75%);
16
+
17
+ &:hover {
18
+ border-color: hsl(0, 0%, 72%) hsl(0, 0%, 66%) hsl(0, 0%, 66%) hsl(0, 0%, 72%);
19
+ }
20
+
21
+ &:active {
22
+ border-width: 2px 1px 1px 2px;
23
+ border-color: hsl(0, 0%, 66%) hsl(0, 0%, 72%) hsl(0, 0%, 72%) hsl(0, 0%, 66%);
24
+ }
25
+
26
+ &:disabled {
27
+ border-width: 1px;
28
+ border-color: hsl(0, 0%, 72%);
29
+ }
30
+ }
package/css/code.css CHANGED
@@ -2,29 +2,11 @@
2
2
  @import './code/block.css';
3
3
  @import './code/diff.css';
4
4
 
5
- code {
6
- border-radius: 4px;
7
- }
8
- pre {
9
- background: none !important;
10
- }
5
+ /* For code blocks, see Pre.css instead */
11
6
 
12
- /* Inline */
7
+ /* Inline <code> */
13
8
  code {
9
+ border-radius: 4px;
14
10
  background: rgba(0, 0, 0, 0.063137255);
15
11
  font-size: 1.1em;
16
12
  }
17
-
18
- /* Block */
19
- pre > code {
20
- /*
21
- background-color: #f4f4f4;
22
- 0.043137255 = 1 - (#f4 / #ff)
23
- */
24
- background: rgba(0, 0, 0, 0.043137255);
25
- font-size: 1em;
26
- }
27
- /* Workaround for shiki regression */
28
- pre > code:not([data-language]) {
29
- padding: 16px !important;
30
- }
package/css/index.css CHANGED
@@ -7,4 +7,5 @@
7
7
  @import './code.css';
8
8
  @import './table.css';
9
9
  @import './tooltip.css';
10
+ @import '../components/Pre.css';
10
11
  @import '@docsearch/css';
@@ -11,8 +11,7 @@ function useSelectCodeLang() {
11
11
  };
12
12
  const updateStateOnStorageEvent = (event) => {
13
13
  if (event.key === storageKey)
14
- return;
15
- updateState();
14
+ updateState();
16
15
  };
17
16
  const getCodeLangStorage = () => {
18
17
  try {
@@ -1,6 +1,5 @@
1
1
  export { TypescriptOnly };
2
2
  export { CodeSnippets };
3
- export { CodeSnippet };
4
3
  import React from 'react';
5
4
  import './CodeSnippets.css';
6
5
  /** Only show if TypeScript is selected */
@@ -10,8 +9,3 @@ declare function TypescriptOnly({ children }: {
10
9
  declare function CodeSnippets({ children }: {
11
10
  children: React.ReactNode;
12
11
  }): React.JSX.Element;
13
- declare function CodeSnippet({ children, codeLang, tsOnly, }: {
14
- children: React.ReactNode;
15
- codeLang: string;
16
- tsOnly: boolean;
17
- }): React.JSX.Element;
@@ -2,8 +2,7 @@
2
2
  export { TypescriptOnly };
3
3
  // Internal
4
4
  export { CodeSnippets };
5
- export { CodeSnippet };
6
- import React, { useState } from 'react';
5
+ import React, { useEffect, useRef } from 'react';
7
6
  import { useSelectCodeLang } from './CodeSnippets/useSelectCodeLang';
8
7
  import './CodeSnippets.css';
9
8
  /** Only show if TypeScript is selected */
@@ -13,61 +12,24 @@ function TypescriptOnly({ children }) {
13
12
  }
14
13
  function CodeSnippets({ children }) {
15
14
  const [codeLangSelected, selectCodeLang] = useSelectCodeLang();
15
+ const prevPositionRef = useRef(null);
16
+ // Restores the scroll position of the toggle element after toggling languages.
17
+ useEffect(() => {
18
+ if (!prevPositionRef.current)
19
+ return;
20
+ const { top, el } = prevPositionRef.current;
21
+ const delta = el.getBoundingClientRect().top - top;
22
+ if (delta !== 0) {
23
+ window.scrollBy(0, delta);
24
+ }
25
+ prevPositionRef.current = null;
26
+ }, [codeLangSelected]);
16
27
  return (React.createElement("div", { className: "code-snippets" },
17
- React.createElement("form", { style: { position: 'relative' } },
18
- React.createElement("select", { className: "code-lang-select", onChange: onChange, value: codeLangSelected },
19
- React.createElement("option", { value: "js" }, "JavaScript"),
20
- React.createElement("option", { value: "ts" }, "TypeScript"))),
28
+ React.createElement("input", { type: "checkbox", name: "code-lang-toggle", className: "code-lang-toggle raised", checked: codeLangSelected === 'ts', onChange: onChange, title: "Toggle language" }),
21
29
  children));
22
30
  function onChange(e) {
23
- selectCodeLang(e.target.value);
31
+ const element = e.target;
32
+ prevPositionRef.current = { top: element.getBoundingClientRect().top, el: element };
33
+ selectCodeLang(element.checked ? 'ts' : 'js');
24
34
  }
25
35
  }
26
- function CodeSnippet({ children, codeLang, tsOnly = false, }) {
27
- const [codeLangSelected] = useSelectCodeLang();
28
- const displayStyle = tsOnly ? {} : { display: codeLangSelected === codeLang ? 'block' : 'none' };
29
- return (React.createElement("div", { className: "code-snippet", style: { ...displayStyle } },
30
- React.createElement(CopyButton, null),
31
- children));
32
- }
33
- function CopyButton() {
34
- const [isSuccess, setIsSuccess] = useState(null);
35
- const onCopy = (success) => {
36
- setIsSuccess(success);
37
- setTimeout(() => {
38
- setIsSuccess(null);
39
- }, 900);
40
- };
41
- const tooltip = isSuccess === null ? 'Copy to clipboard' : isSuccess ? 'Copied' : 'Failed';
42
- const text = isSuccess === null ? (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: "2" },
43
- React.createElement("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
44
- React.createElement("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" }))) : isSuccess ? (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", stroke: "#28a745", strokeWidth: "3" },
45
- React.createElement("polyline", { points: "20 6 9 17 4 12" }))) : ('❌');
46
- return (React.createElement("button", { className: "copy-button", "aria-label": tooltip, "data-label-position": "top", type: "button", onClick: onClick }, text));
47
- async function onClick(e) {
48
- let success;
49
- try {
50
- await copyToClipboard(e);
51
- success = true;
52
- }
53
- catch (error) {
54
- console.error(error);
55
- success = false;
56
- }
57
- onCopy(success);
58
- }
59
- }
60
- async function copyToClipboard(e) {
61
- const figureEl = e.currentTarget.nextElementSibling;
62
- if (figureEl?.tagName === 'FIGURE') {
63
- let text = figureEl.textContent ?? '';
64
- text = removeTrailingWhitespaces(text);
65
- await navigator.clipboard.writeText(text);
66
- }
67
- }
68
- function removeTrailingWhitespaces(text) {
69
- return text
70
- .split('\n')
71
- .map((line) => line.trimEnd())
72
- .join('\n');
73
- }
@@ -0,0 +1,19 @@
1
+ export { rehypeMetaToProps };
2
+ import type { Root } from 'hast';
3
+ /**
4
+ * Rehype plugin to extract metadata from `<code>` blocks in markdown
5
+ * and attach them as props to the parent `<pre>` element.
6
+ *
7
+ * This allows using those props inside a custom `<Pre>` component.
8
+ *
9
+ * Example:
10
+ * ~~~mdx
11
+ * ```js foo="bar" hide_copy='true'
12
+ * export function add(a, b) {
13
+ * return a + b
14
+ * }
15
+ * ```
16
+ * ~~~
17
+ * These props are then added to the `<pre>` element
18
+ */
19
+ declare function rehypeMetaToProps(): (tree: Root) => void;
@@ -0,0 +1,62 @@
1
+ export { rehypeMetaToProps };
2
+ import { visit } from 'unist-util-visit';
3
+ /**
4
+ * Rehype plugin to extract metadata from `<code>` blocks in markdown
5
+ * and attach them as props to the parent `<pre>` element.
6
+ *
7
+ * This allows using those props inside a custom `<Pre>` component.
8
+ *
9
+ * Example:
10
+ * ~~~mdx
11
+ * ```js foo="bar" hide_copy='true'
12
+ * export function add(a, b) {
13
+ * return a + b
14
+ * }
15
+ * ```
16
+ * ~~~
17
+ * These props are then added to the `<pre>` element
18
+ */
19
+ function rehypeMetaToProps() {
20
+ return (tree) => {
21
+ visit(tree, 'element', (node, _index, parent) => {
22
+ if (node.tagName === 'code' && parent?.type === 'element' && parent.tagName === 'pre') {
23
+ const props = parseMetaString(node.data?.meta);
24
+ parent.properties ??= {};
25
+ parent.properties = { ...parent.properties, ...props };
26
+ }
27
+ });
28
+ };
29
+ }
30
+ /**
31
+ * Minimal parser for a metadata string into key-value pairs.
32
+ *
33
+ * Supports simple patterns: key or key="value".
34
+ *
35
+ * Keys must contain only letters, dashes, or underscores (no digits).
36
+ * Keys are converted to kebab-case. Values default to "true" if missing.
37
+ *
38
+ * Example:
39
+ * parseMetaString('foo fooBar="value"')
40
+ * => { foo: 'true', foo_bar: 'value' }
41
+ *
42
+ * @param metaString - The input metadata string.
43
+ * @returns A plain object of parsed key-value pairs.
44
+ */
45
+ function parseMetaString(metaString) {
46
+ if (!metaString)
47
+ return {};
48
+ const props = {};
49
+ const keyValuePairRE = /([a-zA-Z_-]+)(?:="([^"]*)")?(?=\s|$)/g;
50
+ for (const match of metaString.matchAll(keyValuePairRE)) {
51
+ let [_, key, value] = match;
52
+ props[kebabCase(key)] = value || 'true';
53
+ }
54
+ return props;
55
+ }
56
+ // Simple function to convert a camelCase or PascalCase string to kebab-case.
57
+ function kebabCase(str) {
58
+ return str
59
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
60
+ .replace('_', '-')
61
+ .toLowerCase();
62
+ }
@@ -0,0 +1,4 @@
1
+ export { remarkDetype };
2
+ import type { Root } from 'mdast';
3
+ import type { VFile } from '@mdx-js/mdx/internal-create-format-aware-processors';
4
+ declare function remarkDetype(): (tree: Root, file: VFile) => Promise<void>;