@terrafirmagreg-team/kit 0.1.0 → 1.2.1

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.
Files changed (81) hide show
  1. package/package.json +15 -27
  2. package/src/components/blocks/nav-title.tsx +22 -25
  3. package/src/components/outline/dropdown.tsx +3 -2
  4. package/src/components/outline/index.ts +2 -1
  5. package/src/components/outline/list.tsx +3 -2
  6. package/src/components/outline/outline.tsx +4 -3
  7. package/src/components/outline/popover.tsx +3 -2
  8. package/src/components/page-actions.tsx +10 -9
  9. package/src/components/relative-link.tsx +1 -0
  10. package/src/components/toc/base.tsx +2 -1
  11. package/src/components/toc/block.tsx +1 -0
  12. package/src/components/toc/clerk.tsx +2 -1
  13. package/src/components/toc/default.tsx +3 -2
  14. package/src/components/version-menu.tsx +19 -17
  15. package/src/components/views/dataset.tsx +11 -2
  16. package/src/components/views/section.tsx +88 -44
  17. package/src/components/workspace/content.tsx +1 -0
  18. package/src/components/workspace/index.ts +1 -0
  19. package/src/components/workspace/pagination.tsx +2 -1
  20. package/src/components/workspace/workspace.tsx +1 -0
  21. package/src/hooks/use-catalog.ts +1 -0
  22. package/src/hooks/use-current-dataset.ts +15 -5
  23. package/src/hooks/use-dataset-accent.ts +1 -0
  24. package/src/hooks/use-nav-options.ts +20 -17
  25. package/src/hooks/use-section-names.ts +40 -0
  26. package/src/hooks/{toc.ts → use-toc.ts} +1 -0
  27. package/src/hooks/use-version-pref.ts +1 -0
  28. package/src/hooks/use-versioned-dataset.ts +21 -0
  29. package/src/lib/breadcrumb.ts +3 -16
  30. package/src/lib/config.ts +30 -28
  31. package/src/lib/dataset-links.ts +1 -0
  32. package/src/lib/datasets.ts +98 -68
  33. package/src/lib/landing.ts +4 -2
  34. package/src/lib/layout.ts +1 -0
  35. package/src/lib/locale/renderer.ts +49 -0
  36. package/src/lib/order.ts +1 -0
  37. package/src/lib/page.ts +3 -2
  38. package/src/lib/pin-tree.ts +11 -8
  39. package/src/lib/preview/lookup.ts +8 -5
  40. package/src/lib/section/schema.ts +2 -6
  41. package/src/lib/section/source.ts +59 -22
  42. package/src/lib/sources.ts +1 -0
  43. package/src/lib/tree.ts +1 -0
  44. package/src/lib/urls.ts +1 -0
  45. package/src/locales/en_us.json +7 -10
  46. package/src/locales/ru_ru.json +7 -10
  47. package/src/locales.ts +1 -0
  48. package/src/types/embeds.ts +54 -0
  49. package/src/types/index.ts +13 -9
  50. package/src/{components/outline/types.ts → types/outline.ts} +1 -0
  51. package/src/components/docs-image.tsx +0 -49
  52. package/src/components/search/dialog.tsx +0 -145
  53. package/src/lib/legacy.ts +0 -18
  54. package/src/lib/location.ts +0 -63
  55. package/src/lib/markdown/dom.ts +0 -90
  56. package/src/lib/markdown/index.ts +0 -13
  57. package/src/lib/markdown/providers.ts +0 -25
  58. package/src/lib/mdx/accordions.ts +0 -93
  59. package/src/lib/mdx/banner.ts +0 -52
  60. package/src/lib/mdx/directive-utils.ts +0 -125
  61. package/src/lib/mdx/directives.ts +0 -141
  62. package/src/lib/mdx/game-export.ts +0 -44
  63. package/src/lib/mdx/html-style.ts +0 -135
  64. package/src/lib/mdx/preview.ts +0 -252
  65. package/src/lib/mdx/references.ts +0 -56
  66. package/src/lib/mdx/tabs.ts +0 -80
  67. package/src/lib/mdx-components.tsx +0 -139
  68. package/src/lib/pack-assets.ts +0 -45
  69. package/src/lib/preview/index.ts +0 -8
  70. package/src/lib/preview/og.tsx +0 -115
  71. package/src/lib/preview/resolvers.ts +0 -98
  72. package/src/lib/search/config.ts +0 -13
  73. package/src/lib/search/filters.ts +0 -22
  74. package/src/lib/search/index.ts +0 -19
  75. package/src/lib/search/indexes.ts +0 -43
  76. package/src/lib/search/merge.ts +0 -22
  77. package/src/lib/search/server.ts +0 -18
  78. package/src/lib/search/sources.ts +0 -19
  79. package/src/lib/search/structured.ts +0 -23
  80. package/src/types/mdx.ts +0 -55
  81. package/src/types/turndown-plugin-gfm.d.ts +0 -9
