Merge branch 'main' into history_diff

This commit is contained in:
Elian Doran 2025-09-06 22:37:55 +03:00 committed by GitHub
commit 3ba9c3b4a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1905 additions and 476 deletions

View File

@ -10,7 +10,7 @@ runs:
steps: steps:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22
cache: "pnpm" cache: "pnpm"

183
.github/workflows/deploy-docs.yml vendored Normal file
View File

@ -0,0 +1,183 @@
# 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/**'
- 'README.md' # README is synced to docs/index.md
- '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/**'
- 'README.md' # README is synced to docs/index.md
- '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@v6
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@v5
with:
node-version: '22'
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@v8
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
});
}

View File

@ -28,12 +28,15 @@ jobs:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22
cache: "pnpm" cache: "pnpm"
- run: pnpm install --frozen-lockfile - run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm typecheck
- name: Run the unit tests - name: Run the unit tests
run: pnpm run test:all run: pnpm run test:all

View File

@ -44,7 +44,7 @@ jobs:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22
cache: "pnpm" cache: "pnpm"
@ -144,7 +144,7 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'

View File

@ -51,7 +51,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'

View File

@ -20,7 +20,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'

View File

@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v5
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'

3
.gitignore vendored
View File

@ -46,3 +46,6 @@ upload
/result /result
.svelte-kit .svelte-kit
# docs
site/

View File

@ -38,10 +38,10 @@
"@playwright/test": "1.55.0", "@playwright/test": "1.55.0",
"@stylistic/eslint-plugin": "5.3.1", "@stylistic/eslint-plugin": "5.3.1",
"@types/express": "5.0.3", "@types/express": "5.0.3",
"@types/node": "22.18.0", "@types/node": "22.18.1",
"@types/yargs": "17.0.33", "@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"eslint": "9.34.0", "eslint": "9.35.0",
"eslint-plugin-simple-import-sort": "12.1.1", "eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25", "esm": "3.2.25",
"jsdoc": "4.0.4", "jsdoc": "4.0.4",

View File

