mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	feat(docs): create docs.triliumnotes.org as additional place to serve user-facing docs (#6889)
This commit is contained in:
		
							
								
								
									
										181
									
								
								.github/workflows/deploy-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								.github/workflows/deploy-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | |||||||
|  | # GitHub Actions workflow for deploying MkDocs documentation to Cloudflare Pages | ||||||
|  | # This workflow builds and deploys your MkDocs site when changes are pushed to main | ||||||
|  | name: Deploy MkDocs Documentation | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   # Trigger on push to main branch | ||||||
|  |   push: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |       - master  # Also support master branch | ||||||
|  |     # Only run when docs files change | ||||||
|  |     paths: | ||||||
|  |       - 'docs/**' | ||||||
|  |       - 'mkdocs.yml' | ||||||
|  |       - 'requirements-docs.txt' | ||||||
|  |       - '.github/workflows/deploy-docs.yml' | ||||||
|  |       - 'scripts/fix-mkdocs-structure.ts' | ||||||
|  |    | ||||||
|  |   # Allow manual triggering from Actions tab | ||||||
|  |   workflow_dispatch: | ||||||
|  |    | ||||||
|  |   # Run on pull requests for preview deployments | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |       - main | ||||||
|  |       - master | ||||||
|  |     paths: | ||||||
|  |       - 'docs/**' | ||||||
|  |       - 'mkdocs.yml' | ||||||
|  |       - 'requirements-docs.txt' | ||||||
|  |       - '.github/workflows/deploy-docs.yml' | ||||||
|  |       - 'scripts/fix-mkdocs-structure.ts' | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   build-and-deploy: | ||||||
|  |     name: Build and Deploy MkDocs | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     timeout-minutes: 10 | ||||||
|  |      | ||||||
|  |     # Required permissions for deployment | ||||||
|  |     permissions: | ||||||
|  |       contents: read | ||||||
|  |       deployments: write | ||||||
|  |       pull-requests: write # For PR preview comments | ||||||
|  |       id-token: write # For OIDC authentication (if needed) | ||||||
|  |      | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout Repository | ||||||
|  |         uses: actions/checkout@v5 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 # Fetch all history for git info and mkdocs-git-revision-date plugin | ||||||
|  |        | ||||||
|  |       - name: Setup Python | ||||||
|  |         uses: actions/setup-python@v5 | ||||||
|  |         with: | ||||||
|  |           python-version: '3.13' | ||||||
|  |           cache: 'pip' | ||||||
|  |           cache-dependency-path: 'requirements-docs.txt' | ||||||
|  |        | ||||||
|  |       - name: Install MkDocs and Dependencies | ||||||
|  |         run: | | ||||||
|  |           pip install --upgrade pip | ||||||
|  |           pip install -r requirements-docs.txt | ||||||
|  |         env: | ||||||
|  |           PIP_DISABLE_PIP_VERSION_CHECK: 1 | ||||||
|  |        | ||||||
|  |       # Setup pnpm before fixing docs structure | ||||||
|  |       - name: Setup pnpm | ||||||
|  |         uses: pnpm/action-setup@v4 | ||||||
|  |        | ||||||
|  |       # Setup Node.js with pnpm | ||||||
|  |       - name: Setup Node.js | ||||||
|  |         uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version: '20' | ||||||
|  |           cache: 'pnpm' | ||||||
|  |        | ||||||
|  |       # Install Node.js dependencies for the TypeScript script | ||||||
|  |       - name: Install Dependencies | ||||||
|  |         run: | | ||||||
|  |           pnpm install --frozen-lockfile | ||||||
|  |        | ||||||
|  |       - name: Fix Documentation Structure | ||||||
|  |         run: | | ||||||
|  |           # Fix duplicate navigation entries by moving overview pages to index.md | ||||||
|  |           pnpm run chore:fix-mkdocs-structure | ||||||
|  |        | ||||||
|  |       - name: Build MkDocs Site | ||||||
|  |         run: | | ||||||
|  |           # Build with strict mode but allow expected warnings | ||||||
|  |           mkdocs build --verbose || { | ||||||
|  |             EXIT_CODE=$? | ||||||
|  |             # Check if the only issue is expected warnings | ||||||
|  |             if mkdocs build 2>&1 | grep -E "WARNING.*(README|not found)" && \ | ||||||
|  |                [ $(mkdocs build 2>&1 | grep -c "ERROR") -eq 0 ]; then | ||||||
|  |               echo "✅ Build succeeded with expected warnings" | ||||||
|  |               mkdocs build --verbose | ||||||
|  |             else | ||||||
|  |               echo "❌ Build failed with unexpected errors" | ||||||
|  |               exit $EXIT_CODE | ||||||
|  |             fi | ||||||
|  |           } | ||||||
|  |        | ||||||
|  |       - name: Validate Built Site | ||||||
|  |         run: | | ||||||
|  |           # Basic validation that important files exist | ||||||
|  |           test -f site/index.html || (echo "ERROR: site/index.html not found" && exit 1) | ||||||
|  |           test -f site/sitemap.xml || (echo "ERROR: site/sitemap.xml not found" && exit 1) | ||||||
|  |           test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1) | ||||||
|  |           echo "✅ Site validation passed" | ||||||
|  |        | ||||||
|  |       # Install wrangler globally to avoid workspace issues | ||||||
|  |       - name: Install Wrangler | ||||||
|  |         run: | | ||||||
|  |           npm install -g wrangler | ||||||
|  |        | ||||||
|  |       # Deploy using Wrangler (use pre-installed wrangler) | ||||||
|  |       - name: Deploy to Cloudflare Pages | ||||||
|  |         id: deploy | ||||||
|  |         if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | ||||||
|  |         uses: cloudflare/wrangler-action@v3 | ||||||
|  |         with: | ||||||
|  |           apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||||||
|  |           accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||||||
|  |           command: pages deploy site --project-name=trilium-docs --branch=${{ github.ref_name }} | ||||||
|  |           wranglerVersion: ''  # Use pre-installed version | ||||||
|  |        | ||||||
|  |       # Deploy preview for PRs | ||||||
|  |       - name: Deploy Preview to Cloudflare Pages | ||||||
|  |         id: preview-deployment | ||||||
|  |         if: github.event_name == 'pull_request' | ||||||
|  |         uses: cloudflare/wrangler-action@v3 | ||||||
|  |         with: | ||||||
|  |           apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||||||
|  |           accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||||||
|  |           command: pages deploy site --project-name=trilium-docs --branch=pr-${{ github.event.pull_request.number }} | ||||||
|  |           wranglerVersion: ''  # Use pre-installed version | ||||||
|  |        | ||||||
|  |       # Post deployment URL as PR comment | ||||||
|  |       - name: Comment PR with Preview URL | ||||||
|  |         if: github.event_name == 'pull_request' | ||||||
|  |         uses: actions/github-script@v7 | ||||||
|  |         with: | ||||||
|  |           github-token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|  |           script: | | ||||||
|  |             const prNumber = context.issue.number; | ||||||
|  |             // Construct preview URL based on Cloudflare Pages pattern | ||||||
|  |             const previewUrl = `https://pr-${prNumber}.trilium-docs.pages.dev`; | ||||||
|  |             const mainUrl = 'https://docs.triliumnotes.org'; | ||||||
|  |              | ||||||
|  |             // Check if we already commented | ||||||
|  |             const comments = await github.rest.issues.listComments({ | ||||||
|  |               owner: context.repo.owner, | ||||||
|  |               repo: context.repo.repo, | ||||||
|  |               issue_number: prNumber | ||||||
|  |             }); | ||||||
|  |              | ||||||
|  |             const botComment = comments.data.find(comment =>  | ||||||
|  |               comment.user.type === 'Bot' &&  | ||||||
|  |               comment.body.includes('Documentation preview is ready') | ||||||
|  |             ); | ||||||
|  |              | ||||||
|  |             const commentBody = `📚 Documentation preview is ready!\n\n🔗 Preview URL: ${previewUrl}\n📖 Production URL: ${mainUrl}\n\n✅ All checks passed\n\n_This preview will be updated automatically with new commits._`; | ||||||
|  |              | ||||||
|  |             if (botComment) { | ||||||
|  |               // Update existing comment | ||||||
|  |               await github.rest.issues.updateComment({ | ||||||
|  |                 owner: context.repo.owner, | ||||||
|  |                 repo: context.repo.repo, | ||||||
|  |                 comment_id: botComment.id, | ||||||
|  |                 body: commentBody | ||||||
|  |               }); | ||||||
|  |             } else { | ||||||
|  |               // Create new comment | ||||||
|  |               await github.rest.issues.createComment({ | ||||||
|  |                 issue_number: prNumber, | ||||||
|  |                 owner: context.repo.owner, | ||||||
|  |                 repo: context.repo.repo, | ||||||
|  |                 body: commentBody | ||||||
|  |               }); | ||||||
|  |             } | ||||||
							
								
								
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -45,4 +45,7 @@ upload | |||||||
| *.tsbuildinfo | *.tsbuildinfo | ||||||
|  |  | ||||||
| /result | /result | ||||||
| .svelte-kit | .svelte-kit | ||||||
|  |  | ||||||
|  | # docs | ||||||
|  | site/ | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								docs/.pages
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docs/.pages
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | # Control navigation order for top-level sections | ||||||
|  | nav: | ||||||
|  |   - index.md | ||||||
|  |   - User Guide | ||||||
|  |   - Developer Guide | ||||||
|  |   - Script API | ||||||
|  |   - Release Notes | ||||||
|  |   - ...  # Include all other directories/files not explicitly listed | ||||||
							
								
								
									
										12
									
								
								docs/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								docs/README.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # Trilium Notes | ||||||