@@ -0,0 +1,54 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { DatasetRef } from '@terrafirmagreg-team/sites/location';
3
+ import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
4
+
5
+ export type EmbedRecipe = {
6
+ id: string;
7
+ w?: number | null;
8
+ inputs: { id: string }[];
9
+ outputs: { id: string }[];
10
+ slots?: Record<
11
+ string,
12
+ { id: string; alternatives?: { id: string }[] }
13
+ >;
14
+ widgets?: {
15
+ type: string;
16
+ id: string;
17
+ alternatives?: string[];
18
+ }[];
19
+ };
20
+
21
+ export type EmbedRecipeCardProps = {
22
+ id: string;
23
+ recipe: EmbedRecipe;
24
+ dataset: DatasetRef;
25
+ lang: Lang;
26
+ langMap: Record<string, string>;
27
+ };
28
+
29
+ export type EmbedQuestLocation = { filename: string; questId: string };
30
+
31
+ export type SectionEmbeds = {
32
+ quest?: {
33
+ href(
34
+ dataset: DatasetRef,
35
+ lang: Lang | undefined,
36
+ ref: { category: string; id: string },
37
+ ): string;
38
+ findLocation(
39
+ dataset: DatasetRef,
40
+ questId: string,
41
+ ): Promise<EmbedQuestLocation | undefined>;
42
+ };
43
+ recipe?: {
44
+ href(
45
+ dataset: DatasetRef,
46
+ lang: Lang | undefined,
47
+ ref: { id: string },
48
+ ): string;
49
+ cardImageUrl(dataset: DatasetRef, id: string): string | undefined;
50
+ load(dataset: DatasetRef, id: string): Promise<EmbedRecipe | undefined>;
51
+ title(recipe: EmbedRecipe, langMap: Record<string, string>): string;
52
+ Card: ComponentType<EmbedRecipeCardProps>;
53
+ };
54
+ };
@@ -1,12 +1,11 @@
1
- import type { MDXProps } from 'mdx/types';
2
- import type { TOCItemType } from 'fumadocs-core/toc';
1
+ import type { DatasetRef, SectionKey } from '@terrafirmagreg-team/sites/location';
3
2
  import type * as PageTree from 'fumadocs-core/page-tree';
4
- import type { ComponentType, ReactNode } from 'react';
3
+ import type { ComponentType } from 'react';
5
4
  import type { IconName } from 'lucide-react/dynamic';
6
5
  import type { Page } from 'fumadocs-core/source';
7
6
  import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
8
7
 
9
- export type SectionKey = string;
8
+ export type { SectionKey } from '@terrafirmagreg-team/sites/location';
10
9
 
11
10
  export type SectionUrl = string;
12
11
 
@@ -38,11 +37,9 @@ export type SectionTrees = Record<Lang, Record<string, PageTree.Root>>;
38
37
 
