@clidey/dory 0.29.0 → 0.30.0
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/bin/dist/dory.js +115 -29
- package/bin/dist/dory.js.map +1 -1
- package/package.json +12 -4
- package/.dockerignore +0 -4
- package/CLAUDE.md +0 -178
- package/CODE_OF_CONDUCT.md +0 -128
- package/CONTRIBUTING.md +0 -91
- package/SECURITY.md +0 -11
- package/bin/dory.ts +0 -433
- package/dist/favicon.svg +0 -75
- package/dist/frontmatter.json +0 -77
- package/dist/index.html +0 -55
- package/dist/llms.txt +0 -561
- package/dist/logo/logo.svg +0 -18
- package/docs/api-playground-demo.mdx +0 -119
- package/docs/api-reference/asyncapi.json +0 -324
- package/docs/api-reference/openapi.json +0 -366
- package/docs/api-reference/operations/create.mdx +0 -5
- package/docs/api-reference/operations/delete.mdx +0 -5
- package/docs/api-reference/operations/get.mdx +0 -5
- package/docs/api-reference/operations/update.mdx +0 -5
- package/docs/api-reference/websocket-operations/receive-chat-message.mdx +0 -5
- package/docs/api-reference/websocket-operations/receive-notifications.mdx +0 -5
- package/docs/api-reference/websocket-operations/receive-presence-update.mdx +0 -5
- package/docs/api-reference/websocket-operations/send-chat-message.mdx +0 -5
- package/docs/api-reference/websocket-operations/send-presence-update.mdx +0 -5
- package/docs/dory.json +0 -64
- package/docs/favicon.svg +0 -75
- package/docs/features.mdx +0 -440
- package/docs/introduction.mdx +0 -44
- package/docs/logo/logo.svg +0 -18
- package/docs/websocket-playground-demo.mdx +0 -135
- package/index.html +0 -54
- package/scripts/generate-frontmatter-json.js +0 -154
- package/scripts/generate-llm-txt.js +0 -115
- package/src/app.css +0 -5
- package/src/app.tsx +0 -13
- package/src/components/dropdown.tsx +0 -53
- package/src/components/hooks.tsx +0 -28
- package/src/components/loading.tsx +0 -21
- package/src/components/mdx-provider.tsx +0 -13
- package/src/components/navigation.tsx +0 -58
- package/src/components/notification.tsx +0 -13
- package/src/components/routes.tsx +0 -92
- package/src/components/search-index.ts +0 -43
- package/src/components/search.tsx +0 -435
- package/src/components/store.ts +0 -216
- package/src/config/mdx.js +0 -12
- package/src/config/mdx.ts +0 -12
- package/src/main.tsx +0 -22
- package/src/mdx/accordion.tsx +0 -41
- package/src/mdx/api-playground.tsx +0 -659
- package/src/mdx/api.tsx +0 -354
- package/src/mdx/async-api.tsx +0 -353
- package/src/mdx/callout.tsx +0 -64
- package/src/mdx/card.tsx +0 -54
- package/src/mdx/code.tsx +0 -289
- package/src/mdx/expandable.tsx +0 -36
- package/src/mdx/fence.tsx +0 -74
- package/src/mdx/icon.tsx +0 -73
- package/src/mdx/latex.tsx +0 -26
- package/src/mdx/list.tsx +0 -7
- package/src/mdx/mdx.tsx +0 -47
- package/src/mdx/mermaid.tsx +0 -147
- package/src/mdx/open-api.tsx +0 -311
- package/src/mdx/processor.ts +0 -174
- package/src/mdx/source.tsx +0 -58
- package/src/mdx/step.tsx +0 -51
- package/src/mdx/table.tsx +0 -20
- package/src/mdx/tag.tsx +0 -53
- package/src/mdx/text.tsx +0 -99
- package/src/mdx/websocket-playground.tsx +0 -824
- package/src/plugins/docs-assets.ts +0 -79
- package/src/plugins/frontmatter-dev-server.ts +0 -158
- package/src/plugins/frontmatter-generator.ts +0 -157
- package/src/plugins/html-metadata-injector.ts +0 -76
- package/src/plugins/llm-txt-dev-server.ts +0 -183
- package/src/plugins/llm-txt-generator.ts +0 -186
- package/src/plugins/sanitize.ts +0 -163
- package/src/plugins/unified-mdx-plugin.ts +0 -35
- package/src/theme.scss +0 -72
- package/src/types/config.ts +0 -47
- package/src/ui/header.tsx +0 -91
- package/src/ui/layout.tsx +0 -226
- package/src/ui/mobile-navigation.tsx +0 -64
- package/src/ui/prev-next-link.tsx +0 -74
- package/src/ui/table-of-content.tsx +0 -166
- package/src/utils/colors.ts +0 -54
- package/src/utils/fonts.ts +0 -23
- package/src/utils/functions.ts +0 -12
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.app.json +0 -33
- package/tsconfig.bin.json +0 -24
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -43
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from 'vite';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
interface LlmTxtGeneratorOptions {
|
|
6
|
-
docsDir?: string;
|
|
7
|
-
outputPath?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Read dory.json to get navigation order (flat list, in order)
|
|
11
|
-
function getNavigationOrder(docsDir: string): string[] {
|
|
12
|
-
const doryJsonPath = path.join(docsDir, 'dory.json');
|
|
13
|
-
if (!fs.existsSync(doryJsonPath)) return [];
|
|
14
|
-
const doryJson = JSON.parse(fs.readFileSync(doryJsonPath, 'utf-8'));
|
|
15
|
-
const order: string[] = [];
|
|
16
|
-
|
|
17
|
-
function walkPages(pages: any[]) {
|
|
18
|
-
for (const page of pages) {
|
|
19
|
-
if (typeof page === 'string') {
|
|
20
|
-
order.push(page);
|
|
21
|
-
} else if (typeof page === 'object' && page.pages) {
|
|
22
|
-
walkPages(page.pages);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (doryJson.navigation && Array.isArray(doryJson.navigation.tabs)) {
|
|
28
|
-
for (const tab of doryJson.navigation.tabs) {
|
|
29
|
-
if (tab.groups && Array.isArray(tab.groups)) {
|
|
30
|
-
for (const group of tab.groups) {
|
|
31
|
-
if (group.pages && Array.isArray(group.pages)) {
|
|
32
|
-
walkPages(group.pages);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return order;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Function to parse frontmatter from MDX content
|
|
42
|
-
function parseFrontmatter(content: string) {
|
|
43
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
44
|
-
if (!frontmatterMatch) return { data: {}, content };
|
|
45
|
-
|
|
46
|
-
const frontmatter = frontmatterMatch[1];
|
|
47
|
-
const data: Record<string, string> = {};
|
|
48
|
-
|
|
49
|
-
frontmatter.split('\n').forEach(line => {
|
|
50
|
-
const [key, ...valueParts] = line.split(':');
|
|
51
|
-
if (key && valueParts.length) {
|
|
52
|
-
const value = valueParts.join(':').trim();
|
|
53
|
-
data[key.trim()] = value.replace(/^["']|["']$/g, '');
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
return { data, content: content.slice(frontmatterMatch[0].length) };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Function to recursively find all MDX files and map by relative path (without .mdx)
|
|
61
|
-
function findMdxFilesMap(dir: string, baseDir: string): Record<string, string> {
|
|
62
|
-
const files: Record<string, string> = {};
|
|
63
|
-
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
64
|
-
|
|
65
|
-
for (const item of items) {
|
|
66
|
-
const fullPath = path.join(dir, item.name);
|
|
67
|
-
if (item.isDirectory()) {
|
|
68
|
-
Object.assign(files, findMdxFilesMap(fullPath, baseDir));
|
|
69
|
-
} else if (item.isFile() && item.name.endsWith('.mdx')) {
|
|
70
|
-
// Key: relative path without .mdx, with forward slashes
|
|
71
|
-
let rel = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
72
|
-
if (rel.endsWith('.mdx')) rel = rel.slice(0, -4);
|
|
73
|
-
files[rel] = fullPath;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return files;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Function to clean MDX content for LLM consumption
|
|
81
|
-
function cleanMdxContent(content: string): string {
|
|
82
|
-
// Remove JSX components but keep their content
|
|
83
|
-
let cleaned = content
|
|
84
|
-
// Remove JSX opening/closing tags but keep content
|
|
85
|
-
.replace(/<(\w+)([^>]*?)>/g, '')
|
|
86
|
-
.replace(/<\/\w+>/g, '')
|
|
87
|
-
// Remove code block language specifiers
|
|
88
|
-
.replace(/```(\w+)/g, '```')
|
|
89
|
-
// Remove HTML comments
|
|
90
|
-
.replace(/<!--[\s\S]*?-->/g, '')
|
|
91
|
-
// Remove excessive whitespace
|
|
92
|
-
.replace(/\n\s*\n\s*\n/g, '\n\n')
|
|
93
|
-
// Remove leading/trailing whitespace
|
|
94
|
-
.trim();
|
|
95
|
-
|
|
96
|
-
return cleaned;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Function to generate LLM text content in dory.json order, then append any extra files
|
|
100
|
-
function generateLlmContent(docsDir: string): string {
|
|
101
|
-
const navOrder = getNavigationOrder(docsDir);
|
|
102
|
-
const mdxFilesMap = findMdxFilesMap(docsDir, docsDir);
|
|
103
|
-
const usedKeys = new Set<string>();
|
|
104
|
-
let aggregatedContent = '';
|
|
105
|
-
|
|
106
|
-
// Output files in navigation order, in order as in dory.json
|
|
107
|
-
for (const navKey of navOrder) {
|
|
108
|
-
// Try both with and without "index"
|
|
109
|
-
let fileKey = navKey;
|
|
110
|
-
if (mdxFilesMap[fileKey]) {
|
|
111
|
-
usedKeys.add(fileKey);
|
|
112
|
-
} else if (mdxFilesMap[`${fileKey}/index`]) {
|
|
113
|
-
fileKey = `${fileKey}/index`;
|
|
114
|
-
usedKeys.add(fileKey);
|
|
115
|
-
} else {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
const filePath = mdxFilesMap[fileKey];
|
|
119
|
-
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
120
|
-
const { data, content } = parseFrontmatter(rawContent);
|
|
121
|
-
const cleanedContent = cleanMdxContent(content);
|
|
122
|
-
|
|
123
|
-
// Add file header
|
|
124
|
-
aggregatedContent += `\n# ${fileKey}.mdx\n`;
|
|
125
|
-
|
|
126
|
-
// Add title if available
|
|
127
|
-
if (data.title) {
|
|
128
|
-
aggregatedContent += `## ${data.title}\n\n`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Add description if available
|
|
132
|
-
if (data.description) {
|
|
133
|
-
aggregatedContent += `${data.description}\n\n`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Add cleaned content
|
|
137
|
-
aggregatedContent += cleanedContent + '\n\n';
|
|
138
|
-
aggregatedContent += '---\n\n';
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Output any extra files not in navigation order, sorted
|
|
142
|
-
const extraKeys = Object.keys(mdxFilesMap).filter(k => !usedKeys.has(k)).sort();
|
|
143
|
-
for (const fileKey of extraKeys) {
|
|
144
|
-
const filePath = mdxFilesMap[fileKey];
|
|
145
|
-
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
146
|
-
const { data, content } = parseFrontmatter(rawContent);
|
|
147
|
-
const cleanedContent = cleanMdxContent(content);
|
|
148
|
-
|
|
149
|
-
aggregatedContent += `\n# ${fileKey}.mdx\n`;
|
|
150
|
-
if (data.title) {
|
|
151
|
-
aggregatedContent += `## ${data.title}\n\n`;
|
|
152
|
-
}
|
|
153
|
-
if (data.description) {
|
|
154
|
-
aggregatedContent += `${data.description}\n\n`;
|
|
155
|
-
}
|
|
156
|
-
aggregatedContent += cleanedContent + '\n\n';
|
|
157
|
-
aggregatedContent += '---\n\n';
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Remove leading newlines for the very first file, so the first file is at the very top
|
|
161
|
-
return aggregatedContent.replace(/^\n+/, '').trim();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function llmTxtGenerator(options: LlmTxtGeneratorOptions = {}): Plugin {
|
|
165
|
-
return {
|
|
166
|
-
name: 'llm-txt-generator',
|
|
167
|
-
generateBundle() {
|
|
168
|
-
const docsDir = options.docsDir || path.join(process.cwd(), 'docs');
|
|
169
|
-
const aggregatedContent = generateLlmContent(docsDir);
|
|
170
|
-
|
|
171
|
-
this.emitFile({
|
|
172
|
-
type: 'asset',
|
|
173
|
-
fileName: 'llms.txt',
|
|
174
|
-
source: aggregatedContent
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// For logging, count the number of MDX files processed
|
|
178
|
-
const mdxFilesMap = findMdxFilesMap(docsDir, docsDir);
|
|
179
|
-
const mdxFilesCount = Object.keys(mdxFilesMap).length;
|
|
180
|
-
|
|
181
|
-
console.log(`✅ Generated LLM text file`);
|
|
182
|
-
console.log(`📄 Processed ${mdxFilesCount} MDX files`);
|
|
183
|
-
console.log(`📝 Total content size: ${aggregatedContent.length} characters`);
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
}
|
package/src/plugins/sanitize.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { visit } from 'unist-util-visit';
|
|
2
|
-
|
|
3
|
-
// This plugin replaces mdxTextExpression nodes with inlineCode for safety.
|
|
4
|
-
export const remarkSafeVars = () => {
|
|
5
|
-
return (tree: any) => {
|
|
6
|
-
visit(tree, 'mdxTextExpression', (node: any, index: number | undefined, parent: any) => {
|
|
7
|
-
if (parent && typeof index === 'number') {
|
|
8
|
-
parent.children[index] = {
|
|
9
|
-
type: 'inlineCode',
|
|
10
|
-
value: node.value
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const KNOWN_COMPONENTS = [
|
|
18
|
-
// Text components
|
|
19
|
-
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'Row', 'Col', 'a', 'img',
|
|
20
|
-
// Code components
|
|
21
|
-
'code', 'CodeGroup', 'pre',
|
|
22
|
-
// UI components
|
|
23
|
-
'Accordion', 'AccordionGroup', 'Icon', 'Card', 'CardGroup',
|
|
24
|
-
'Callout', 'Note', 'Warning', 'Info', 'Tip', 'Check',
|
|
25
|
-
'Latex', 'API', 'Expandable', 'ResponseField', 'Properties', 'Property',
|
|
26
|
-
'Steps', 'Step', 'table', 'th', 'td', 'ul', 'ol', 'li',
|
|
27
|
-
'APIPlayground', 'WebSocketPlayground', 'AsyncAPI', 'Source',
|
|
28
|
-
// Common HTML elements that should be preserved
|
|
29
|
-
'div', 'span', 'br', 'hr', 'strong', 'em', 'b', 'i', 'u', 's',
|
|
30
|
-
'blockquote', 'nav', 'section', 'article', 'header', 'footer', 'main',
|
|
31
|
-
'aside', 'figure', 'figcaption', 'details', 'summary', 'mark', 'small',
|
|
32
|
-
'del', 'ins', 'sub', 'sup', 'kbd', 'samp', 'var', 'time', 'abbr',
|
|
33
|
-
'address', 'cite', 'q', 'dfn'
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
// Helper: returns true if the given code string is inside a code block (```)
|
|
37
|
-
function isInsideCodeBlock(code: string, idx: number): boolean {
|
|
38
|
-
// Count the number of ``` before idx
|
|
39
|
-
const before = code.slice(0, idx);
|
|
40
|
-
const codeBlockMatches = before.match(/```/g);
|
|
41
|
-
// Odd number of ``` means inside a code block
|
|
42
|
-
return codeBlockMatches ? codeBlockMatches.length % 2 === 1 : false;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Helper: checks if a known component tag has a matching closing tag
|
|
46
|
-
function hasMatchingClosingTag(code: string, tagName: string, openingTagOffset: number): boolean {
|
|
47
|
-
// Simple approach: look for the next occurrence of either an opening or closing tag of the same type
|
|
48
|
-
const afterOpening = code.slice(openingTagOffset + `<${tagName}>`.length);
|
|
49
|
-
|
|
50
|
-
// Find the first occurrence of either opening or closing tag
|
|
51
|
-
const nextOpeningMatch = afterOpening.match(new RegExp(`<${tagName}>`, 'i'));
|
|
52
|
-
const nextClosingMatch = afterOpening.match(new RegExp(`</${tagName}>`, 'i'));
|
|
53
|
-
|
|
54
|
-
// If no closing tag found at all
|
|
55
|
-
if (!nextClosingMatch) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const nextOpeningIndex = nextOpeningMatch ? nextOpeningMatch.index! : Infinity;
|
|
60
|
-
const nextClosingIndex = nextClosingMatch ? nextClosingMatch.index! : Infinity;
|
|
61
|
-
|
|
62
|
-
// If the closing tag comes before any other opening tag, this tag is properly closed
|
|
63
|
-
if (nextClosingIndex < nextOpeningIndex) {
|
|
64
|
-
// Check if the closing tag is inside a code block
|
|
65
|
-
const closingTagOffset = openingTagOffset + `<${tagName}>`.length + nextClosingIndex;
|
|
66
|
-
if (isInsideCodeBlock(code, closingTagOffset)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return false; // Another opening tag comes before the closing tag, so this one is not properly closed
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Allow any <[a-z][a-z0-9]*> tag inside code blocks to be left as-is (not parsed as JSX).
|
|
76
|
-
export function preprocessMdxTags() {
|
|
77
|
-
return {
|
|
78
|
-
name: 'preprocess-mdx-tags',
|
|
79
|
-
enforce: 'pre' as const,
|
|
80
|
-
transform(code: string, id: string) {
|
|
81
|
-
if (!id.endsWith('.mdx')) return;
|
|
82
|
-
|
|
83
|
-
// Remove leading whitespace from code fence blocks
|
|
84
|
-
let processed = code.replace(/^(\s+)(```\w*)/gm, '$2');
|
|
85
|
-
|
|
86
|
-
// Handle self-closing tags first (like <VideoPlayer />)
|
|
87
|
-
processed = processed.replace(/<([A-Za-z][A-Za-z0-9]*)[^>]*\/>/g, (match, tag, offset) => {
|
|
88
|
-
// If inside a code block, do not touch
|
|
89
|
-
if (isInsideCodeBlock(processed, offset)) {
|
|
90
|
-
return match;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// If unknown component, treat as text
|
|
94
|
-
if (!KNOWN_COMPONENTS.includes(tag)) {
|
|
95
|
-
return `\`${match}\``;
|
|
96
|
-
}
|
|
97
|
-
return match;
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Only replace unrecognized <someTag> with `"<someTag>"` if NOT inside a code block
|
|
101
|
-
// This regex finds all <tag> occurrences (fixed to handle uppercase)
|
|
102
|
-
processed = processed.replace(/<([A-Za-z][A-Za-z0-9]*)>/g, (match, tag, offset) => {
|
|
103
|
-
// If inside a code block, do not touch
|
|
104
|
-
if (isInsideCodeBlock(processed, offset)) {
|
|
105
|
-
return match;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// If known component, check if it has a closing tag
|
|
109
|
-
if (KNOWN_COMPONENTS.includes(tag)) {
|
|
110
|
-
// If it doesn't have a matching closing tag, treat it as text
|
|
111
|
-
if (!hasMatchingClosingTag(processed, tag, offset)) {
|
|
112
|
-
return `\`<${tag}>\``;
|
|
113
|
-
}
|
|
114
|
-
// Otherwise, leave as-is for proper JSX parsing
|
|
115
|
-
return match;
|
|
116
|
-
}
|
|
117
|
-
// For unknown components, always treat as text (wrap in backticks)
|
|
118
|
-
// If already inside backticks, do not wrap again
|
|
119
|
-
const before = processed.slice(0, offset);
|
|
120
|
-
const backtickMatches = before.match(/`+/g);
|
|
121
|
-
const insideBackticks = backtickMatches ? backtickMatches.reduce((acc, s) => acc + s.length, 0) % 2 === 1 : false;
|
|
122
|
-
if (insideBackticks) {
|
|
123
|
-
return match;
|
|
124
|
-
}
|
|
125
|
-
return `\`<${tag}>\``;
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// This regex finds all <tag> and </tag> occurrences (fixed to handle uppercase)
|
|
129
|
-
processed = processed.replace(/<\/?([A-Za-z][A-Za-z0-9]*)>/g, (match, tag, offset) => {
|
|
130
|
-
// If inside a code block, do not touch
|
|
131
|
-
if (isInsideCodeBlock(processed, offset)) {
|
|
132
|
-
return match;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// If known component, check if it has a closing tag (only for opening tags)
|
|
136
|
-
if (KNOWN_COMPONENTS.includes(tag)) {
|
|
137
|
-
// Only check for matching closing tag for opening tags
|
|
138
|
-
if (!match.startsWith('</')) {
|
|
139
|
-
if (!hasMatchingClosingTag(processed, tag, offset)) {
|
|
140
|
-
return `\`${match}\``;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
// Otherwise, leave as-is for proper JSX parsing
|
|
144
|
-
return match;
|
|
145
|
-
}
|
|
146
|
-
// For unknown components, always treat as text (wrap in backticks)
|
|
147
|
-
// If already inside backticks, do not wrap again
|
|
148
|
-
const before = processed.slice(0, offset);
|
|
149
|
-
const backtickMatches = before.match(/`+/g);
|
|
150
|
-
const insideBackticks = backtickMatches ? backtickMatches.reduce((acc, s) => acc + s.length, 0) % 2 === 1 : false;
|
|
151
|
-
if (insideBackticks) {
|
|
152
|
-
return match;
|
|
153
|
-
}
|
|
154
|
-
return `\`${match}\``;
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
code: processed,
|
|
159
|
-
map: null,
|
|
160
|
-
};
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from 'vite';
|
|
2
|
-
import { preprocessMdxContent } from '../mdx/processor';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Unified MDX preprocessing plugin for Vite.
|
|
6
|
-
* This plugin ensures that ALL MDX preprocessing goes through the same pipeline
|
|
7
|
-
* as the CLI verify:content command.
|
|
8
|
-
*
|
|
9
|
-
* This is the SINGLE SOURCE OF TRUTH for MDX preprocessing.
|
|
10
|
-
* Any changes to MDX processing should be made in src/mdx/processor.ts
|
|
11
|
-
*/
|
|
12
|
-
export function unifiedMdxPreprocessor(): Plugin {
|
|
13
|
-
return {
|
|
14
|
-
name: 'unified-mdx-preprocessor',
|
|
15
|
-
enforce: 'pre', // Must run before the MDX plugin
|
|
16
|
-
async transform(code: string, id: string) {
|
|
17
|
-
if (!id.endsWith('.mdx')) return;
|
|
18
|
-
|
|
19
|
-
// Process through the unified preprocessing pipeline
|
|
20
|
-
const result = await preprocessMdxContent(code, id);
|
|
21
|
-
|
|
22
|
-
if (!result.success) {
|
|
23
|
-
// Throw the error to stop the build
|
|
24
|
-
throw result.error;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Return the preprocessed content
|
|
28
|
-
// The actual MDX compilation will be handled by @mdx-js/rollup plugin
|
|
29
|
-
return {
|
|
30
|
-
code: result.processedContent || code,
|
|
31
|
-
map: null
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
package/src/theme.scss
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
font-synthesis: none;
|
|
3
|
-
text-rendering: optimizeLegibility;
|
|
4
|
-
-webkit-font-smoothing: antialiased;
|
|
5
|
-
-moz-osx-font-smoothing: grayscale;
|
|
6
|
-
|
|
7
|
-
// Brand colors (dynamically set from dory.json via loadColors())
|
|
8
|
-
// These variables are set at runtime:
|
|
9
|
-
// --brand-primary: primary color
|
|
10
|
-
// --brand-light: light mode accent
|
|
11
|
-
// --brand-dark: dark mode accent
|
|
12
|
-
// --brand-foreground: legacy/fallback (set to primary)
|
|
13
|
-
|
|
14
|
-
// Default fallback colors (will be overridden by loadColors())
|
|
15
|
-
--brand-foreground: #0ea5e9;
|
|
16
|
-
--brand-primary: #0ea5e9;
|
|
17
|
-
--brand-light: #38bdf8;
|
|
18
|
-
--brand-dark: #0284c7;
|
|
19
|
-
|
|
20
|
-
// ShadCN/clidey/ux compatible primary color
|
|
21
|
-
// Using light color for light mode
|
|
22
|
-
--primary: var(--brand-light);
|
|
23
|
-
--primary-rgb: var(--brand-light-rgb);
|
|
24
|
-
|
|
25
|
-
--ring: rgba(0, 0, 0, 0.1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Dark mode overrides
|
|
29
|
-
:root.dark {
|
|
30
|
-
// Use dark color for dark mode
|
|
31
|
-
--primary: var(--brand-dark);
|
|
32
|
-
--primary-rgb: var(--brand-dark-rgb);
|
|
33
|
-
|
|
34
|
-
--ring: rgba(255, 255, 255, 0.1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
article h2:first-of-type {
|
|
38
|
-
display: none;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
article hr:first-of-type {
|
|
42
|
-
display: none;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
article img {
|
|
46
|
-
margin-top: 1rem;
|
|
47
|
-
margin-bottom: 1rem;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
[data-testid="header-actions"] button,
|
|
51
|
-
[data-slot="dropdown-menu-content"] {
|
|
52
|
-
border-color: var(--ring);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
[data-slot="search-input-wrapper"] {
|
|
56
|
-
font-size: 12px;
|
|
57
|
-
outline: none;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
[data-slot="search-input"] {
|
|
61
|
-
font-size: 12px;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
[data-slot="search-input-wrapper"]:focus-within {
|
|
65
|
-
outline: none !important;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
[role="dialog"] [data-slot="search-input-wrapper"]:focus-within {
|
|
69
|
-
outline: none !important;
|
|
70
|
-
border: 0px;
|
|
71
|
-
box-shadow: none !important;
|
|
72
|
-
}
|
package/src/types/config.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript interfaces for dory.json configuration
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface DoryColors {
|
|
6
|
-
primary: string;
|
|
7
|
-
light: string;
|
|
8
|
-
dark: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface DoryFonts {
|
|
12
|
-
family: string;
|
|
13
|
-
source: string;
|
|
14
|
-
format: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface DoryLogo {
|
|
18
|
-
light: string;
|
|
19
|
-
dark: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface DoryNavigationPage {
|
|
23
|
-
[key: string]: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface DoryNavigationGroup {
|
|
27
|
-
group: string;
|
|
28
|
-
pages: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface DoryNavigationTab {
|
|
32
|
-
tab: string;
|
|
33
|
-
groups: DoryNavigationGroup[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface DoryNavigation {
|
|
37
|
-
tabs: DoryNavigationTab[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface DoryConfig {
|
|
41
|
-
name: string;
|
|
42
|
-
favicon: string;
|
|
43
|
-
colors?: DoryColors;
|
|
44
|
-
fonts?: DoryFonts;
|
|
45
|
-
navigation: DoryNavigation;
|
|
46
|
-
logo: DoryLogo;
|
|
47
|
-
}
|
package/src/ui/header.tsx
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import classNames from 'classnames';
|
|
2
|
-
import { useEffect, useMemo, useState } from 'preact/hooks';
|
|
3
|
-
import { Link } from 'wouter-preact';
|
|
4
|
-
import { usePathname } from 'wouter-preact/use-browser-location';
|
|
5
|
-
import docsConfig from '../../docs/dory.json' with { type: 'json' };
|
|
6
|
-
import type { DoryConfig } from '../types/config';
|
|
7
|
-
import { MobileSearch, Search } from '../components/search';
|
|
8
|
-
import { ALL_NAVIGATION } from '../components/store';
|
|
9
|
-
import { MobileNavigation } from './mobile-navigation';
|
|
10
|
-
import { ModeToggle } from '@clidey/ux';
|
|
11
|
-
|
|
12
|
-
const config = docsConfig as DoryConfig;
|
|
13
|
-
|
|
14
|
-
function TopLevelNavItem({ href, children, isActive }: { href: string; children: string; isActive?: boolean }) {
|
|
15
|
-
return (
|
|
16
|
-
<li>
|
|
17
|
-
<Link
|
|
18
|
-
to={href}
|
|
19
|
-
className={classNames(
|
|
20
|
-
"text-xs transition hover:opacity-80",
|
|
21
|
-
{
|
|
22
|
-
"text-brand-foreground": isActive,
|
|
23
|
-
},
|
|
24
|
-
)}
|
|
25
|
-
>
|
|
26
|
-
{children}
|
|
27
|
-
</Link>
|
|
28
|
-
</li>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function Header({ className }: { className?: string }) {
|
|
33
|
-
const [isScrolled, setIsScrolled] = useState(false);
|
|
34
|
-
const pathname = usePathname();
|
|
35
|
-
|
|
36
|
-
const { light, dark } = useMemo(() => {
|
|
37
|
-
return { light: config.logo.light, dark: config.logo.dark };
|
|
38
|
-
}, []);
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
function onScroll() {
|
|
42
|
-
setIsScrolled(window.scrollY > 0);
|
|
43
|
-
}
|
|
44
|
-
onScroll();
|
|
45
|
-
window.addEventListener('scroll', onScroll, { passive: true });
|
|
46
|
-
return () => {
|
|
47
|
-
window.removeEventListener('scroll', onScroll);
|
|
48
|
-
}
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<header
|
|
53
|
-
className={classNames(
|
|
54
|
-
'sticky top-0 z-50 flex flex-none flex-wrap items-center justify-between bg-white px-4 py-5 shadow-md shadow-gray-900/5 sm:px-6 lg:px-8 dark:shadow-none',
|
|
55
|
-
className,
|
|
56
|
-
{
|
|
57
|
-
'dark:bg-gray-900/95 dark:backdrop-blur-sm dark:[@supports(backdrop-filter:blur(0))]:bg-neutral-900/75': isScrolled,
|
|
58
|
-
'dark:bg-transparent': !isScrolled,
|
|
59
|
-
},
|
|
60
|
-
)}
|
|
61
|
-
>
|
|
62
|
-
<div className="relative flex justify-between gap-6 sm:gap-8 md:grow items-center w-full px-4">
|
|
63
|
-
<div className="flex items-center grow gap-2">
|
|
64
|
-
<img src={`/${light}`} alt="logo" className="w-8 hidden dark:block" />
|
|
65
|
-
<img src={`/${dark}`} alt="logo" className="w-8 block dark:hidden" />
|
|
66
|
-
<h1 className="text-base lg:text-2xl font-bold">{config.name}</h1>
|
|
67
|
-
</div>
|
|
68
|
-
<nav className="hidden md:block">
|
|
69
|
-
<ul role="list" className="flex items-center gap-8">
|
|
70
|
-
{ALL_NAVIGATION.map((tab) => (
|
|
71
|
-
<TopLevelNavItem
|
|
72
|
-
key={tab.title}
|
|
73
|
-
href={tab.groups[0].pages[0].href}
|
|
74
|
-
isActive={pathname.startsWith(`/${tab.groups[0].pages[0].href.split('/')[1]}`)}
|
|
75
|
-
>
|
|
76
|
-
{tab.title}
|
|
77
|
-
</TopLevelNavItem>
|
|
78
|
-
))}
|
|
79
|
-
</ul>
|
|
80
|
-
</nav>
|
|
81
|
-
<div className="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:dark:bg-white/15" />
|
|
82
|
-
<div className="flex items-center gap-3" data-testid="header-actions">
|
|
83
|
-
<MobileSearch />
|
|
84
|
-
<Search />
|
|
85
|
-
<ModeToggle data-testid="mode-toggle" />
|
|
86
|
-
<MobileNavigation />
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
</header>
|
|
90
|
-
);
|
|
91
|
-
}
|