|  |  | ||||||
|  | Please see the [main documentation](index.md) or visit one of our translated versions: | ||||||
|  |  | ||||||
|  | - [Español](README.es.md) | ||||||
|  | - [Italiano](README.it.md) | ||||||
|  | - [日本語](README.ja.md) | ||||||
|  | - [Русский](README.ru.md) | ||||||
|  | - [简体中文](README-ZH_CN.md) | ||||||
|  | - [繁體中文](README-ZH_TW.md) | ||||||
|  |  | ||||||
|  | For the full application README, please visit our [GitHub repository](https://github.com/triliumnext/trilium). | ||||||
							
								
								
									
										94
									
								
								docs/index.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								docs/index.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | # Trilium Notes Documentation | ||||||
|  |  | ||||||
|  | Welcome to the official documentation for **Trilium Notes** - a hierarchical note-taking application with a focus on building large personal knowledge bases. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## What is Trilium Notes? | ||||||
|  |  | ||||||
|  | Trilium Notes is a powerful, feature-rich note-taking application designed for building and managing extensive personal knowledge bases. It offers: | ||||||
|  |  | ||||||
|  | - **Hierarchical organization** with unlimited nesting of notes | ||||||
|  | - **Rich text editing** with markdown support | ||||||
|  | - **Powerful search** capabilities | ||||||
|  | - **Note relations** and attributes for semantic connections | ||||||
|  | - **Scripting support** for automation and customization | ||||||
|  | - **Synchronization** between devices | ||||||
|  | - **Encryption** for sensitive notes | ||||||
|  | - **Web clipper** for saving web content | ||||||
|  |  | ||||||
|  | ## Quick Links | ||||||
|  |  | ||||||
|  | <div class="grid cards" markdown> | ||||||
|  |  | ||||||
|  | - :material-rocket-launch-outline: **[Quick Start Guide](User%20Guide/quick-start.md)** | ||||||
|  |      | ||||||
|  |     Get up and running with Trilium in minutes | ||||||
|  |  | ||||||
|  | - :material-download: **[Installation](User%20Guide/installation.md)** | ||||||
|  |      | ||||||
|  |     Download and install Trilium on your platform | ||||||
|  |  | ||||||
|  | - :material-docker: **[Docker Setup](User%20Guide/docker.md)** | ||||||
|  |      | ||||||
|  |     Deploy Trilium using Docker containers | ||||||
|  |  | ||||||
|  | - :material-book-open-variant: **[User Guide](User%20Guide/index.md)** | ||||||
|  |      | ||||||
|  |     Comprehensive guide to all features | ||||||
|  |  | ||||||
|  | - :material-code-braces: **[Script API](Script%20API/index.md)** | ||||||
|  |      | ||||||
|  |     Automate and extend Trilium with scripting | ||||||
|  |  | ||||||
|  | - :material-wrench: **[Developer Guide](Developer%20Guide/index.md)** | ||||||
|  |      | ||||||
|  |     Contributing and development documentation | ||||||
|  |  | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | ## Features Overview | ||||||
|  |  | ||||||
|  | ### Note Organization | ||||||
|  | - Create unlimited hierarchical note structures | ||||||
|  | - Clone notes to appear in multiple locations | ||||||
|  | - Use attributes and relations for metadata | ||||||
|  | - Template system for consistent note creation | ||||||
|  |  | ||||||
|  | ### Content Types | ||||||
|  | - **Text notes** with rich formatting | ||||||
|  | - **Code notes** with syntax highlighting | ||||||
|  | - **Canvas notes** for drawing and diagrams | ||||||
|  | - **File attachments** of any type | ||||||
|  | - **Web view** for embedded content | ||||||
|  | - **Mermaid diagrams** support | ||||||
|  |  | ||||||
|  | ### Advanced Features | ||||||
|  | - **Full-text search** with advanced operators | ||||||
|  | - **Note map** visualization | ||||||
|  | - **Day notes** for journaling | ||||||
|  | - **Book notes** for long-form content | ||||||
|  | - **Protected notes** with encryption | ||||||
|  | - **Note versioning** and history | ||||||
|  |  | ||||||
|  | ### Automation & Integration | ||||||
|  | - JavaScript-based scripting | ||||||
|  | - Custom widgets and themes | ||||||
|  | - REST API for external integrations | ||||||
|  | - Web clipper browser extension | ||||||
|  | - Import/export in multiple formats | ||||||
|  |  | ||||||
|  | ## Getting Help | ||||||
|  |  | ||||||
|  | - **[FAQ](support/faq.md)** - Frequently asked questions | ||||||
|  | - **[Troubleshooting](support/troubleshooting.md)** - Common issues and solutions | ||||||
|  | - **[Community Forum](https://github.com/triliumnext/trilium/discussions)** - Ask questions and share tips | ||||||
|  | - **[Issue Tracker](https://github.com/triliumnext/trilium/issues)** - Report bugs and request features | ||||||
|  |  | ||||||
|  | ## Contributing | ||||||
|  |  | ||||||
|  | Trilium is open-source and welcomes contributions! Check out our [Contributing Guide](Developer%20Guide/contributing.md) to get started. | ||||||
|  |  | ||||||
|  | ## License | ||||||
|  |  | ||||||
|  | Trilium Notes is licensed under [AGPL-3.0](https://github.com/triliumnext/trilium/blob/master/LICENSE). | ||||||
							
								
								
									
										111
									
								
								docs/javascripts/extra.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								docs/javascripts/extra.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | // Custom JavaScript for Trilium Notes documentation | ||||||
|  |  | ||||||
|  | // Add smooth scrolling for anchor links | ||||||
|  | document.addEventListener('DOMContentLoaded', function() { | ||||||
|  |     // Smooth scroll for internal links | ||||||
|  |     document.querySelectorAll('a[href^="#"]').forEach(anchor => { | ||||||
|  |         anchor.addEventListener('click', function (e) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             const target = document.querySelector(this.getAttribute('href')); | ||||||
|  |             if (target) { | ||||||
|  |                 target.scrollIntoView({ | ||||||
|  |                     behavior: 'smooth', | ||||||
|  |                     block: 'start' | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Add copy button to code blocks if not already present | ||||||
|  |     const codeBlocks = document.querySelectorAll('pre code'); | ||||||
|  |     codeBlocks.forEach(block => { | ||||||
|  |         if (!block.parentElement.querySelector('.copy-button')) { | ||||||
|  |             const button = document.createElement('button'); | ||||||
|  |             button.className = 'copy-button'; | ||||||
|  |             button.textContent = 'Copy'; | ||||||
|  |             button.addEventListener('click', () => { | ||||||
|  |                 navigator.clipboard.writeText(block.textContent); | ||||||
|  |                 button.textContent = 'Copied!'; | ||||||
|  |                 setTimeout(() => { | ||||||
|  |                     button.textContent = 'Copy'; | ||||||
|  |                 }, 2000); | ||||||
|  |             }); | ||||||
|  |             block.parentElement.appendChild(button); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Add external link indicators | ||||||
|  |     document.querySelectorAll('a[href^="http"]').forEach(link => { | ||||||
|  |         if (!link.hostname.includes('trilium')) { | ||||||
|  |             link.classList.add('external-link'); | ||||||
|  |             link.setAttribute('target', '_blank'); | ||||||
|  |             link.setAttribute('rel', 'noopener noreferrer'); | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Platform detection for download buttons | ||||||
|  |     const platform = detectPlatform(); | ||||||
|  |     const downloadButtons = document.querySelectorAll('.download-button'); | ||||||
|  |     downloadButtons.forEach(button => { | ||||||
|  |         if (button.dataset.platform === platform) { | ||||||
|  |             button.classList.add('recommended'); | ||||||
|  |             button.innerHTML += ' <span class="badge">Recommended</span>'; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // Detect user's platform | ||||||
|  | function detectPlatform() { | ||||||
|  |     const userAgent = navigator.userAgent.toLowerCase(); | ||||||
|  |     if (userAgent.includes('win')) return 'windows'; | ||||||
|  |     if (userAgent.includes('mac')) return 'macos'; | ||||||
|  |     if (userAgent.includes('linux')) return 'linux'; | ||||||
|  |     return 'unknown'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Add search shortcuts | ||||||
|  | document.addEventListener('keydown', function(e) { | ||||||
|  |     // Ctrl/Cmd + K to focus search | ||||||
|  |     if ((e.ctrlKey || e.metaKey) && e.key === 'k') { | ||||||
|  |         e.preventDefault(); | ||||||
|  |         const searchInput = document.querySelector('.md-search__input'); | ||||||
|  |         if (searchInput) { | ||||||
|  |             searchInput.focus(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // Version selector enhancement | ||||||
|  | const versionSelector = document.querySelector('.md-version__current'); | ||||||
|  | if (versionSelector) { | ||||||
|  |     // Add version comparison tooltip | ||||||
|  |     versionSelector.addEventListener('mouseenter', function() { | ||||||
|  |         const tooltip = document.createElement('div'); | ||||||
|  |         tooltip.className = 'version-tooltip'; | ||||||
|  |         tooltip.textContent = 'Click to view other versions'; | ||||||
|  |         this.appendChild(tooltip); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Analytics event tracking for documentation | ||||||
|  | if (typeof gtag !== 'undefined') { | ||||||
|  |     // Track external link clicks | ||||||
|  |     document.querySelectorAll('a[href^="http"]').forEach(link => { | ||||||
|  |         link.addEventListener('click', () => { | ||||||
|  |             gtag('event', 'click', { | ||||||
|  |                 'event_category': 'external_link', | ||||||
|  |                 'event_label': link.href | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // Track code copy events | ||||||
|  |     document.querySelectorAll('.copy-button').forEach(button => { | ||||||
|  |         button.addEventListener('click', () => { | ||||||
|  |             gtag('event', 'copy_code', { | ||||||
|  |                 'event_category': 'engagement', | ||||||
|  |                 'event_label': window.location.pathname | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								docs/javascripts/mathjax.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								docs/javascripts/mathjax.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | // MathJax configuration for mathematical notation support | ||||||
|  | window.MathJax = { | ||||||
|  |   tex: { | ||||||
|  |     inlineMath: [['$', '$'], ['\\(', '\\)']], | ||||||
|  |     displayMath: [['$$', '$$'], ['\\[', '\\]']], | ||||||
|  |     processEscapes: true, | ||||||
|  |     processEnvironments: true | ||||||
|  |   }, | ||||||
|  |   options: { | ||||||
|  |     ignoreHtmlClass: 'no-mathjax', | ||||||
|  |     processHtmlClass: 'mathjax' | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										121
									
								
								docs/stylesheets/extra.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								docs/stylesheets/extra.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | /* Custom styles for Trilium Notes documentation */ | ||||||
|  |  | ||||||
|  | /* Grid cards for homepage */ | ||||||
|  | .md-typeset .grid { | ||||||
|  |   display: grid; | ||||||
|  |   gap: 1rem; | ||||||
|  |   grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); | ||||||
|  |   margin-top: 1rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .md-typeset .grid.cards > ul { | ||||||
|  |   display: contents; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .md-typeset .grid.cards > ul > li { | ||||||
|  |   border: 1px solid var(--md-default-fg-color--lightest); | ||||||
|  |   border-radius: .25rem; | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   padding: 1rem; | ||||||
|  |   transition: border-color .25s, box-shadow .25s; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .md-typeset .grid.cards > ul > li:hover { | ||||||
|  |   border-color: var(--md-accent-fg-color); | ||||||
|  |   box-shadow: 0 0 0 .1rem var(--md-accent-fg-color--transparent); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Improve code block appearance */ | ||||||
|  | .md-typeset pre > code { | ||||||
|  |   font-size: .85rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Better admonition spacing */ | ||||||
|  | .md-typeset .admonition { | ||||||
|  |   margin: 1.5rem 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Trilium brand colors */ | ||||||
|  | :root { | ||||||
|  |   --trilium-primary: #4a5568; | ||||||
|  |   --trilium-accent: #805ad5; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Custom badge styles */ | ||||||
|  | .badge { | ||||||
|  |   background-color: var(--md-accent-fg-color); | ||||||
|  |   border-radius: .125rem; | ||||||
|  |   color: var(--md-accent-bg-color); | ||||||
|  |   display: inline-block; | ||||||
|  |   font-size: .75rem; | ||||||
|  |   font-weight: 700; | ||||||
|  |   padding: .125rem .375rem; | ||||||
|  |   text-transform: uppercase; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Version badge */ | ||||||
|  | .version-badge { | ||||||
|  |   background-color: var(--md-primary-fg-color); | ||||||
|  |   margin-left: .5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Platform badges */ | ||||||
|  | .platform-badge { | ||||||
|  |   margin: 0 .25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .platform-badge.windows { | ||||||
|  |   background-color: #0078d4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .platform-badge.macos { | ||||||
|  |   background-color: #000000; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .platform-badge.linux { | ||||||
|  |   background-color: #fcc624; | ||||||
|  |   color: #000000; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Improve table readability */ | ||||||
|  | .md-typeset table:not([class]) { | ||||||
|  |   font-size: .85rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .md-typeset table:not([class]) th { | ||||||
|  |   background-color: var(--md-default-bg-color); | ||||||
|  |   font-weight: 700; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* API reference styling */ | ||||||
|  | .api-method { | ||||||
|  |   background-color: var(--md-code-bg-color); | ||||||
|  |   border-radius: .125rem; | ||||||
|  |   font-family: var(--md-code-font-family); | ||||||
|  |   font-weight: 600; | ||||||
|  |   padding: .125rem .25rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-method.get { | ||||||
|  |   color: #10b981; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-method.post { | ||||||
|  |   color: #3b82f6; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-method.put { | ||||||
|  |   color: #f59e0b; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .api-method.delete { | ||||||
|  |   color: #ef4444; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* Responsive improvements */ | ||||||
|  | @media screen and (max-width: 76.1875em) { | ||||||
|  |   .md-typeset .grid { | ||||||
|  |     grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										191
									
								
								mkdocs.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								mkdocs.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | |||||||
|  | # MkDocs configuration for Trilium Notes documentation | ||||||
|  | site_name: Trilium Notes Documentation | ||||||
|  | site_url: https://docs.triliumnext.com | ||||||
|  | site_description: Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases | ||||||
|  | site_author: Trilium Notes Team | ||||||
|  |  | ||||||
|  | # Repository information | ||||||
|  | repo_name: triliumnext/trilium | ||||||
|  | repo_url: https://github.com/triliumnext/trilium | ||||||
|  | edit_uri: edit/main/docs/ | ||||||
|  |  | ||||||
|  | # Copyright | ||||||
|  | copyright: Copyright © 2025 Trilium Notes | ||||||
|  |  | ||||||
|  | # Use document-style URLs to fix image paths | ||||||
|  | use_directory_urls: false | ||||||
|  |  | ||||||
|  | # Theme configuration | ||||||
|  | theme: | ||||||
|  |   name: material | ||||||
|  |    | ||||||
|  |   # Color scheme | ||||||
|  |   palette: | ||||||
|  |     # Light mode | ||||||
|  |     - media: "(prefers-color-scheme: light)" | ||||||
|  |       scheme: default | ||||||
|  |       primary: indigo | ||||||
|  |       accent: deep-purple | ||||||
|  |       toggle: | ||||||
|  |         icon: material/brightness-7 | ||||||
|  |         name: Switch to dark mode | ||||||
|  |      | ||||||
|  |     # Dark mode | ||||||
|  |     - media: "(prefers-color-scheme: dark)" | ||||||
|  |       scheme: slate | ||||||
|  |       primary: blue-grey | ||||||
|  |       accent: deep-purple | ||||||
|  |       toggle: | ||||||
|  |         icon: material/brightness-4 | ||||||
|  |         name: Switch to light mode | ||||||
|  |    | ||||||
|  |   # Font configuration | ||||||
|  |   font: | ||||||
|  |     text: Inter | ||||||
|  |     code: JetBrains Mono | ||||||
|  |    | ||||||
|  |   # Features | ||||||
|  |   features: | ||||||
|  |     - announce.dismiss | ||||||
|  |     - content.action.edit | ||||||
|  |     - content.action.view | ||||||
|  |     - content.code.annotate | ||||||
|  |     - content.code.copy | ||||||
|  |     - content.tooltips | ||||||
|  |     - navigation.footer | ||||||
|  |     - navigation.indexes | ||||||
|  |     - navigation.instant | ||||||
|  |     - navigation.instant.prefetch | ||||||
|  |     - navigation.instant.progress | ||||||
|  |     - navigation.path | ||||||
|  |     - navigation.prune | ||||||
|  |     - navigation.sections | ||||||
|  |     - navigation.tabs | ||||||
|  |     - navigation.tabs.sticky | ||||||
|  |     - navigation.top | ||||||
|  |     - navigation.tracking | ||||||
|  |     - search.highlight | ||||||
|  |     - search.share | ||||||
|  |     - search.suggest | ||||||
|  |     - toc.follow | ||||||
|  |     - toc.integrate | ||||||
|  |    | ||||||
|  |   # Icons | ||||||
|  |   icon: | ||||||
|  |     logo: material/note-multiple | ||||||
|  |     repo: fontawesome/brands/github | ||||||
|  |  | ||||||
|  | # Plugins | ||||||
|  | plugins: | ||||||
|  |   - search: | ||||||
|  |       separator: '[\s\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])' | ||||||
|  |       lang: | ||||||
|  |         - en | ||||||
|  |   - awesome-pages: | ||||||
|  |       collapse_single_pages: false | ||||||
|  |       strict: false | ||||||
|  |       order: asc | ||||||
|  |       sort_type: natural | ||||||
|  |       order_by: title | ||||||
|  |   - minify: | ||||||
|  |       minify_html: true | ||||||
|  |       minify_js: true | ||||||
|  |       minify_css: true | ||||||
|  |       htmlmin_opts: | ||||||
|  |         remove_comments: true | ||||||
|  |   - git-revision-date-localized: | ||||||
|  |       enable_creation_date: true | ||||||
|  |       type: iso_datetime | ||||||
|  |       fallback_to_build_date: true | ||||||
|  |  | ||||||
|  | # Extensions | ||||||
|  | markdown_extensions: | ||||||
|  |   # Python Markdown | ||||||
|  |   - abbr | ||||||
|  |   - admonition | ||||||
|  |   - attr_list | ||||||
|  |   - def_list | ||||||
|  |   - footnotes | ||||||
|  |   - md_in_html | ||||||
|  |   - toc: | ||||||
|  |       permalink: true | ||||||
|  |       permalink_title: Anchor link to this section for reference | ||||||
|  |    | ||||||
|  |   # Python Markdown Extensions | ||||||
|  |   - pymdownx.arithmatex: | ||||||
|  |       generic: true | ||||||
|  |   - pymdownx.betterem: | ||||||
|  |       smart_enable: all | ||||||
|  |   - pymdownx.caret | ||||||
|  |   - pymdownx.details | ||||||
|  |   - pymdownx.emoji: | ||||||
|  |       emoji_index: !!python/name:material.extensions.emoji.twemoji | ||||||
|  |       emoji_generator: !!python/name:material.extensions.emoji.to_svg | ||||||
|  |   - pymdownx.highlight: | ||||||
|  |       anchor_linenums: true | ||||||
|  |       line_spans: __span | ||||||
|  |       pygments_lang_class: true | ||||||
|  |   - pymdownx.inlinehilite | ||||||
|  |   - pymdownx.keys | ||||||
|  |   - pymdownx.mark | ||||||
|  |   - pymdownx.smartsymbols | ||||||
|  |   - pymdownx.snippets | ||||||
|  |   - pymdownx.superfences: | ||||||
|  |       custom_fences: | ||||||
|  |         - name: mermaid | ||||||
|  |           class: mermaid | ||||||
|  |           format: !!python/name:pymdownx.superfences.fence_code_format | ||||||
|  |   - pymdownx.tabbed: | ||||||
|  |       alternate_style: true | ||||||
|  |       combine_header_slug: true | ||||||
|  |   - pymdownx.tasklist: | ||||||
|  |       custom_checkbox: true | ||||||
|  |   - pymdownx.tilde | ||||||
|  |  | ||||||
|  | # Extra CSS and JavaScript (if needed) | ||||||
|  | extra_css: | ||||||
|  |   - stylesheets/extra.css | ||||||
|  |  | ||||||
|  | extra_javascript: | ||||||
|  |   - javascripts/extra.js | ||||||
|  |   # MathJax for mathematical notation | ||||||
|  |   - javascripts/mathjax.js | ||||||
|  |   - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js | ||||||
|  |  | ||||||
|  | # Extra configuration | ||||||
|  | extra: | ||||||
|  |   # Social links | ||||||
|  |   social: | ||||||
|  |     - icon: fontawesome/brands/github | ||||||
|  |       link: https://github.com/triliumnext/trilium | ||||||
|  |     - icon: fontawesome/brands/docker | ||||||
|  |       link: https://hub.docker.com/r/triliumnext/trilium | ||||||
|  |     - icon: fontawesome/solid/globe | ||||||
|  |       link: https://trilium.cc | ||||||
|  |    | ||||||
|  |   # Analytics (optional - add your own if needed) | ||||||
|  |   analytics: | ||||||
|  |     provider: google | ||||||
|  |     property: G-XXXXXXXXXX  # Replace with your Google Analytics ID | ||||||
|  |     feedback: | ||||||
|  |       title: Was this page helpful? | ||||||
|  |       ratings: | ||||||
|  |         - icon: material/emoticon-happy-outline | ||||||
|  |           name: This page was helpful | ||||||
|  |           data: 1 | ||||||
|  |           note: >- | ||||||
|  |             Thanks for your feedback! | ||||||
|  |         - icon: material/emoticon-sad-outline | ||||||
|  |           name: This page could be improved | ||||||
|  |           data: 0 | ||||||
|  |           note: >- | ||||||
|  |             Thanks for your feedback! Help us improve this page by | ||||||
|  |             <a href="https://github.com/triliumnext/trilium/issues/new/?title=[Feedback]+{title}+-+{url}" target="_blank" rel="noopener">opening an issue</a>. | ||||||
|  |    | ||||||
|  |   # Version | ||||||
|  |   version: | ||||||
|  |     provider: mike | ||||||
|  |     default: stable | ||||||
|  |  | ||||||
|  | # Navigation is automatically generated from folder structure by awesome-pages plugin | ||||||
|  | # To customize order or titles, create .pages files in directories | ||||||
| @@ -24,6 +24,7 @@ | |||||||
|     "chore:generate-openapi": "tsx ./scripts/generate-openapi.ts", |     "chore:generate-openapi": "tsx ./scripts/generate-openapi.ts", | ||||||
|     "chore:update-build-info": "tsx ./scripts/update-build-info.ts", |     "chore:update-build-info": "tsx ./scripts/update-build-info.ts", | ||||||
|     "chore:update-version": "tsx ./scripts/update-version.ts", |     "chore:update-version": "tsx ./scripts/update-version.ts", | ||||||
|  |     "chore:fix-mkdocs-structure": "tsx ./scripts/fix-mkdocs-structure.ts", | ||||||
|     "edit-docs:edit-docs": "pnpm run --filter edit-docs edit-docs", |     "edit-docs:edit-docs": "pnpm run --filter edit-docs edit-docs", | ||||||
|     "edit-docs:edit-demo": "pnpm run --filter edit-docs edit-demo", |     "edit-docs:edit-demo": "pnpm run --filter edit-docs edit-demo", | ||||||
|     "test:all": "pnpm test:parallel && pnpm test:sequential", |     "test:all": "pnpm test:parallel && pnpm test:sequential", | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								requirements-docs.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								requirements-docs.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | # MkDocs and Material theme requirements for Trilium documentation | ||||||
|  | mkdocs>=1.6.0 | ||||||
|  | mkdocs-material>=9.5.0 | ||||||
|  | mkdocs-material-extensions>=1.3.0 | ||||||
|  |  | ||||||
|  | # Essential plugins | ||||||
|  | mkdocs-awesome-pages-plugin>=2.9.0  # Auto-generate navigation from folder structure | ||||||
|  | mkdocs-minify-plugin>=0.8.0 | ||||||
|  | mkdocs-git-revision-date-localized-plugin>=1.2.0 | ||||||
|  |  | ||||||
|  | # Optional but recommended plugins | ||||||
|  | mkdocs-redirects>=1.2.0 | ||||||
|  | mkdocs-rss-plugin>=1.12.0 | ||||||
|  | mkdocs-glightbox>=0.3.0 | ||||||
|  |  | ||||||
|  | # For advanced features | ||||||
|  | pillow>=10.0.0  # For social cards generation | ||||||
|  | cairosvg>=2.7.0  # For social cards with SVG support | ||||||
|  |  | ||||||
|  | # Search enhancements | ||||||
|  | mkdocs-material[imaging]>=9.5.0 | ||||||
							
								
								
									
										308
									
								
								scripts/fix-mkdocs-structure.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								scripts/fix-mkdocs-structure.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,308 @@ | |||||||
|  | #!/usr/bin/env node | ||||||
|  | /** | ||||||
|  |  * Fix MkDocs structure by moving overview pages to index.md inside their directories. | ||||||
|  |  * This prevents duplicate navigation entries when a file and directory have the same name. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | import * as fs from 'fs'; | ||||||
|  | import * as path from 'path'; | ||||||
|  |  | ||||||
|  | interface FixResult { | ||||||
|  |     message: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Find markdown files that have a corresponding directory with the same name, | ||||||
|  |  * and move them to index.md inside that directory. | ||||||
|  |  */ | ||||||
|  | function fixDuplicateEntries(docsDir: string): FixResult[] { | ||||||
|  |     const fixesMade: FixResult[] = []; | ||||||
|  |      | ||||||
|  |     function walkDir(dir: string): void { | ||||||
|  |         let files: string[]; | ||||||
|  |         try { | ||||||
|  |             files = fs.readdirSync(dir); | ||||||
|  |         } catch (err) { | ||||||
|  |             console.warn(`Warning: Unable to read directory ${dir}: ${err.message}`); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         for (const file of files) { | ||||||
|  |             const filePath = path.join(dir, file); | ||||||
|  |             let stat: fs.Stats; | ||||||
|  |              | ||||||
|  |             try { | ||||||
|  |                 stat = fs.statSync(filePath); | ||||||
|  |             } catch (err) { | ||||||
|  |                 // File might have been moved already, skip it | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             if (stat.isDirectory()) { | ||||||
|  |                 walkDir(filePath); | ||||||
|  |             } else if (file.endsWith('.md')) { | ||||||
|  |                 const basename = file.slice(0, -3); // Remove .md extension | ||||||
|  |                 const dirPath = path.join(dir, basename); | ||||||
|  |                  | ||||||
|  |                 // Check if there's a directory with the same name | ||||||
|  |                 if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) { | ||||||
|  |                     const indexPath = path.join(dirPath, 'index.md'); | ||||||
|  |                      | ||||||
|  |                     // Check if index.md already exists in that directory | ||||||
|  |                     if (!fs.existsSync(indexPath)) { | ||||||
|  |                         // Move the file to index.md in the directory | ||||||
|  |                         fs.renameSync(filePath, indexPath); | ||||||
|  |                         fixesMade.push({ | ||||||
|  |                             message: `Moved ${path.relative(docsDir, filePath)} -> ${path.relative(docsDir, indexPath)}` | ||||||
|  |                         }); | ||||||
|  |                          | ||||||
|  |                         // Move associated images with pattern basename_* | ||||||
|  |                         try { | ||||||
|  |                             const dirFiles = fs.readdirSync(dir); | ||||||
|  |                             for (const imgFile of dirFiles) { | ||||||
|  |                                 if (imgFile.startsWith(`${basename}_`)) { | ||||||
|  |                                     const imgSrc = path.join(dir, imgFile); | ||||||
|  |                                     try { | ||||||
|  |                                         if (!fs.statSync(imgSrc).isDirectory()) { | ||||||
|  |                                             const imgDest = path.join(dirPath, imgFile); | ||||||
|  |                                             fs.renameSync(imgSrc, imgDest); | ||||||
|  |                                             fixesMade.push({ | ||||||
|  |                                                 message: `Moved ${path.relative(docsDir, imgSrc)} -> ${path.relative(docsDir, imgDest)}` | ||||||
|  |                                             }); | ||||||
|  |                                         } | ||||||
|  |                                     } catch (err) { | ||||||
|  |                                         // File might have been moved already, skip it | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } catch (err) { | ||||||
|  |                             // Directory might not exist anymore, skip it | ||||||
|  |                         } | ||||||
|  |                          | ||||||
|  |                         // Move exact match images | ||||||
|  |                         const imgExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg']; | ||||||
|  |                         for (const ext of imgExtensions) { | ||||||
|  |                             const imgFile = path.join(dir, `${basename}${ext}`); | ||||||
|  |                             if (fs.existsSync(imgFile)) { | ||||||
|  |                                 const imgDest = path.join(dirPath, `${basename}${ext}`); | ||||||
|  |                                 fs.renameSync(imgFile, imgDest); | ||||||
|  |                                 fixesMade.push({ | ||||||
|  |                                     message: `Moved ${path.relative(docsDir, imgFile)} -> ${path.relative(docsDir, imgDest)}` | ||||||
|  |                                 }); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     walkDir(docsDir); | ||||||
|  |     return fixesMade; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Update references in markdown files to point to the new locations. | ||||||
|  |  */ | ||||||
|  | function updateReferences(docsDir: string): FixResult[] { | ||||||
|  |     const updatesMade: FixResult[] = []; | ||||||
|  |      | ||||||
|  |     function fixLink(match: string, text: string, link: string, currentDir: string, isIndex: boolean): string { | ||||||
|  |         // Skip external links | ||||||
|  |         if (link.startsWith('http')) { | ||||||
|  |             return match; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Decode URL-encoded paths for processing | ||||||
|  |         // Use decodeURIComponent which is equivalent to Python's unquote | ||||||
|  |         let decodedLink: string; | ||||||
|  |         try { | ||||||
|  |             decodedLink = decodeURIComponent(link); | ||||||
|  |         } catch (err) { | ||||||
|  |             // If decoding fails, use the original link | ||||||
|  |             decodedLink = link; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Special case: if we're in index.md and the link starts with the parent directory name | ||||||
|  |         if (isIndex && decodedLink.includes('/')) { | ||||||
|  |             const pathParts = decodedLink.split('/'); | ||||||
|  |             const parentDirName = path.basename(currentDir); | ||||||
|  |              | ||||||
|  |             // Check if first part matches the parent directory name | ||||||
|  |             if (pathParts[0] === parentDirName) { | ||||||
|  |                 // This is a self-referential path, strip the first part | ||||||
|  |                 const fixedLink = pathParts.slice(1).join('/'); | ||||||
|  |                 // Continue processing with the fixed link | ||||||
|  |                 const decodedFixedLink = fixedLink; | ||||||
|  |                  | ||||||
|  |                 // Check if this fixed link points to a directory with index.md | ||||||
|  |                 if (!decodedFixedLink.startsWith('/')) { | ||||||
|  |                     const resolvedPath = path.resolve(currentDir, decodedFixedLink); | ||||||
|  |                      | ||||||
|  |                     if (resolvedPath.endsWith('.md')) { | ||||||
|  |                         const potentialDir = resolvedPath.slice(0, -3); | ||||||
|  |                         const potentialIndex = path.join(potentialDir, 'index.md'); | ||||||
|  |                          | ||||||
|  |                         if (fs.existsSync(potentialIndex)) { | ||||||
|  |                             // Check if they share the same parent directory | ||||||
|  |                             if (path.dirname(potentialDir) === path.dirname(currentDir)) { | ||||||
|  |                                 // It's a sibling - just use directory name | ||||||
|  |                                 const dirName = path.basename(potentialDir).replace(/ /g, '%20'); | ||||||
|  |                                 return `[${text}](${dirName}/)`; | ||||||
|  |                             } | ||||||
|  |                              | ||||||
|  |                             // Calculate relative path from current file to the directory | ||||||
|  |                             const newPath = path.relative(currentDir, potentialDir).replace(/\\/g, '/').replace(/ /g, '%20'); | ||||||
|  |                             return `[${text}](${newPath}/)`; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 // If no special handling needed for the fixed link, return it as-is | ||||||
|  |                 const fixedLinkEncoded = fixedLink.replace(/ /g, '%20'); | ||||||
|  |                 return `[${text}](${fixedLinkEncoded})`; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // For any .md link, check if there's a directory with index.md | ||||||
|  |         if (!decodedLink.startsWith('/')) { | ||||||
|  |             const resolvedPath = path.resolve(currentDir, decodedLink); | ||||||
|  |              | ||||||
|  |             // Check if this points to a file that should be a directory | ||||||
|  |             if (resolvedPath.endsWith('.md')) { | ||||||
|  |                 const potentialDir = resolvedPath.slice(0, -3); | ||||||
|  |                 const potentialIndex = path.join(potentialDir, 'index.md'); | ||||||
|  |                  | ||||||
|  |                 // If a directory with index.md exists, update the link | ||||||
|  |                 if (fs.existsSync(potentialIndex)) { | ||||||
|  |                     if (isIndex) { | ||||||
|  |                         // Check if they share the same parent directory | ||||||
|  |                         if (path.dirname(potentialDir) === path.dirname(currentDir)) { | ||||||
|  |                             // It's a sibling - just use directory name | ||||||
|  |                             const dirName = path.basename(potentialDir).replace(/ /g, '%20'); | ||||||
|  |                             return `[${text}](${dirName}/)`; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |                     // Calculate relative path from current file to the directory | ||||||
|  |                     const newPath = path.relative(currentDir, potentialDir).replace(/\\/g, '/').replace(/ /g, '%20'); | ||||||
|  |                     return `[${text}](${newPath}/)`; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         // Also handle local references (same directory) - should be 'if', not 'elif' | ||||||
|  |         // This is intentional to handle both absolute and relative paths | ||||||
|  |         if (!decodedLink.includes('/')) { | ||||||
|  |             const basename = decodedLink.slice(0, -3); // Remove .md | ||||||
|  |             const possibleDir = path.join(currentDir, basename); | ||||||
|  |              | ||||||
|  |             if (fs.existsSync(possibleDir) && fs.statSync(possibleDir).isDirectory()) { | ||||||
|  |                 const encodedBasename = basename.replace(/ /g, '%20'); | ||||||
|  |                 return `[${text}](${encodedBasename}/)`; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         return match; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     function walkDir(dir: string): void { | ||||||
|  |         let files: string[]; | ||||||
|  |         try { | ||||||
|  |             files = fs.readdirSync(dir); | ||||||
|  |         } catch (err) { | ||||||
|  |             console.warn(`Warning: Unable to read directory ${dir}: ${err.message}`); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         for (const file of files) { | ||||||
|  |             const filePath = path.join(dir, file); | ||||||
|  |             let stat: fs.Stats; | ||||||
|  |              | ||||||
|  |             try { | ||||||
|  |                 stat = fs.statSync(filePath); | ||||||
|  |             } catch (err) { | ||||||
|  |                 // File might have been moved already, skip it | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             if (stat.isDirectory()) { | ||||||
|  |                 walkDir(filePath); | ||||||
|  |             } else if (file.endsWith('.md')) { | ||||||
|  |                 let content = fs.readFileSync(filePath, 'utf-8'); | ||||||
|  |                 const originalContent = content; | ||||||
|  |                  | ||||||
|  |                 const isIndex = file === 'index.md'; | ||||||
|  |                 const currentDir = path.dirname(filePath); | ||||||
|  |                  | ||||||
|  |                 // Update markdown links: [text](path.md) | ||||||
|  |                 const pattern = /\[([^\]]*)\]\(([^)]+\.md)\)/g; | ||||||
|  |                 content = content.replace(pattern, (match, text, link) => { | ||||||
|  |                     return fixLink(match, text, link, currentDir, isIndex); | ||||||
|  |                 }); | ||||||
|  |                  | ||||||
|  |                 if (content !== originalContent) { | ||||||
|  |                     fs.writeFileSync(filePath, content, 'utf-8'); | ||||||
|  |                     updatesMade.push({ | ||||||
|  |                         message: `Updated references in ${path.relative(docsDir, filePath)}` | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     walkDir(docsDir); | ||||||
|  |     return updatesMade; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function main(): number { | ||||||
|  |     // Get the docs directory | ||||||
|  |     const scriptDir = path.dirname(new URL(import.meta.url).pathname); | ||||||
|  |     const projectRoot = path.dirname(scriptDir); | ||||||
|  |     const docsDir = path.join(projectRoot, 'docs'); | ||||||
|  |      | ||||||
|  |     // Handle Windows paths (remove leading slash if on Windows) | ||||||
|  |     const normalizedDocsDir = process.platform === 'win32' && docsDir.startsWith('/')  | ||||||
|  |         ? docsDir.substring(1)  | ||||||
|  |         : docsDir; | ||||||
|  |      | ||||||
|  |     if (!fs.existsSync(normalizedDocsDir)) { | ||||||
|  |         console.error(`Error: docs directory not found at ${normalizedDocsDir}`); | ||||||
|  |         return 1; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log(`Fixing MkDocs structure in ${normalizedDocsDir}`); | ||||||
|  |     console.log('-'.repeat(50)); | ||||||
|  |      | ||||||
|  |     // Fix duplicate entries | ||||||
|  |     const fixes = fixDuplicateEntries(normalizedDocsDir); | ||||||
|  |     if (fixes.length > 0) { | ||||||
|  |         console.log('Files reorganized:'); | ||||||
|  |         for (const fix of fixes) { | ||||||
|  |             console.log(`  - ${fix.message}`); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         console.log('No duplicate entries found that need fixing'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log(); | ||||||
|  |      | ||||||
|  |     // Update references | ||||||
|  |     const updates = updateReferences(normalizedDocsDir); | ||||||
|  |     if (updates.length > 0) { | ||||||
|  |         console.log('References updated:'); | ||||||
|  |         for (const update of updates) { | ||||||
|  |             console.log(`  - ${update.message}`); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         console.log('No references needed updating'); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     console.log('-'.repeat(50)); | ||||||
|  |     console.log(`Structure fix complete: ${fixes.length} files moved, ${updates.length} files updated`); | ||||||
|  |      | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Run the main function | ||||||
|  | process.exit(main()); | ||||||
		Reference in New Issue
	
	Block a user