@ -15,7 +15,7 @@
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular" "circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
}, },
"dependencies": { "dependencies": {
"@eslint/js": "9.34.0", "@eslint/js": "9.35.0",
"@excalidraw/excalidraw": "0.18.0", "@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19", "@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19", "@fullcalendar/daygrid": "6.1.19",
@ -39,9 +39,9 @@
"dayjs-plugin-utc": "0.1.2", "dayjs-plugin-utc": "0.1.2",
"debounce": "2.2.0", "debounce": "2.2.0",
"draggabilly": "3.0.0", "draggabilly": "3.0.0",
"force-graph": "1.50.1", "force-graph": "1.51.0",
"globals": "16.3.0", "globals": "16.3.0",
"i18next": "25.4.2", "i18next": "25.5.2",
"i18next-http-backend": "3.0.2", "i18next-http-backend": "3.0.2",
"jquery": "3.7.1", "jquery": "3.7.1",
"jquery.fancytree": "2.38.5", "jquery.fancytree": "2.38.5",
@ -52,7 +52,7 @@
"leaflet-gpx": "2.2.0", "leaflet-gpx": "2.2.0",
"mark.js": "8.11.1", "mark.js": "8.11.1",
"marked": "16.2.1", "marked": "16.2.1",
"mermaid": "11.10.1", "mermaid": "11.11.0",
"mind-elixir": "5.0.6", "mind-elixir": "5.0.6",
"normalize.css": "8.0.1", "normalize.css": "8.0.1",
"panzoom": "9.4.3", "panzoom": "9.4.3",
@ -69,7 +69,7 @@
"@types/bootstrap": "5.2.10", "@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.33", "@types/jquery": "3.5.33",
"@types/leaflet": "1.9.20", "@types/leaflet": "1.9.20",
"@types/leaflet-gpx": "1.3.7", "@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12", "@types/mark.js": "8.11.12",
"@types/tabulator-tables": "6.2.10", "@types/tabulator-tables": "6.2.10",
"copy-webpack-plugin": "13.0.1", "copy-webpack-plugin": "13.0.1",

View File

@ -13,6 +13,7 @@
--theme-style: dark; --theme-style: dark;
--native-titlebar-background: #00000000; --native-titlebar-background: #00000000;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: #272727; --main-background-color: #272727;
--main-text-color: #ccc; --main-text-color: #ccc;
@ -147,6 +148,7 @@
--launcher-pane-vert-button-hover-background: #ffffff1c; --launcher-pane-vert-button-hover-background: #ffffff1c;
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2); --launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-vert-background-color-bgfx: #00000026; /* When background effects enabled */
--launcher-pane-horiz-border-color: rgb(22, 22, 22); --launcher-pane-horiz-border-color: rgb(22, 22, 22);
--launcher-pane-horiz-background-color: #282828; --launcher-pane-horiz-background-color: #282828;
@ -155,6 +157,8 @@
--launcher-pane-horiz-button-hover-background: #ffffff1c; --launcher-pane-horiz-button-hover-background: #ffffff1c;
--launcher-pane-horiz-button-hover-shadow: unset; --launcher-pane-horiz-button-hover-shadow: unset;
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-horiz-background-color-bgfx: #ffffff17; /* When background effects enabled */
--launcher-pane-horiz-border-color-bgfx: #00000080; /* When background effects enabled */
--protected-session-active-icon-color: #8edd8e; --protected-session-active-icon-color: #8edd8e;
--sync-status-error-pulse-color: #f47871; --sync-status-error-pulse-color: #f47871;

View File

@ -13,6 +13,7 @@
--theme-style: light; --theme-style: light;
--native-titlebar-background: #ffffff00; --native-titlebar-background: #ffffff00;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: white; --main-background-color: white;
--main-text-color: black; --main-text-color: black;
@ -121,11 +122,11 @@
--left-pane-collapsed-border-color: #0000000d; --left-pane-collapsed-border-color: #0000000d;
--left-pane-background-color: #f2f2f2; --left-pane-background-color: #f2f2f2;
--left-pane-text-color: #383838; --left-pane-text-color: #383838;
--left-pane-item-hover-background: #eaeaea; --left-pane-item-hover-background: rgba(0, 0, 0, 0.032);
--left-pane-item-selected-background: white; --left-pane-item-selected-background: white;
--left-pane-item-selected-color: black; --left-pane-item-selected-color: black;
--left-pane-item-selected-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); --left-pane-item-selected-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
--left-pane-item-action-button-background: #d7d7d7; --left-pane-item-action-button-background: rgba(0, 0, 0, 0.11);
--left-pane-item-action-button-color: inherit; --left-pane-item-action-button-color: inherit;
--left-pane-item-action-button-hover-background: white; --left-pane-item-action-button-hover-background: white;
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15); --left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
@ -141,6 +142,7 @@
--launcher-pane-vert-button-hover-background: white; --launcher-pane-vert-button-hover-background: white;
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.075); --launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.075);
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-vert-background-color-bgfx: #00000009; /* When background effects enabled */
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.1); --launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.1);
--launcher-pane-horiz-background-color: #fafafa; --launcher-pane-horiz-background-color: #fafafa;
@ -148,6 +150,8 @@
--launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background); --launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background);
--launcher-pane-horiz-button-hover-shadow: unset; --launcher-pane-horiz-button-hover-shadow: unset;
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-horiz-background-color-bgfx: #ffffffb3; /* When background effects enabled */
--launcher-pane-horiz-border-color-bgfx: #00000026; /* When background effects enabled */
--protected-session-active-icon-color: #16b516; --protected-session-active-icon-color: #16b516;
--sync-status-error-pulse-color: #ff5528; --sync-status-error-pulse-color: #ff5528;

