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 forelements to preserve code block + * language identifiers. + * + * Docusaurus places language classes onand parentelements, + * but not onelements. The default rehype-remark handler only + * checkselements, causing language identifiers to be lost. + * + * This handler: + * 1. Checks ifcontains achild + * 2. Extracts language fromelement's className + * 3. Falls back to checking parent element if needed + * 4. Extracts code content fromelement + * 5. Returns proper mdast code node with language preserved + * + * @param state - Handler state from hast-util-to-mdast + * @param node - Theelement 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 + ++ + +## Backend 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; +} +``` + + ++ + +## Shell and Configuration + ++ +```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 + +``` + + ++ + +## Data and Query Languages + ++ +```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" +``` + + ++ + +## Mobile 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 + } + } + } + } + } +} +``` + + ++ + +## Other 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 ++ + +## Test Summary + +This page includes code blocks in Tabs for the following languages: + +**Web:** JavaScript, TypeScript, HTML, CSS +**Backend:** Python, Java, Go, Rust, PHP +**Shell/Config:** Bash, Shell, YAML, JSON, TOML +**Data:** SQL, GraphQL +**Mobile:** Swift, Kotlin +**Other:** Ruby, C++, C# + +All code blocks should preserve their language identifiers and formatting in the generated llms.txt files. diff --git a/website/package.json b/website/package.json index f4f1de2..c9d8b7a 100644 --- a/website/package.json +++ b/website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "version": "0.0.1-alpha.1", + "version": "0.0.1", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -24,7 +24,7 @@ "@docusaurus/tsconfig": "^3.9.1", "@docusaurus/types": "^3.9.1", "@mdx-js/react": "^3.0.0", - "@signalwire/docusaurus-plugin-llms-txt": "2.0.0-alpha.6", + "@signalwire/docusaurus-plugin-llms-txt": "2.0.0-alpha.7", "@signalwire/docusaurus-theme-llms-txt": "1.0.0-alpha.8", "clsx": "^2.0.0", "docusaurus-plugin-sass": "^0.2.6",+ +```ruby +class FibonacciCalculator + def self.calculate(n) + return n if n <= 1 + + fib = [0, 1] + (2..n).each do |i| + fib[i] = fib[i-1] + fib[i-2] + end + + fib[n] + end +end + +result = FibonacciCalculator.calculate(10) +puts "Fibonacci(10) = #{result}" +``` + + ++ +```cpp +#include ++#include + +int calculateFibonacci(int n) { + if (n <= 1) { + return n; + } + + std::vector fib(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]; +} + +int main() { + std::cout << "Fibonacci(10) = " << calculateFibonacci(10) << std::endl; + return 0; +} +``` + + + +```csharp +using System; + +public class FibonacciCalculator +{ + public static int Calculate(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]; + } + + public static void Main() + { + Console.WriteLine($"Fibonacci(10) = {Calculate(10)}"); + } +} +``` + + +