39
38
  export type SectionPage = {
40
39
  data: {
41
- body: (props: MDXProps) => ReactNode | Promise<ReactNode>;
42
- toc: TOCItemType[];
43
- full?: boolean;
44
40
  title: string;
45
41
  description?: string;
42
+ full?: boolean;
46
43
  comments?: boolean;
47
44
  redirect?: string;
48
45
  authors?: string[];
@@ -52,6 +49,10 @@ export type SectionPage = {
52
49
  disableChangelog?: boolean;
53
50
  disableContributors?: boolean;
54
51
  };
52
+ load?(): Promise<unknown>;
53
+ content?: string;
54
+ structuredData?: unknown;
55
+ frontmatter?: Record<string, unknown>;
55
56
  };
56
57
  path: string;
57
58
  slugs: string[];
@@ -72,12 +73,15 @@ export type SectionSource = {
72
73
  export type DatasetLanding = {
73
74
  title: string;
74
75
  description?: string;
75
- dataset: string;
76
+ dataset: DatasetRef;
76
77
  datasetTitle?: string;
77
78
  lang: Lang;
79
+ versions?: string[];
78
80
  };
79
81
 
80
82
  export type SectionSourceEntry = {
81
- source: SectionSource;
83
+ getSource: () => Promise<SectionSource>;
84
+ getSourceSync?: () => SectionSource | undefined;
82
85
  markdown: { page(page: unknown): Promise<string> };
83
86
  };
87
+
@@ -27,3 +27,4 @@ export type WorkspaceOutlineDropdownProps = OutlineContent & {
27
27
  export type WorkspaceOutlinePopoverProps = WorkspaceOutlineDropdownProps & {
28
28
  container?: ComponentProps<'div'>;
29
29
  };
30
+
@@ -1,49 +0,0 @@
1
- import { ImageZoom } from '@terrafirmagreg-team/ui/components/mdx/image-zoom';
2
- import { resolveSrc } from '@terrafirmagreg-team/kit/lib/pack-assets';
3
- import type { DocsImageProps } from '@terrafirmagreg-team/kit/types/mdx';
4
-
5
- export function DocsImage({
6
- src,
7
- pack,
8
- mod,
9
- path,
10
- alt = '',
11
- width,
12
- height,
13
- className,
14
- caption,
15
- defaultPack: pagePack,
16
- ...rest
17
- }: DocsImageProps) {
18
- const packHint = pack ?? pagePack;
19
- const resolved = resolveSrc({ src, pack, mod, path }, packHint);
20
- if (!resolved) {
21
- return (
22
- <p className="not-prose text-sm text-muted-foreground">
23
- Missing image source (set <code>src</code> or <code>mod</code>+
24
- <code>path</code>; pack defaults from the docs slug).
25
- </p>
26
- );
27
- }
28
-
29
- const w = Number(width) || 960;
30
- const h = Number(height) || 540;
31
-
32
- return (
33
- <figure className="not-prose my-4 space-y-2">
34
- <ImageZoom
35
- src={resolved}
36
- alt={alt}
37
- width={w}
38
- height={h}
39
- className={className}
40
- {...rest}
41
- />
42
- {caption ? (
43
- <figcaption className="text-center text-sm text-muted-foreground">
44
- {caption}
45
- </figcaption>
46
- ) : null}
47
- </figure>
48
- );
49
- }
@@ -1,145 +0,0 @@
1
- 'use client';
2
- import {
3
- SearchDialog as SearchDialogRoot,
4
- SearchDialogClose,
5
- SearchDialogContent,
6
- SearchDialogFooter,
7
- SearchDialogHeader,
8
- SearchDialogIcon,
9
- SearchDialogInput,
10
- SearchDialogList,
11
- SearchDialogOverlay,
12
- SearchTagsList,
13
- SearchTagsListItem,
14
- type SharedProps,
15
- } from '@terrafirmagreg-team/ui/components/blocks/search/dialog';
16
- import {
17
- useDocsSearch,
18
- type SearchClient,
19
- } from 'fumadocs-core/search/client';
20
- import { staticClient } from 'fumadocs-core/search/client/orama-static';
21
- import { useResolvedLang } from '@terrafirmagreg-team/translate/hooks/use-resolved-lang';
22
- import { usePathname } from 'next/navigation';
23
- import { useMemo, useState } from 'react';
24
- import { Skeleton } from '@terrafirmagreg-team/ui/components/ui/skeleton';
25
- import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
26
- import { useSectionCatalog } from '@terrafirmagreg-team/kit/hooks/use-catalog';
27
- import {
28
- dumpSectionKeys,
29
- isDumpSection,
30
- parseDatasetRef,
31
- parseSectionLocation,
32
- } from '@terrafirmagreg-team/kit/lib/location';
33
- import { searchHitFilter, mergeClients } from '@terrafirmagreg-team/kit/lib/search';
34
- import { getSearchClientSources } from '@terrafirmagreg-team/kit/lib/search/sources';
35
-
36
- type Scope = 'dataset' | 'section' | 'all';
37
-
38
- export function SearchDialog(props: SharedProps & { api: string }) {
39
- const locale = useResolvedLang();
40
- const t = useTranslations();
41
- const catalog = useSectionCatalog();
42
- const pathname = usePathname();
43
- const { section, dataset: locationDataset } =
44
- parseSectionLocation(pathname);
45
- const dataset = locationDataset
46
- ? parseDatasetRef(locationDataset).id
47
- : undefined;
48
- const scopeKey = `${section ?? ''}/${locationDataset ?? ''}`;
49
- const defaultScope: Scope = dataset
50
- ? 'dataset'
51
- : section
52
- ? 'section'
53
- : 'all';
54
- const [picked, setPicked] = useState<{ key: string; value: Scope }>();
55
- const scope = picked?.key === scopeKey ? picked.value : defaultScope;
56
- const setScope = (value: Scope) => setPicked({ key: scopeKey, value });
57
-
58
- const tag = useMemo((): string | string[] | undefined => {
59
- if (!section || scope === 'all') return undefined;
60
- if (scope === 'dataset' && dataset) {
61
- return `${section}:${dataset}`;
62
- }
63
- const ids = catalog[section].map(({ id }) => `${section}:${id}`);
64
- return ids.length > 0 ? ids : `${section}:`;
65
- }, [scope, section, dataset, catalog]);
66
-
67
- const client = useMemo((): SearchClient => {
68
- const content = staticClient({ locale, tag, from: props.api });
69
-
70
- const scopedSections =
71
- scope === 'all'
72
- ? dumpSectionKeys()
73
- : section && isDumpSection(section)
74
- ? [section]
75
- : [];
76
- const refs = scopedSections.flatMap((sec) =>
77
- (catalog[sec] ?? []).map(({ id }) =>
78
- sec === section && id === dataset && locationDataset
79
- ? locationDataset
80
- : id,
81
- ),
82
- );
83
- const extra = getSearchClientSources().map((make) =>
84
- make({ refs, locale, tag }),
85
- );
86
- return mergeClients(content, ...extra);
87
- }, [locale, tag, section, scope, dataset, locationDataset, catalog, props.api]);
88
- const { search, setSearch, query } = useDocsSearch({ client });
89
- const items = useMemo(() => {
90
- const list = query.data !== 'empty' ? (query.data ?? []) : [];
91
- const hitFilter =
92
- section && typeof window !== 'undefined'
93
- ? searchHitFilter(
94
- section,
95
- new URLSearchParams(window.location.search),
96
- )
97
- : undefined;
98
- return hitFilter ? list.filter(hitFilter) : list;
99
- }, [query.data, section]);
100
- const datasetMeta = catalog[section ?? 'docs']?.find(
101
- ({ id }) => id === dataset,
102
- );
103
-
104
- return (
105
- <SearchDialogRoot search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
106
- <SearchDialogOverlay />
107
- <SearchDialogContent>
108
- <SearchDialogHeader>
109
- <SearchDialogIcon />
110
- <SearchDialogInput />
111
- <SearchDialogClose />
112
- </SearchDialogHeader>
113
- {query.isLoading ? (
114
- <div className="flex flex-col gap-1.5 p-3">
115
- <Skeleton className="h-9 w-full" />
116
- <Skeleton className="h-9 w-5/6" />
117
- <Skeleton className="h-9 w-4/6" />
118
- </div>
119
- ) : (
120
- <SearchDialogList items={items.length ? items : null} />
121
- )}
122
- {section && (
123
- <SearchDialogFooter>
124
- <SearchTagsList
125
- tag={scope}
126
- onTagChange={(value) => setScope((value as Scope) ?? 'all')}
127
- >
128
- {dataset && (
129
- <SearchTagsListItem value="dataset">
130
- {datasetMeta?.title ?? dataset}
131
- </SearchTagsListItem>
132
- )}
133
- <SearchTagsListItem value="section">
134
- {t(`sections.${section}`)}
135
- </SearchTagsListItem>
136
- <SearchTagsListItem value="all">
137
- {t('ui.search_everywhere')}
138
- </SearchTagsListItem>
139
- </SearchTagsList>
140
- </SearchDialogFooter>
141
- )}
142
- </SearchDialogContent>
143
- </SearchDialogRoot>
144
- );
145
- }
package/src/lib/legacy.ts DELETED
@@ -1,18 +0,0 @@
1
- import { sectionConfig } from './config';
2
-
3
- export function legacyTarget(pathname: string): string | undefined {
4
- const { docsRoute, legacyRoutes } = sectionConfig();
5
- const prefixes = Object.keys(legacyRoutes).sort((a, b) => b.length - a.length);
6
- const parts = pathname.split('/').filter(Boolean);
7
- const prefix = prefixes.find((p) => p === parts[0] || p === `${parts[0]}/${parts[2]}`);
8
- if (!prefix) return undefined;
9
- const prefixParts = prefix.split('/');
10
- const lang = parts[1];
11
- const rest = parts.slice(prefixParts.length + 1).join('/');
12
- const route = legacyRoutes[prefix];
13
- const override = route.overrides?.[rest];
14
- const target =
15
- override ??
16
- `${docsRoute}/${route.dataset}${[route.path, rest].filter(Boolean).map((p) => `/${p}`).join('')}`;
17
- return lang ? `${target}?lang=${lang}` : target;
18
- }
@@ -1,63 +0,0 @@
1
- import { sectionConfig } from './config';
2
- import type { SectionKey } from '@terrafirmagreg-team/kit/types';
3
-
4
- let urls: Record<SectionKey, string> | undefined;
5
- const sectionUrls = () =>
6
- (urls ??= Object.fromEntries(
7
- sectionConfig().sectionTabs.map(({ key, url }) => [key, url]),
8
- ) as Record<SectionKey, string>);
9
-
10
- let keys: Set<SectionKey> | undefined;
11
- const sectionKeys = () =>
12
- (keys ??= new Set(sectionConfig().sectionTabs.map(({ key }) => key)));
13
-
14
- export function sectionUrl(section: SectionKey, dataset?: string): string {
15
- const base = sectionUrls()[section];
16
- return dataset ? `${base}/${dataset}` : base;
17
- }
18
-
19
- export function isSectionKey(value: string | undefined): value is SectionKey {
20
- return value !== undefined && sectionKeys().has(value);
21
- }
22
-
23
- export type SectionLocation = {
24
- section?: SectionKey;
25
-
26
- dataset?: string;
27
- };
28
-
29
- export function isDumpSection(section: SectionKey | undefined): boolean {
30
- return (
31
- section !== undefined &&
32
- (sectionConfig().dumpSections ?? []).includes(section)
33
- );
34
- }
35
-
36
- export function dumpSectionKeys(): readonly SectionKey[] {
37
- return sectionConfig().dumpSections ?? [];
38
- }
39
-
40
- export function parseDatasetRef(ref: string): {
41
- id: string;
42
- version?: string;
43
- } {
44
- const decoded = decodeURIComponent(ref);
45
- const at = decoded.lastIndexOf('@');
46
- if (at <= 0 || at === decoded.length - 1) return { id: decoded };
47
- return { id: decoded.slice(0, at), version: decoded.slice(at + 1) };
48
- }
49
-
50
- export function datasetRef(id: string, version?: string): string {
51
- return version ? `${id}@${version}` : id;
52
- }
53
-
54
- export function parseSectionLocation(pathname: string): SectionLocation {
55
- const { basePath } = sectionConfig();
56
- const path =
57
- basePath && pathname.startsWith(basePath)
58
- ? pathname.slice(basePath.length)
59
- : pathname;
60
- const [section, dataset] = path.split('/').filter(Boolean);
61
- if (!isSectionKey(section)) return {};
62
- return { section, dataset };
63
- }
@@ -1,90 +0,0 @@
1
- 'use client';
2
- import TurndownService from 'turndown';
3
- import { gfm } from 'turndown-plugin-gfm';
4
-
5
- const service = new TurndownService({
6
- headingStyle: 'atx',
7
- codeBlockStyle: 'fenced',
8
- bulletListMarker: '-',
9
- });
10
- service.use(gfm);
11
- service.remove([
12
- 'script',
13
- 'style',
14
- 'noscript',
15
- 'template',
16
- 'button',
17
- 'select',
18
- 'nav',
19
- 'aside',
20
- 'footer',
21
- ]);
22
- service.remove(
23
- (node) =>
24
- node.nodeName === 'SVG' ||
25
- (node instanceof HTMLImageElement &&
26
- (node.src.startsWith('data:') ||
27
- (node.width > 0 && node.width <= 48))) ||
28
- (node instanceof HTMLElement && node.dataset.mdSkip !== undefined),
29
- );
30
-
31
- function decodedUrl(raw: string): string {
32
- const url = raw.replace(/(?:%[0-9A-Fa-f]{2})+/g, (run) => {
33
- let decoded: string;
34
- try {
35
- decoded = decodeURIComponent(run);
36
- } catch {
37
- return run;
38
- }
39
- return /[/?#&=%+]/.test(decoded) ? run : decoded;
40
- });
41
- return /[\s<>()]/.test(url) ? `<${url}>` : url;
42
- }
43
-
44
- service.addRule('decodedLink', {
45
- filter: 'a',
46
- replacement(content, node) {
47
- const anchor = node as HTMLAnchorElement;
48
- const href = anchor.getAttribute('href');
49
- if (!href) return content;
50
- const title = anchor.getAttribute('title');
51
- const titlePart = title ? ` "${title.replace(/"/g, '\"')}"` : '';
52
- return `[${content}](${decodedUrl(href)}${titlePart})`;
53
- },
54
- });
55
- service.addRule('decodedImage', {
56
- filter: 'img',
57
- replacement(_content, node) {
58
- const image = node as HTMLImageElement;
59
- const src = image.getAttribute('src');
60
- if (!src) return '';
61
- const alt = image.getAttribute('alt') ?? '';
62
- const title = image.getAttribute('title');
63
- const titlePart = title ? ` "${title.replace(/"/g, '\\"')}"` : '';
64
- return `![${alt}](${decodedUrl(src)}${titlePart})`;
65
- },
66
- });
67
-
68
- export function domToMarkdown(root: Element | null | undefined): string {
69
- if (!root) return '';
70
- return service.turndown(root as HTMLElement);
71
- }
72
-
73
- export function pageMarkdown(
74
- root: Element | null | undefined,
75
- title: string,
76
- ): Promise<string> {
77
- return Promise.resolve(`# ${title}\n\n${domToMarkdown(root)}`);
78
- }
79
-
80
- export async function openPageMarkdown(
81
- root: Element | null | undefined,
82
- title: string,
83
- ): Promise<void> {
84
- const markdown = await pageMarkdown(root, title);
85
- const url = URL.createObjectURL(
86
- new Blob([markdown], { type: 'text/markdown;charset=utf-8' }),
87
- );
88
- window.open(url, '_blank', 'noopener');
89
- setTimeout(() => URL.revokeObjectURL(url), 60_000);
90
- }
@@ -1,13 +0,0 @@
1
- export * from './dom';
2
- export * from './providers';
3
-
4
- export async function renderSectionMarkdown(page: {
5
- data: {
6
- title?: string;
7
- getText: (type: 'processed' | 'raw') => Promise<string>;
8
- };
9
- }) {
10
- return `# ${page.data.title}
11
-
12
- ${await page.data.getText('processed')}`;
13
- }
@@ -1,25 +0,0 @@
1
- import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
2
-
3
- export interface MarkdownProviderCtx {
4
-
5
- slugs: string[];
6
- lang: Lang;
7
- }
8
-
9
- export type MarkdownProvider = (
10
- req: Request,
11
- ctx: MarkdownProviderCtx,
12
- ) => Promise<Response | undefined>;
13
-
14
- const providers = new Map<string, MarkdownProvider>();
15
-
16
- export function registerMarkdownProvider(
17
- section: string,
18
- provider: MarkdownProvider,
19
- ): void {
20
- providers.set(section, provider);
21
- }
22
-
23
- export function markdownProvider(section: string): MarkdownProvider | undefined {
24
- return providers.get(section);
25
- }
@@ -1,93 +0,0 @@
1
- import {
2
- contentChildren,
3
- extractDirectiveLabel,
4
- extractLeafLabel,
5
- jsxAttr,
6
- walkContainers,
7
- type DirectiveNode,
8
- type JsxAttribute,
9
- type JsxElement,
10
- type MdastNode,
11
- } from './directive-utils.ts';
12
-
13
- export function remarkAccordions() {
14
- return (tree: MdastNode) => {
15
- walkContainers(tree, (directive) => {
16
- if (directive.name === 'accordion' || directive.name === 'details') {
17
- wrapSingle(directive);
18
- return false;
19
- }
20
- if (directive.name === 'accordions') {
21
- toAccordions(directive);
22
- return false;
23
- }
24
- });
25
- };
26
- }
27
-
28
- function toAccordion(node: DirectiveNode): JsxElement {
29
- return {
30
- type: 'mdxJsxFlowElement',
31
- name: 'Accordion',
32
- attributes: [jsxAttr('title', extractDirectiveLabel(node))],
33
- children: contentChildren(node),
34
- };
35
- }
36
-
37
- function wrapSingle(node: DirectiveNode): void {
38
- Object.assign(node, {
39
- type: 'mdxJsxFlowElement',
40
- name: 'Accordions',
41
- attributes: [],
42
- children: [toAccordion(node)],
43
- });
44
- }
45
-
46
- function accordionsAttributes(node: DirectiveNode): JsxAttribute[] {
47
- const attrs = node.attributes ?? {};
48
- const out: JsxAttribute[] = [];
49
- const isMultiple =
50
- attrs.multiple === true ||
51
- attrs.multiple === '' ||
52
- attrs.type === 'multiple';
53
-
54
- if (isMultiple) out.push(jsxAttr('multiple', true));
55
-
56
- for (const [name, value] of Object.entries(attrs)) {
57
- if (name === 'type' || name === 'multiple') continue;
58
- out.push({ type: 'mdxJsxAttribute', name, value: value ?? true });
59
- }
60
-
61
- return out;
62
- }
63
-
64
- function toAccordions(node: DirectiveNode): void {
65
- const items: JsxElement[] = [];
66
- let current: JsxElement | undefined;
67
-
68
- for (const item of node.children ?? []) {
69
- if (
70
- item.type === 'leafDirective' &&
71
- (item as { name?: string }).name === 'accordion'
72
- ) {
73
- current = {
74
- type: 'mdxJsxFlowElement',
75
- name: 'Accordion',
76
- attributes: [jsxAttr('title', extractLeafLabel(item))],
77
- children: [],
78
- };
79
- items.push(current);
80
- continue;
81
- }
82
-
83
- current?.children.push(item);
84
- }
85
-
86
- Object.assign(node, {
87
- type: 'mdxJsxFlowElement',
88
- name: 'Accordions',
89
- attributes: accordionsAttributes(node),
90
- children: items,
91
- });
92
- }
93
-
@@ -1,52 +0,0 @@
1
- import {
2
- estreeBoolean,
3
- estreeStringArray,
4
- jsxExpression,
5
- walkContainers,
6
- type DirectiveNode,
7
- type MdastNode,
8
- } from './directive-utils.ts';
9
-
10
-
11
- export function remarkBanner() {
12
- return (tree: MdastNode) => {
13
- walkContainers(tree, (directive) => {
14
- if (directive.name !== 'banner') return;
15
- Object.assign(directive, {
16
- type: 'mdxJsxFlowElement',
17
- name: 'Banner',
18
- attributes: [
19
- {
20
- type: 'mdxJsxAttribute',
21
- name: 'changeLayout',
22
- value: jsxExpression(estreeBoolean(false)),
23
- },
24
- ...toAttributes(directive),
25
- ],
26
- children: directive.children,
27
- });
28
- return false;
29
- });
30
- };
31
- }
32
-
33
- function toAttributes(directive: DirectiveNode) {
34
- return Object.entries(directive.attributes ?? {})
35
- .filter(([name]) => name !== 'id')
36
- .map(([name, value]) => {
37
- if (name === 'rainbowColors' && typeof value === 'string') {
38
- const colors = value
39
- .split(',')
40
- .map((color) => color.trim())
41
- .filter(Boolean);
42
- return {
43
- type: 'mdxJsxAttribute' as const,
44
- name,
45
- value: jsxExpression(estreeStringArray(colors)),
46
- };
47
- }
48
- return { type: 'mdxJsxAttribute' as const, name, value: value ?? true };
49
- });
50
- }
51
-
52
-