View File

@ -36,31 +36,23 @@ body.mobile {
/* #region Mica */ /* #region Mica */
body.background-effects.platform-win32 { body.background-effects.platform-win32 {
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.15);
--launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.7);
--launcher-pane-vert-background-color: rgba(255, 255, 255, 0.055);
--tab-background-color: transparent;
--new-tab-button-background: transparent;
--active-tab-background-color: var(--launcher-pane-horiz-background-color);
--background-material: tabbed; --background-material: tabbed;
} --launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
@media (prefers-color-scheme: dark) { --launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
body.background-effects.platform-win32 { --tab-background-color: var(--window-background-color-bgfx);
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.5); --new-tab-button-background: var(--window-background-color-bgfx);
--launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.09); --active-tab-background-color: var(--launcher-pane-horiz-background-color);
}
} }
body.background-effects.platform-win32.layout-vertical { body.background-effects.platform-win32.layout-vertical {
--left-pane-background-color: transparent; --left-pane-background-color: var(--window-background-color-bgfx);
--left-pane-item-hover-background: rgba(127, 127, 127, 0.05);
--background-material: mica; --background-material: mica;
} }
body.background-effects.platform-win32, body.background-effects.platform-win32,
body.background-effects.platform-win32 #root-widget { body.background-effects.platform-win32 #root-widget {
background: transparent !important; background: var(--window-background-color-bgfx) !important;
} }
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container, body.background-effects.platform-win32.layout-horizontal #horizontal-main-container,

View File

