diff --git a/.changeset/code-cleanup-cache-optimization.md b/.changeset/code-cleanup-cache-optimization.md deleted file mode 100644 index 65d3f70..0000000 --- a/.changeset/code-cleanup-cache-optimization.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@signalwire/docusaurus-plugin-llms-txt': patch -'@signalwire/docusaurus-theme-llms-txt': patch ---- - -Code cleanup and cache optimization: - -- Remove dead code (className prop, normalizePathname export, CopyContentData export) -- Optimize cache implementation (replace over-engineered promise cache with minimal in-memory cache) -- Fix resize re-fetch bug (component no longer re-fetches data when switching between mobile/desktop - views) -- Reduce code size by 47% in useCopyContentData hook -- Changed the location of the CopyButtonContent component. The theme now swizzles DocItem/Layout and - conditionally puts the Copy button content component after it or below it diff --git a/.changeset/eight-clubs-read.md b/.changeset/eight-clubs-read.md deleted file mode 100644 index 3d375b8..0000000 --- a/.changeset/eight-clubs-read.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@signalwire/docusaurus-theme-llms-txt': patch ---- - -fix styling issue diff --git a/.changeset/eight-vans-sleep.md b/.changeset/eight-vans-sleep.md deleted file mode 100644 index 8781e60..0000000 --- a/.changeset/eight-vans-sleep.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@signalwire/docusaurus-plugin-llms-txt': patch -'@signalwire/docusaurus-theme-llms-txt': patch ---- - -Organize links by path now in llms-txt diff --git a/.changeset/fast-lands-sit.md b/.changeset/fast-lands-sit.md deleted file mode 100644 index 07e2176..0000000 --- a/.changeset/fast-lands-sit.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@signalwire/docusaurus-plugin-llms-txt': patch -'@signalwire/docusaurus-theme-llms-txt': patch ---- - -Fixed attachments filename bug diff --git a/.changeset/legal-toes-joke.md b/.changeset/legal-toes-joke.md deleted file mode 100644 index 490fac7..0000000 --- a/.changeset/legal-toes-joke.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@signalwire/docusaurus-plugin-llms-txt': major -'@signalwire/docusaurus-theme-llms-txt': major ---- - -Alpha Release diff --git a/.changeset/major-refactor-breadcrumbs-wrap.md b/.changeset/major-refactor-breadcrumbs-wrap.md deleted file mode 100644 index caea669..0000000 --- a/.changeset/major-refactor-breadcrumbs-wrap.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@signalwire/docusaurus-plugin-llms-txt': patch -'@signalwire/docusaurus-theme-llms-txt': patch ---- - -Major architecture improvements for better plugin compatibility: - -**Component Changes:** - -- Switched from ejecting `DocItem/Layout` to wrapping `DocBreadcrumbs` - - This prevents conflicts with other plugins that customize the layout - - Uses WRAP pattern instead of EJECT for better compatibility -- Changed internal import from `@theme-original` to `@theme-init` following Docusaurus best - practices for theme enhancers - -**Improvements:** - -- Fixed type declarations to accurately reflect component props - - Removed unused `className` prop from `CopyPageContent` - - Fixed `DocBreadcrumbs` type declaration for proper wrapping support -- Added `margin-left: auto` to ensure copy button always aligns right in desktop view -- Fixed package publishing configuration - - Added `src/theme` directory to published files for TypeScript swizzling support - - Updated devDependencies for proper type resolution - - Changed `react-icons` from exact version to version range - -**Documentation:** - -- Updated README with correct swizzle examples for `DocBreadcrumbs` -- Added explanation of `@theme-init` vs `@theme-original` usage -- Updated swizzle configuration to reflect new safe wrapping pattern - -**Compatibility:** - -- Now compatible with plugins like `docusaurus-plugin-openapi-docs` that also customize layouts -- Follows official Docusaurus theme enhancer pattern (similar to `@docusaurus/theme-live-codeblock`) -- Users can now safely wrap our enhanced breadcrumbs with `@theme-original/DocBreadcrumbs` diff --git a/packages/docusaurus-plugin-llms-txt/CHANGELOG.md b/packages/docusaurus-plugin-llms-txt/CHANGELOG.md index 3d28fc0..7577d39 100644 --- a/packages/docusaurus-plugin-llms-txt/CHANGELOG.md +++ b/packages/docusaurus-plugin-llms-txt/CHANGELOG.md @@ -1,5 +1,21 @@ # @signalwire/docusaurus-plugin-llms-txt +## 2.0.0-alpha.7 + +### Patch Changes + +- Fix code block language identifiers lost in MDX components (fixes #20) + + Code blocks were losing their language identifiers during HTML → Markdown conversion because + Docusaurus places language classes on `
` and wrapper `
` elements, not on `` + elements. Implemented a custom pre handler that: + - Extracts language from `
` element's className
+  - Falls back to checking parent element if needed
+  - Preserves code formatting by converting `
` to newlines + - Handles all edge cases gracefully with proper fallbacks + + Tested with 21 programming languages in MDX Tabs and regular code blocks. + ## 2.0.0-alpha.5 ### Patch Changes diff --git a/packages/docusaurus-plugin-llms-txt/package.json b/packages/docusaurus-plugin-llms-txt/package.json index 305305d..0e009e0 100644 --- a/packages/docusaurus-plugin-llms-txt/package.json +++ b/packages/docusaurus-plugin-llms-txt/package.json @@ -1,6 +1,6 @@ { "name": "@signalwire/docusaurus-plugin-llms-txt", - "version": "2.0.0-alpha.6", + "version": "2.0.0-alpha.7", "type": "module", "description": "Generate Markdown versions of Docusaurus HTML pages and an llms.txt index file", "main": "./lib/src/index.js", diff --git a/packages/docusaurus-plugin-llms-txt/src/transformation/plugins/code-block-handler.ts b/packages/docusaurus-plugin-llms-txt/src/transformation/plugins/code-block-handler.ts new file mode 100644 index 0000000..a204409 --- /dev/null +++ b/packages/docusaurus-plugin-llms-txt/src/transformation/plugins/code-block-handler.ts @@ -0,0 +1,127 @@ +/** + * Copyright (c) SignalWire, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { Element, Parents } from 'hast'; +import type { State } from 'hast-util-to-mdast'; +import type { Code } from 'mdast'; + +/** + * Extracts language identifier from className array. + * Looks for classes starting with 'language-' and extracts the language part. + * + * @param className - Array of class names from the element + * @returns Language identifier or null if not found + */ +function extractLanguage(className: unknown): string | null { + if (!Array.isArray(className)) { + return null; + } + + for (const cls of className) { + if (typeof cls === 'string' && cls.startsWith('language-')) { + return cls.replace('language-', ''); + } + } + + return null; +} + +/** + * Extracts text content from a hast node and its children, + * converting
elements to newlines. + * + * @param node - The hast node to extract text from + * @returns The text content with preserved line breaks + */ +function extractText(node: Element | { type: string; value?: string }): string { + // Text nodes have a value property + if ('value' in node && typeof node.value === 'string') { + return node.value; + } + + // Element nodes - check if it's a
tag + if ('tagName' in node && node.tagName === 'br') { + return '\n'; + } + + // Recursively extract text from children + if ('children' in node && Array.isArray(node.children)) { + return node.children + .map((child: Element | { type: string; value?: string }) => + extractText(child) + ) + .join(''); + } + + return ''; +} + +/** + * Custom handler for
 elements to preserve code block
+ * language identifiers.
+ *
+ * Docusaurus places language classes on 
 and parent 
elements, + * but not on elements. The default rehype-remark handler only + * checks elements, causing language identifiers to be lost. + * + * This handler: + * 1. Checks if
 contains a  child
+ * 2. Extracts language from 
 element's className
+ * 3. Falls back to checking parent element if needed
+ * 4. Extracts code content from  element
+ * 5. Returns proper mdast code node with language preserved
+ *
+ * @param state - Handler state from hast-util-to-mdast
+ * @param node - The 
 element from the hast tree
+ * @param parent - The parent element (optional)
+ * @returns An mdast code node with language identifier
+ */
+export function handlePreElement(
+  state: State,
+  node: Element,
+  parent?: Parents
+): Code | void {
+  // Verify this is a pre element
+  if (node.tagName !== 'pre') {
+    return undefined;
+  }
+
+  // Find the code element child
+  const codeElement = node.children?.find(
+    (child): child is Element =>
+      typeof child === 'object' &&
+      child !== null &&
+      'type' in child &&
+      child.type === 'element' &&
+      'tagName' in child &&
+      child.tagName === 'code'
+  );
+
+  if (!codeElement) {
+    // No code element found, let default handler process it
+    return undefined;
+  }
+
+  // Try to extract language from pre element first
+  let lang = extractLanguage(node.properties?.className);
+
+  // If not found on pre, check parent element (Docusaurus wrapper div)
+  if (!lang && parent && parent.type === 'element') {
+    lang = extractLanguage(parent.properties?.className);
+  }
+
+  // Extract the code content from the code element
+  const value = extractText(codeElement);
+
+  // Return mdast code node with language (or null if not found)
+  return {
+    type: 'code',
+    lang,
+    meta: null,
+    value,
+  };
+}
diff --git a/packages/docusaurus-plugin-llms-txt/src/transformation/plugins/plugin-registry.ts b/packages/docusaurus-plugin-llms-txt/src/transformation/plugins/plugin-registry.ts
index 960062d..ff3c5e9 100644
--- a/packages/docusaurus-plugin-llms-txt/src/transformation/plugins/plugin-registry.ts
+++ b/packages/docusaurus-plugin-llms-txt/src/transformation/plugins/plugin-registry.ts
@@ -11,6 +11,7 @@ import remarkGfm from 'remark-gfm';
 import remarkStringify from 'remark-stringify';
 import { unified } from 'unified';
 
+import { handlePreElement } from './code-block-handler';
 import rehypeLinks from './rehype-links';
 import rehypeTables from './rehype-tables';
 
@@ -97,7 +98,10 @@ export class PluginRegistry {
 
     // Always last - converts HTML AST to Markdown AST
     processor.use(rehypeRemark, {
-      handlers: { br: () => ({ type: 'html', value: '
' }) }, + handlers: { + br: () => ({ type: 'html', value: '
' }), + pre: handlePreElement, + }, }); } diff --git a/packages/docusaurus-theme-llms-txt/package.json b/packages/docusaurus-theme-llms-txt/package.json index c44dccb..d2a3625 100644 --- a/packages/docusaurus-theme-llms-txt/package.json +++ b/packages/docusaurus-theme-llms-txt/package.json @@ -53,7 +53,7 @@ "@docusaurus/module-type-aliases": "^3.0.0", "@docusaurus/plugin-content-docs": "^3.0.0", "@docusaurus/types": "^3.0.0", - "@signalwire/docusaurus-plugin-llms-txt": "2.0.0-alpha.6", + "@signalwire/docusaurus-plugin-llms-txt": "2.0.0-alpha.7", "@types/node": "^22.15.19", "@types/react": "^19.1.13", "@types/react-dom": "^19.1.9", diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index b65d065..8c3cef4 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -1,5 +1,12 @@ # website +## 0.0.1 + +### Patch Changes + +- Updated dependencies [dfedf00] + - @signalwire/docusaurus-plugin-llms-txt@2.0.0 + ## 0.0.1-alpha.1 ### Patch Changes diff --git a/website/docs/test-mdx-tabs.mdx b/website/docs/test-mdx-tabs.mdx new file mode 100644 index 0000000..769078f --- /dev/null +++ b/website/docs/test-mdx-tabs.mdx @@ -0,0 +1,520 @@ +--- +sidebar_position: 100 +title: Test MDX Tabs +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Testing Code Blocks in MDX Components + +This page tests whether code blocks inside MDX components (Tabs) preserve their language identifiers and formatting. + +## Web Languages + + + + +```javascript +function fetchUserData(userId) { + return fetch(`/api/users/${userId}`) + .then(response => response.json()) + .then(data => { + console.log('User data:', data); + return data; + }) + .catch(error => console.error('Error:', error)); +} +``` + + + + +```typescript +interface User { + id: number; + name: string; + email: string; +} + +async function fetchUserData(userId: number): Promise { + const response = await fetch(`/api/users/${userId}`); + const data: User = await response.json(); + return data; +} +``` + + + + +```html + + + + + Test Page + + +