@ -30,13 +30,16 @@
"search_note": "Wyszukaj notatkę po nazwie", "search_note": "Wyszukaj notatkę po nazwie",
"link_title_arbitrary": "Tytuł linku można dowolnie zmieniać", "link_title_arbitrary": "Tytuł linku można dowolnie zmieniać",
"link_title": "Tytuł linku", "link_title": "Tytuł linku",
"button_add_link": "Dodaj link" "button_add_link": "Dodaj link",
"help_on_links": "Pomoc dotycząca linków",
"link_title_mirrors": "tytuł linku odzwierciedla tytuł obecnej notatki"
}, },
"branch_prefix": { "branch_prefix": {
"save": "Zapisz", "save": "Zapisz",
"edit_branch_prefix": "Edytuj prefiks gałęzi", "edit_branch_prefix": "Edytuj prefiks gałęzi",
"prefix": "Prefiks: ", "prefix": "Prefiks: ",
"branch_prefix_saved": "Zapisano prefiks gałęzi." "branch_prefix_saved": "Zapisano prefiks gałęzi.",
"help_on_tree_prefix": "Pomoc dotycząca prefiksu drzewa"
}, },
"bulk_actions": { "bulk_actions": {
"labels": "Etykiety", "labels": "Etykiety",
@ -98,7 +101,8 @@
"prefix_optional": "Prefiks (opcjonalne)", "prefix_optional": "Prefiks (opcjonalne)",
"clone_to_selected_note": "Sklonuj do wybranej notatki", "clone_to_selected_note": "Sklonuj do wybranej notatki",
"no_path_to_clone_to": "Brak ścieżki do sklonowania.", "no_path_to_clone_to": "Brak ścieżki do sklonowania.",
"note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\"" "note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\"",
"help_on_links": "Pomoc dotycząca linków"
}, },
"help": { "help": {
"title": "Ściągawka", "title": "Ściągawka",

View File

@ -28,7 +28,7 @@
"better-sqlite3": "12.2.0" "better-sqlite3": "12.2.0"
}, },
"devDependencies": { "devDependencies": {
"@anthropic-ai/sdk": "0.60.0", "@anthropic-ai/sdk": "0.61.0",
"@braintree/sanitize-url": "7.1.1", "@braintree/sanitize-url": "7.1.1",
"@electron/remote": "2.1.3", "@electron/remote": "2.1.3",
"@preact/preset-vite": "2.10.2", "@preact/preset-vite": "2.10.2",
@ -88,7 +88,7 @@
"express": "5.1.0", "express": "5.1.0",
"express-http-proxy": "2.1.1", "express-http-proxy": "2.1.1",
"express-openid-connect": "^2.17.1", "express-openid-connect": "^2.17.1",
"express-rate-limit": "8.0.1", "express-rate-limit": "8.1.0",
"express-session": "1.18.2", "express-session": "1.18.2",
"file-uri-to-path": "2.0.0", "file-uri-to-path": "2.0.0",
"fs-extra": "11.3.1", "fs-extra": "11.3.1",
@ -97,7 +97,7 @@
"html2plaintext": "2.1.4", "html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2", "http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6", "https-proxy-agent": "7.0.6",
"i18next": "25.4.2", "i18next": "25.5.2",
"i18next-fs-backend": "2.6.0", "i18next-fs-backend": "2.6.0",
"image-type": "6.0.0", "image-type": "6.0.0",
"ini": "5.0.0", "ini": "5.0.0",

View File

@ -74,7 +74,8 @@
"zoom-out": "Pomniejsz", "zoom-out": "Pomniejsz",
"zoom-in": "Powiększ", "zoom-in": "Powiększ",
"print-active-note": "Drukuj aktywną notatkę", "print-active-note": "Drukuj aktywną notatkę",
"toggle-full-screen": "Przełącz pełny ekran" "toggle-full-screen": "Przełącz pełny ekran",
"cut-into-note": "Wycina zaznaczony tekst i tworzy podrzędną notatkę z tym tekstem"
}, },
"keyboard_action_names": { "keyboard_action_names": {
"zoom-in": "Powiększ", "zoom-in": "Powiększ",

View File

@ -5,7 +5,7 @@ import attributeService from "../services/attributes.js";
import config from "../services/config.js"; import config from "../services/config.js";
import optionService from "../services/options.js"; import optionService from "../services/options.js";
import log from "../services/log.js"; import log from "../services/log.js";
import { isDev, isElectron } from "../services/utils.js"; import { isDev, isElectron, isWindows11 } from "../services/utils.js";
import protectedSessionService from "../services/protected_session.js"; import protectedSessionService from "../services/protected_session.js";
import packageJson from "../../package.json" with { type: "json" }; import packageJson from "../../package.json" with { type: "json" };
import assetPath from "../services/asset_path.js"; import assetPath from "../services/asset_path.js";
@ -42,7 +42,7 @@ function index(req: Request, res: Response) {
platform: process.platform, platform: process.platform,
isElectron, isElectron,
hasNativeTitleBar: isElectron && options.nativeTitleBarVisible === "true", hasNativeTitleBar: isElectron && options.nativeTitleBarVisible === "true",
hasBackgroundEffects: isElectron && options.backgroundEffects === "true", hasBackgroundEffects: isElectron && isWindows11 && options.backgroundEffects === "true",
mainFontSize: parseInt(options.mainFontSize), mainFontSize: parseInt(options.mainFontSize),
treeFontSize: parseInt(options.treeFontSize), treeFontSize: parseInt(options.treeFontSize),
detailFontSize: parseInt(options.detailFontSize), detailFontSize: parseInt(options.detailFontSize),

View File

@ -12,6 +12,9 @@ import path from "path";
import type NoteMeta from "./meta/note_meta.js"; import type NoteMeta from "./meta/note_meta.js";
import log from "./log.js"; import log from "./log.js";
import { t } from "i18next"; import { t } from "i18next";
import { release as osRelease } from "os";
const osVersion = osRelease().split('.').map(Number);
const randtoken = generator({ source: "crypto" }); const randtoken = generator({ source: "crypto" });
@ -19,6 +22,8 @@ export const isMac = process.platform === "darwin";
export const isWindows = process.platform === "win32"; export const isWindows = process.platform === "win32";
export const isWindows11 = isWindows && osVersion[0] === 10 && osVersion[2] >= 22000;
export const isElectron = !!process.versions["electron"]; export const isElectron = !!process.versions["electron"];
export const isDev = !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === "dev"); export const isDev = !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === "dev");

7
docs/.pages vendored Normal file
View File

@ -0,0 +1,7 @@
# Navigation order for top-level sections
nav:
- index.md
- User Guide
- Developer Guide
- Script API
- Release Notes

12
docs/README.md vendored Normal file
View 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
View 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.
![Trilium Notes Screenshot](app.png)
## 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
View 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
View 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
View 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
View 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 &copy; 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

View File

@ -24,11 +24,13 @@
"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",
"test:parallel": "pnpm --filter=!server --filter=!ckeditor5-mermaid --filter=!ckeditor5-math --parallel test", "test:parallel": "pnpm --filter=!server --filter=!ckeditor5-mermaid --filter=!ckeditor5-math --parallel test",
"test:sequential": "pnpm --filter=server --filter=ckeditor5-mermaid --filter=ckeditor5-math --sequential test", "test:sequential": "pnpm --filter=server --filter=ckeditor5-mermaid --filter=ckeditor5-math --sequential test",
"typecheck": "tsc --build",
"postinstall": "tsx scripts/electron-rebuild.mts" "postinstall": "tsx scripts/electron-rebuild.mts"
}, },
"private": true, "private": true,
@ -37,7 +39,7 @@
"@playwright/test": "^1.36.0", "@playwright/test": "^1.36.0",
"@triliumnext/server": "workspace:*", "@triliumnext/server": "workspace:*",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/node": "22.18.0", "@types/node": "22.18.1",
"@vitest/coverage-v8": "^3.0.5", "@vitest/coverage-v8": "^3.0.5",
"@vitest/ui": "^3.0.0", "@vitest/ui": "^3.0.0",
"chalk": "5.6.0", "chalk": "5.6.0",
@ -85,7 +87,7 @@
"ckeditor5": "patches/ckeditor5.patch" "ckeditor5": "patches/ckeditor5.patch"
}, },
"overrides": { "overrides": {
"mermaid": "11.10.1", "mermaid": "11.11.0",
"preact": "10.27.1", "preact": "10.27.1",
"roughjs": "4.6.6", "roughjs": "4.6.6",
"@types/express-serve-static-core": "5.0.7", "@types/express-serve-static-core": "5.0.7",

View File

@ -15,7 +15,7 @@
"ckeditor5-premium-features": "46.0.2" "ckeditor5-premium-features": "46.0.2"
}, },
"devDependencies": { "devDependencies": {
"@smithy/middleware-retry": "4.1.20", "@smithy/middleware-retry": "4.2.0",
"@types/jquery": "3.5.33" "@types/jquery": "3.5.33"
} }
} }

View File

@ -46,9 +46,10 @@
"@replit/codemirror-vim": "6.3.0", "@replit/codemirror-vim": "6.3.0",
"@ssddanbrown/codemirror-lang-smarty": "1.0.0", "@ssddanbrown/codemirror-lang-smarty": "1.0.0",
"@ssddanbrown/codemirror-lang-twig": "1.0.0", "@ssddanbrown/codemirror-lang-twig": "1.0.0",
"@triliumnext/commons": "workspace:*",
"codemirror-lang-elixir": "4.0.0", "codemirror-lang-elixir": "4.0.0",
"codemirror-lang-hcl": "0.1.0", "codemirror-lang-hcl": "0.1.0",
"codemirror-lang-mermaid": "0.5.0", "codemirror-lang-mermaid": "0.5.0",
"eslint-linter-browserify": "9.34.0" "eslint-linter-browserify": "9.35.0"
} }
} }

1169
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

21
requirements-docs.txt Normal file
View 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

View File

@ -0,0 +1,342 @@
#!/usr/bin/env node
/**
* Fix MkDocs structure by:
* 1. Syncing README.md to docs/index.md with necessary path adjustments
* 2. Moving overview pages to index.md inside their directories to prevent duplicate navigation entries
*/
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
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
// This happens when a file was converted to index.md and had links to siblings
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('/');
// Re-encode spaces for URL compatibility before recursing
const fixedLinkEncoded = fixedLink.replace(/ /g, '%20');
// Recursively process the fixed link
return fixLink(`[${text}](${fixedLinkEncoded})`, text, fixedLinkEncoded, currentDir, isIndex);
}
}
// For any .md link, check if there's a directory with index.md
// that should be used instead
if (!decodedLink.startsWith('/')) {
// Resolve relative to current directory
const resolvedPath = path.resolve(currentDir, decodedLink);
// Check if this points to a file that should be a directory
// Remove .md extension to get the potential directory name
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 we're in an index.md file and linking to a file that's now
// in a sibling directory, adjust the path
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)
if (!decodedLink.includes('/')) {
const basename = decodedLink.slice(0, -3); // Remove .md extension
const possibleDir = path.join(currentDir, basename);
if (fs.existsSync(possibleDir) && fs.statSync(possibleDir).isDirectory()) {
// Re-encode spaces for URL compatibility
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;
}
/**
* Sync README.md to docs/index.md with necessary path adjustments
*/
function syncReadmeToIndex(projectRoot: string, docsDir: string): FixResult[] {
const results: FixResult[] = [];
const readmePath = path.join(projectRoot, 'README.md');
const indexPath = path.join(docsDir, 'index.md');
if (!fs.existsSync(readmePath)) {
console.warn('README.md not found in project root');
return results;
}
// Read README content
let content = fs.readFileSync(readmePath, 'utf-8');
// Fix image path (./docs/app.png -> app.png)
content = content.replace(/src="\.\/docs\/app\.png"/g, 'src="app.png"');
// Fix language links in header
content = content.replace(/\[English\]\(\.\/README\.md\)/g, '[English](./index.md)');
content = content.replace(/\.\/docs\/README-ZH_CN\.md/g, './README-ZH_CN.md');
content = content.replace(/\.\/docs\/README-ZH_TW\.md/g, './README-ZH_TW.md');
content = content.replace(/\.\/docs\/README\.ru\.md/g, './README.ru.md');
content = content.replace(/\.\/docs\/README\.ja\.md/g, './README.ja.md');
content = content.replace(/\.\/docs\/README\.it\.md/g, './README.it.md');
content = content.replace(/\.\/docs\/README\.es\.md/g, './README.es.md');
// Fix internal documentation links (./docs/User%20Guide -> ./User%20Guide)
content = content.replace(/\.\/docs\/User%20Guide/g, './User%20Guide');
// Write the adjusted content to docs/index.md
fs.writeFileSync(indexPath, content, 'utf-8');
results.push({
message: `Synced README.md to docs/index.md with path adjustments`
});
return results;
}
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 normalizedProjectRoot = process.platform === 'win32' && projectRoot.startsWith('/')
? projectRoot.substring(1)
: projectRoot;
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));
// Sync README.md to docs/index.md
const syncResults = syncReadmeToIndex(normalizedProjectRoot, normalizedDocsDir);
if (syncResults.length > 0) {
console.log('README sync:');
for (const result of syncResults) {
console.log(` - ${result.message}`);
}
console.log();
}
// 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: ${syncResults.length} README syncs, ${fixes.length} files moved, ${updates.length} files updated`);
return 0;
}
// Run the main function
process.exit(main());

View File

@ -59,12 +59,6 @@
}, },
{ {
"path": "./packages/share-theme" "path": "./packages/share-theme"
},
{
"path": "./apps/website"
},
{
"path": "./apps/desktop-e2e"
} }
] ]
} }