Hello World

+

This is a test page.

+ + +``` + +
+ + +```css +.container { + display: flex; + justify-content: center; + align-items: center; + padding: 2rem; + background-color: #f0f0f0; +} + +.container h1 { + color: #333; + font-size: 2rem; +} +``` + + +
+ +## Backend Languages + + + + +```python +def calculate_fibonacci(n): + if n <= 1: + return n + + fib = [0, 1] + for i in range(2, n + 1): + fib.append(fib[i-1] + fib[i-2]) + + return fib[n] + +result = calculate_fibonacci(10) +print(f"Fibonacci(10) = {result}") +``` + + + + +```java +public class FibonacciCalculator { + public static int calculateFibonacci(int n) { + if (n <= 1) { + return n; + } + + int[] fib = new int[n + 1]; + fib[0] = 0; + fib[1] = 1; + + for (int i = 2; i <= n; i++) { + fib[i] = fib[i-1] + fib[i-2]; + } + + return fib[n]; + } +} +``` + + + + +```go +package main + +import "fmt" + +func calculateFibonacci(n int) int { + if n <= 1 { + return n + } + + fib := make([]int, n+1) + fib[0] = 0 + fib[1] = 1 + + for i := 2; i <= n; i++ { + fib[i] = fib[i-1] + fib[i-2] + } + + return fib[n] +} + +func main() { + fmt.Printf("Fibonacci(10) = %d\n", calculateFibonacci(10)) +} +``` + + + + +```rust +fn calculate_fibonacci(n: usize) -> u64 { + if n <= 1 { + return n as u64; + } + + let mut fib = vec![0u64; n + 1]; + fib[0] = 0; + fib[1] = 1; + + for i in 2..=n { + fib[i] = fib[i-1] + fib[i-2]; + } + + fib[n] +} + +fn main() { + println!("Fibonacci(10) = {}", calculate_fibonacci(10)); +} +``` + + + + +```php + +``` + + + + +## Shell and Configuration + + + + +```bash +#!/bin/bash + +# Function to calculate factorial +factorial() { + local n=$1 + if [ $n -le 1 ]; then + echo 1 + else + local prev=$(factorial $((n-1))) + echo $((n * prev)) + fi +} + +result=$(factorial 5) +echo "Factorial of 5 is: $result" +``` + + + + +```shell +#!/bin/sh + +# Deploy script +echo "Starting deployment..." + +npm install +npm run build +npm run test + +if [ $? -eq 0 ]; then + echo "Build successful, deploying..." + rsync -avz build/ user@server:/var/www/ +else + echo "Build failed, aborting deployment" + exit 1 +fi +``` + + + + +```yaml +name: CI/CD Pipeline +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '18' + - run: npm install + - run: npm test +``` + + + + +```json +{ + "name": "my-app", + "version": "1.0.0", + "scripts": { + "start": "node index.js", + "test": "jest", + "build": "webpack --mode production" + }, + "dependencies": { + "express": "^4.18.0", + "react": "^18.2.0" + } +} +``` + + + + +```toml +[package] +name = "my-app" +version = "1.0.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.0", features = ["full"] } + +[dev-dependencies] +criterion = "0.5" +``` + + + + +## Data and Query Languages + + + + +```sql +SELECT + u.id, + u.name, + u.email, + COUNT(o.id) as order_count, + SUM(o.total) as total_spent +FROM users u +LEFT JOIN orders o ON u.id = o.user_id +WHERE u.created_at > '2024-01-01' +GROUP BY u.id, u.name, u.email +HAVING COUNT(o.id) > 5 +ORDER BY total_spent DESC +LIMIT 10; +``` + + + + +```graphql +query GetUserWithOrders($userId: ID!) { + user(id: $userId) { + id + name + email + orders(first: 10, orderBy: CREATED_AT_DESC) { + edges { + node { + id + total + createdAt + items { + productName + quantity + } + } + } + } + } +} +``` + + + + +## Mobile Languages + + + + +```swift +struct ContentView: View { + @State private var count = 0 + + var body: some View { + VStack { + Text("Count: \(count)") + .font(.largeTitle) + .padding() + + Button(action: { + count += 1 + }) { + Text("Increment") + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(8) + } + } + } +} +``` + + + + +```kotlin +class MainActivity : AppCompatActivity() { + private var count = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val button = findViewById