mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 23:29:02 +02:00
Merge branch 'main' into sirius_tree_patch
This commit is contained in:
commit
617548f6b6
2
.github/actions/build-server/action.yml
vendored
2
.github/actions/build-server/action.yml
vendored
@ -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
183
.github/workflows/deploy-docs.yml
vendored
Normal 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
|
||||||
|
});
|
||||||
|
}
|
5
.github/workflows/dev.yml
vendored
5
.github/workflows/dev.yml
vendored
@ -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
|
||||||
|
|
||||||
|
4
.github/workflows/main-docker.yml
vendored
4
.github/workflows/main-docker.yml
vendored
@ -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'
|
||||||
|
6
.github/workflows/nightly.yml
vendored
6
.github/workflows/nightly.yml
vendored
@ -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'
|
||||||
@ -78,7 +78,7 @@ jobs:
|
|||||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
uses: softprops/action-gh-release@v2.3.2
|
uses: softprops/action-gh-release@v2.3.3
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
make_latest: false
|
make_latest: false
|
||||||
@ -119,7 +119,7 @@ jobs:
|
|||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
uses: softprops/action-gh-release@v2.3.2
|
uses: softprops/action-gh-release@v2.3.3
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
make_latest: false
|
make_latest: false
|
||||||
|
2
.github/workflows/playwright.yml
vendored
2
.github/workflows/playwright.yml
vendored
@ -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'
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -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'
|
||||||
@ -114,7 +114,7 @@ jobs:
|
|||||||
path: upload
|
path: upload
|
||||||
|
|
||||||
- name: Publish stable release
|
- name: Publish stable release
|
||||||
uses: softprops/action-gh-release@v2.3.2
|
uses: softprops/action-gh-release@v2.3.3
|
||||||
with:
|
with:
|
||||||
draft: false
|
draft: false
|
||||||
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md
|
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -46,3 +46,6 @@ upload
|
|||||||
|
|
||||||
/result
|
/result
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
|
|
||||||
|
# docs
|
||||||
|
site/
|
||||||
|
@ -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",
|
||||||
|
@ -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",
|
||||||
@ -24,7 +24,7 @@
|
|||||||
"@fullcalendar/multimonth": "6.1.19",
|
"@fullcalendar/multimonth": "6.1.19",
|
||||||
"@fullcalendar/timegrid": "6.1.19",
|
"@fullcalendar/timegrid": "6.1.19",
|
||||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
||||||
"@mermaid-js/layout-elk": "0.1.9",
|
"@mermaid-js/layout-elk": "0.2.0",
|
||||||
"@mind-elixir/node-menu": "5.0.0",
|
"@mind-elixir/node-menu": "5.0.0",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@triliumnext/ckeditor5": "workspace:*",
|
"@triliumnext/ckeditor5": "workspace:*",
|
||||||
@ -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,8 +52,8 @@
|
|||||||
"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.1.1",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"panzoom": "9.4.3",
|
"panzoom": "9.4.3",
|
||||||
"preact": "10.27.1",
|
"preact": "10.27.1",
|
||||||
@ -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",
|
||||||
|
@ -22,6 +22,7 @@ import FloatingButtons from "../widgets/FloatingButtons.jsx";
|
|||||||
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
|
||||||
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
|
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
|
||||||
import CloseZenModeButton from "../widgets/close_zen_button.js";
|
import CloseZenModeButton from "../widgets/close_zen_button.js";
|
||||||
|
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||||
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
|
||||||
|
|
||||||
const MOBILE_CSS = `
|
const MOBILE_CSS = `
|
||||||
@ -131,30 +132,33 @@ export default class MobileLayout {
|
|||||||
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
new ScreenContainer("detail", "column")
|
new ScreenContainer("detail", "row")
|
||||||
.id("detail-container")
|
.id("detail-container")
|
||||||
.class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-9")
|
.class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-9")
|
||||||
.child(
|
.child(
|
||||||
new FlexContainer("row")
|
new NoteWrapperWidget()
|
||||||
.contentSized()
|
.child(
|
||||||
.css("font-size", "larger")
|
new FlexContainer("row")
|
||||||
.css("align-items", "center")
|
.contentSized()
|
||||||
.child(<ToggleSidebarButton />)
|
.css("font-size", "larger")
|
||||||
.child(<NoteTitleWidget />)
|
.css("align-items", "center")
|
||||||
.child(<MobileDetailMenu />)
|
.child(<ToggleSidebarButton />)
|
||||||
|
.child(<NoteTitleWidget />)
|
||||||
|
.child(<MobileDetailMenu />)
|
||||||
|
)
|
||||||
|
.child(<SharedInfoWidget />)
|
||||||
|
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
|
||||||
|
.child(new PromotedAttributesWidget())
|
||||||
|
.child(
|
||||||
|
new ScrollingContainer()
|
||||||
|
.filling()
|
||||||
|
.contentSized()
|
||||||
|
.child(new NoteDetailWidget())
|
||||||
|
.child(new NoteListWidget(false))
|
||||||
|
.child(<FilePropertiesWrapper />)
|
||||||
|
)
|
||||||
|
.child(<MobileEditorToolbar />)
|
||||||
)
|
)
|
||||||
.child(<SharedInfoWidget />)
|
|
||||||
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
|
|
||||||
.child(new PromotedAttributesWidget())
|
|
||||||
.child(
|
|
||||||
new ScrollingContainer()
|
|
||||||
.filling()
|
|
||||||
.contentSized()
|
|
||||||
.child(new NoteDetailWidget())
|
|
||||||
.child(new NoteListWidget(false))
|
|
||||||
.child(<FilePropertiesWrapper />)
|
|
||||||
)
|
|
||||||
.child(<MobileEditorToolbar />)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -10,6 +10,10 @@ let leftInstance: ReturnType<typeof Split> | null;
|
|||||||
let rightPaneWidth: number;
|
let rightPaneWidth: number;
|
||||||
let rightInstance: ReturnType<typeof Split> | null;
|
let rightInstance: ReturnType<typeof Split> | null;
|
||||||
|
|
||||||
|
const noteSplitMap = new Map<string[], ReturnType<typeof Split> | undefined>(); // key: a group of ntxIds, value: the corresponding Split instance
|
||||||
|
const noteSplitRafMap = new Map<string[], number>();
|
||||||
|
let splitNoteContainer: HTMLElement | undefined;
|
||||||
|
|
||||||
function setupLeftPaneResizer(leftPaneVisible: boolean) {
|
function setupLeftPaneResizer(leftPaneVisible: boolean) {
|
||||||
if (leftInstance) {
|
if (leftInstance) {
|
||||||
leftInstance.destroy();
|
leftInstance.destroy();
|
||||||
@ -83,7 +87,86 @@ function setupRightPaneResizer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findKeyByNtxId(ntxId: string): string[] | undefined {
|
||||||
|
// Find the corresponding key in noteSplitMap based on ntxId
|
||||||
|
for (const key of noteSplitMap.keys()) {
|
||||||
|
if (key.includes(ntxId)) return key;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupNoteSplitResizer(ntxIds: string[]) {
|
||||||
|
let targetNtxIds: string[] | undefined;
|
||||||
|
for (const ntxId of ntxIds) {
|
||||||
|
targetNtxIds = findKeyByNtxId(ntxId);
|
||||||
|
if (targetNtxIds) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetNtxIds) {
|
||||||
|
noteSplitMap.get(targetNtxIds)?.destroy();
|
||||||
|
for (const id of ntxIds) {
|
||||||
|
if (!targetNtxIds.includes(id)) {
|
||||||
|
targetNtxIds.push(id)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetNtxIds = [...ntxIds];
|
||||||
|
}
|
||||||
|
noteSplitMap.set(targetNtxIds, undefined);
|
||||||
|
createSplitInstance(targetNtxIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function delNoteSplitResizer(ntxIds: string[]) {
|
||||||
|
let targetNtxIds = findKeyByNtxId(ntxIds[0]);
|
||||||
|
if (!targetNtxIds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
noteSplitMap.get(targetNtxIds)?.destroy();
|
||||||
|
noteSplitMap.delete(targetNtxIds);
|
||||||
|
targetNtxIds = targetNtxIds.filter(id => !ntxIds.includes(id));
|
||||||
|
|
||||||
|
if (targetNtxIds.length >= 2) {
|
||||||
|
noteSplitMap.set(targetNtxIds, undefined);
|
||||||
|
createSplitInstance(targetNtxIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveNoteSplitResizer(ntxId: string) {
|
||||||
|
const targetNtxIds = findKeyByNtxId(ntxId);
|
||||||
|
if (!targetNtxIds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
noteSplitMap.get(targetNtxIds)?.destroy();
|
||||||
|
noteSplitMap.set(targetNtxIds, undefined);
|
||||||
|
createSplitInstance(targetNtxIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSplitInstance(targetNtxIds: string[]) {
|
||||||
|
const prevRafId = noteSplitRafMap.get(targetNtxIds);
|
||||||
|
if (prevRafId) {
|
||||||
|
cancelAnimationFrame(prevRafId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rafId = requestAnimationFrame(() => {
|
||||||
|
splitNoteContainer = splitNoteContainer ?? $("#center-pane").find(".split-note-container-widget")[0];
|
||||||
|
const splitPanels = [...splitNoteContainer.querySelectorAll<HTMLElement>(':scope > .note-split')]
|
||||||
|
.filter(el => targetNtxIds.includes(el.getAttribute('data-ntx-id') ?? ""));
|
||||||
|
const splitInstance = Split(splitPanels, {
|
||||||
|
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||||
|
minSize: 150,
|
||||||
|
});
|
||||||
|
noteSplitMap.set(targetNtxIds, splitInstance);
|
||||||
|
noteSplitRafMap.delete(targetNtxIds);
|
||||||
|
});
|
||||||
|
noteSplitRafMap.set(targetNtxIds, rafId);
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setupLeftPaneResizer,
|
setupLeftPaneResizer,
|
||||||
setupRightPaneResizer
|
setupRightPaneResizer,
|
||||||
|
setupNoteSplitResizer,
|
||||||
|
delNoteSplitResizer,
|
||||||
|
moveNoteSplitResizer
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
import shortcuts, { keyMatches, matchesShortcut } from "./shortcuts.js";
|
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
|
||||||
|
|
||||||
// Mock utils module
|
// Mock utils module
|
||||||
vi.mock("./utils.js", () => ({
|
vi.mock("./utils.js", () => ({
|
||||||
@ -320,4 +320,36 @@ describe("shortcuts", () => {
|
|||||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isIMEComposing', () => {
|
||||||
|
it('should return true when event.isComposing is true', () => {
|
||||||
|
const event = { isComposing: true, keyCode: 65 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when keyCode is 229', () => {
|
||||||
|
const event = { isComposing: false, keyCode: 229 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when both isComposing is true and keyCode is 229', () => {
|
||||||
|
const event = { isComposing: true, keyCode: 229 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for normal keys', () => {
|
||||||
|
const event = { isComposing: false, keyCode: 65 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when isComposing is undefined and keyCode is not 229', () => {
|
||||||
|
const event = { keyCode: 13 } as KeyboardEvent;
|
||||||
|
expect(isIMEComposing(event)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle null/undefined events gracefully', () => {
|
||||||
|
expect(isIMEComposing(null as any)).toBe(false);
|
||||||
|
expect(isIMEComposing(undefined as any)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -40,6 +40,24 @@ for (let i = 1; i <= 19; i++) {
|
|||||||
keyMap[`f${i}`] = [`F${i}`];
|
keyMap[`f${i}`] = [`F${i}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if IME (Input Method Editor) is composing
|
||||||
|
* This is used to prevent keyboard shortcuts from firing during IME composition
|
||||||
|
* @param e - The keyboard event to check
|
||||||
|
* @returns true if IME is currently composing, false otherwise
|
||||||
|
*/
|
||||||
|
export function isIMEComposing(e: KeyboardEvent): boolean {
|
||||||
|
// Handle null/undefined events gracefully
|
||||||
|
if (!e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard check for composition state
|
||||||
|
// e.isComposing is true when IME is actively composing
|
||||||
|
// e.keyCode === 229 is a fallback for older browsers where 229 indicates IME processing
|
||||||
|
return e.isComposing || e.keyCode === 229;
|
||||||
|
}
|
||||||
|
|
||||||
function removeGlobalShortcut(namespace: string) {
|
function removeGlobalShortcut(namespace: string) {
|
||||||
bindGlobalShortcut("", null, namespace);
|
bindGlobalShortcut("", null, namespace);
|
||||||
}
|
}
|
||||||
@ -68,6 +86,13 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
|||||||
}
|
}
|
||||||
|
|
||||||
const e = evt as KeyboardEvent;
|
const e = evt as KeyboardEvent;
|
||||||
|
|
||||||
|
// Skip processing if IME is composing to prevent shortcuts from
|
||||||
|
// interfering with text input in CJK languages
|
||||||
|
if (isIMEComposing(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (matchesShortcut(e, keyboardShortcut)) {
|
if (matchesShortcut(e, keyboardShortcut)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -297,6 +297,54 @@ function isHtmlEmpty(html: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatHtml(html: string) {
|
||||||
|
let indent = "\n";
|
||||||
|
const tab = "\t";
|
||||||
|
let i = 0;
|
||||||
|
let pre: { indent: string; tag: string }[] = [];
|
||||||
|
|
||||||
|
html = html
|
||||||
|
.replace(new RegExp("<pre>([\\s\\S]+?)?</pre>"), function (x) {
|
||||||
|
pre.push({ indent: "", tag: x });
|
||||||
|
return "<--TEMPPRE" + i++ + "/-->";
|
||||||
|
})
|
||||||
|
.replace(new RegExp("<[^<>]+>[^<]?", "g"), function (x) {
|
||||||
|
let ret;
|
||||||
|
const tagRegEx = /<\/?([^\s/>]+)/.exec(x);
|
||||||
|
let tag = tagRegEx ? tagRegEx[1] : "";
|
||||||
|
let p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x);
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
const pInd = parseInt(p[1]);
|
||||||
|
pre[pInd].indent = indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"].indexOf(tag) >= 0) {
|
||||||
|
// self closing tag
|
||||||
|
ret = indent + x;
|
||||||
|
} else {
|
||||||
|
if (x.indexOf("</") < 0) {
|
||||||
|
//open tag
|
||||||
|
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length);
|
||||||
|
else ret = indent + x;
|
||||||
|
!p && (indent += tab);
|
||||||
|
} else {
|
||||||
|
//close tag
|
||||||
|
indent = indent.substr(0, indent.length - 1);
|
||||||
|
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length);
|
||||||
|
else ret = indent + x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (i = pre.length; i--;) {
|
||||||
|
html = html.replace("<--TEMPPRE" + i + "/-->", pre[i].tag.replace("<pre>", "<pre>\n").replace("</pre>", pre[i].indent + "</pre>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html;
|
||||||
|
}
|
||||||
|
|
||||||
export async function clearBrowserCache() {
|
export async function clearBrowserCache() {
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
const win = dynamicRequire("@electron/remote").getCurrentWindow();
|
const win = dynamicRequire("@electron/remote").getCurrentWindow();
|
||||||
@ -855,6 +903,7 @@ export default {
|
|||||||
getNoteTypeClass,
|
getNoteTypeClass,
|
||||||
getMimeTypeClass,
|
getMimeTypeClass,
|
||||||
isHtmlEmpty,
|
isHtmlEmpty,
|
||||||
|
formatHtml,
|
||||||
clearBrowserCache,
|
clearBrowserCache,
|
||||||
copySelectionToClipboard,
|
copySelectionToClipboard,
|
||||||
dynamicRequire,
|
dynamicRequire,
|
||||||
|
@ -1243,6 +1243,10 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
|||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden-ext.note-split + .gutter {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#context-menu-cover.show {
|
#context-menu-cover.show {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -1772,7 +1776,6 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.note-split {
|
.note-split {
|
||||||
flex-basis: 0; /* so that each split has same width */
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
@ -2376,3 +2379,12 @@ footer.webview-footer button {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.revision-diff-added {
|
||||||
|
background: rgba(100, 200, 100, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.revision-diff-removed {
|
||||||
|
background: rgba(255, 100, 100, 0.5);
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
@ -89,7 +81,7 @@ body.background-effects.zen #root-widget {
|
|||||||
* Gutter
|
* Gutter
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.gutter {
|
.gutter {
|
||||||
background: var(--gutter-color) !important;
|
background: var(--gutter-color) !important;
|
||||||
transition: background 150ms ease-out;
|
transition: background 150ms ease-out;
|
||||||
}
|
}
|
||||||
@ -1175,6 +1167,11 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
|
|||||||
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
|
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.split-note-container-widget > .gutter {
|
||||||
|
background: var(--root-background) !important;
|
||||||
|
transition: background 150ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ribbon & note header
|
* Ribbon & note header
|
||||||
*/
|
*/
|
||||||
@ -1183,10 +1180,6 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
|
|||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-split:not(.hidden-ext) + .note-split:not(.hidden-ext) {
|
|
||||||
border-left: 4px solid var(--root-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes note-entrance {
|
@keyframes note-entrance {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -263,6 +263,11 @@
|
|||||||
"confirm_delete_all": "Do you want to delete all revisions of this note?",
|
"confirm_delete_all": "Do you want to delete all revisions of this note?",
|
||||||
"no_revisions": "No revisions for this note yet...",
|
"no_revisions": "No revisions for this note yet...",
|
||||||
"restore_button": "Restore",
|
"restore_button": "Restore",
|
||||||
|
"diff_on": "Show diff",
|
||||||
|
"diff_off": "Show content",
|
||||||
|
"diff_on_hint": "Click to show note source diff",
|
||||||
|
"diff_off_hint": "Click to show note content",
|
||||||
|
"diff_not_available": "Diff isn't available.",
|
||||||
"confirm_restore": "Do you want to restore this revision? This will overwrite the current title and content of the note with this revision.",
|
"confirm_restore": "Do you want to restore this revision? This will overwrite the current title and content of the note with this revision.",
|
||||||
"delete_button": "Delete",
|
"delete_button": "Delete",
|
||||||
"confirm_delete": "Do you want to delete this revision?",
|
"confirm_delete": "Do you want to delete this revision?",
|
||||||
@ -1118,7 +1123,9 @@
|
|||||||
"title": "Performance",
|
"title": "Performance",
|
||||||
"enable-motion": "Enable transitions and animations",
|
"enable-motion": "Enable transitions and animations",
|
||||||
"enable-shadows": "Enable shadows",
|
"enable-shadows": "Enable shadows",
|
||||||
"enable-backdrop-effects": "Enable background effects for menus, popups and panels"
|
"enable-backdrop-effects": "Enable background effects for menus, popups and panels",
|
||||||
|
"enable-smooth-scroll": "Enable smooth scrolling",
|
||||||
|
"app-restart-required": "(a restart of the application is required for the change to take effect)"
|
||||||
},
|
},
|
||||||
"ai_llm": {
|
"ai_llm": {
|
||||||
"not_started": "Not started",
|
"not_started": "Not started",
|
||||||
|
@ -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",
|
||||||
|
@ -2013,7 +2013,9 @@
|
|||||||
"title": "Setări de performanță",
|
"title": "Setări de performanță",
|
||||||
"enable-motion": "Activează tranzițiile și animațiile",
|
"enable-motion": "Activează tranzițiile și animațiile",
|
||||||
"enable-shadows": "Activează umbrirea elementelor",
|
"enable-shadows": "Activează umbrirea elementelor",
|
||||||
"enable-backdrop-effects": "Activează efectele de fundal pentru meniuri, popup-uri și panouri"
|
"enable-backdrop-effects": "Activează efectele de fundal pentru meniuri, popup-uri și panouri",
|
||||||
|
"enable-smooth-scroll": "Activează derularea lină",
|
||||||
|
"app-restart-required": "(este necesară repornirea aplicației pentru ca modificarea să aibă efect)"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"related_settings": "Setări similare"
|
"related_settings": "Setări similare"
|
||||||
|
@ -3,7 +3,7 @@ import appContext, { type CommandData, type CommandListenerData, type EventData,
|
|||||||
import type BasicWidget from "../basic_widget.js";
|
import type BasicWidget from "../basic_widget.js";
|
||||||
import type NoteContext from "../../components/note_context.js";
|
import type NoteContext from "../../components/note_context.js";
|
||||||
import Component from "../../components/component.js";
|
import Component from "../../components/component.js";
|
||||||
|
import splitService from "../../services/resizer.js";
|
||||||
interface NoteContextEvent {
|
interface NoteContextEvent {
|
||||||
noteContext: NoteContext;
|
noteContext: NoteContext;
|
||||||
}
|
}
|
||||||
@ -52,6 +52,10 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
|||||||
await widget.handleEvent("setNoteContext", { noteContext });
|
await widget.handleEvent("setNoteContext", { noteContext });
|
||||||
|
|
||||||
this.child(widget);
|
this.child(widget);
|
||||||
|
|
||||||
|
if (noteContext.mainNtxId && noteContext.ntxId) {
|
||||||
|
splitService.setupNoteSplitResizer([noteContext.mainNtxId,noteContext.ntxId]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: EventData<"openNewNoteSplit">) {
|
async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: EventData<"openNewNoteSplit">) {
|
||||||
@ -95,9 +99,9 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) {
|
async closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) {
|
||||||
if (ntxId) {
|
if (ntxId) {
|
||||||
appContext.tabManager.removeNoteContext(ntxId);
|
await appContext.tabManager.removeNoteContext(ntxId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +141,8 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
|||||||
|
|
||||||
// activate context that now contains the original note
|
// activate context that now contains the original note
|
||||||
await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]);
|
await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]);
|
||||||
|
|
||||||
|
splitService.moveNoteSplitResizer(ntxIds[leftIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
activeContextChangedEvent() {
|
activeContextChangedEvent() {
|
||||||
@ -157,6 +163,8 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
|||||||
recursiveCleanup(widget);
|
recursiveCleanup(widget);
|
||||||
delete this.widgets[ntxId];
|
delete this.widgets[ntxId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splitService.delNoteSplitResizer(ntxIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
contextsReopenedEvent({ ntxId, afterNtxId }: EventData<"contextsReopened">) {
|
contextsReopenedEvent({ ntxId, afterNtxId }: EventData<"contextsReopened">) {
|
||||||
|
@ -7,6 +7,7 @@ import { t } from "../../services/i18n";
|
|||||||
import server from "../../services/server";
|
import server from "../../services/server";
|
||||||
import toast from "../../services/toast";
|
import toast from "../../services/toast";
|
||||||
import Button from "../react/Button";
|
import Button from "../react/Button";
|
||||||
|
import FormToggle from "../react/FormToggle";
|
||||||
import Modal from "../react/Modal";
|
import Modal from "../react/Modal";
|
||||||
import FormList, { FormListItem } from "../react/FormList";
|
import FormList, { FormListItem } from "../react/FormList";
|
||||||
import utils from "../../services/utils";
|
import utils from "../../services/utils";
|
||||||
@ -18,12 +19,15 @@ import open from "../../services/open";
|
|||||||
import ActionButton from "../react/ActionButton";
|
import ActionButton from "../react/ActionButton";
|
||||||
import options from "../../services/options";
|
import options from "../../services/options";
|
||||||
import { useTriliumEvent } from "../react/hooks";
|
import { useTriliumEvent } from "../react/hooks";
|
||||||
|
import { diffWords } from "diff";
|
||||||
|
|
||||||
export default function RevisionsDialog() {
|
export default function RevisionsDialog() {
|
||||||
const [ note, setNote ] = useState<FNote>();
|
const [ note, setNote ] = useState<FNote>();
|
||||||
|
const [ noteContent, setNoteContent ] = useState<string>();
|
||||||
const [ revisions, setRevisions ] = useState<RevisionItem[]>();
|
const [ revisions, setRevisions ] = useState<RevisionItem[]>();
|
||||||
const [ currentRevision, setCurrentRevision ] = useState<RevisionItem>();
|
const [ currentRevision, setCurrentRevision ] = useState<RevisionItem>();
|
||||||
const [ shown, setShown ] = useState(false);
|
const [ shown, setShown ] = useState(false);
|
||||||
|
const [ showDiff, setShowDiff ] = useState(false);
|
||||||
const [ refreshCounter, setRefreshCounter ] = useState(0);
|
const [ refreshCounter, setRefreshCounter ] = useState(0);
|
||||||
|
|
||||||
useTriliumEvent("showRevisions", async ({ noteId }) => {
|
useTriliumEvent("showRevisions", async ({ noteId }) => {
|
||||||
@ -37,8 +41,10 @@ export default function RevisionsDialog() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (note?.noteId) {
|
if (note?.noteId) {
|
||||||
server.get<RevisionItem[]>(`notes/${note.noteId}/revisions`).then(setRevisions);
|
server.get<RevisionItem[]>(`notes/${note.noteId}/revisions`).then(setRevisions);
|
||||||
|
note.getContent().then(setNoteContent);
|
||||||
} else {
|
} else {
|
||||||
setRevisions(undefined);
|
setRevisions(undefined);
|
||||||
|
setNoteContent(undefined);
|
||||||
}
|
}
|
||||||
}, [ note?.noteId, refreshCounter ]);
|
}, [ note?.noteId, refreshCounter ]);
|
||||||
|
|
||||||
@ -54,22 +60,42 @@ export default function RevisionsDialog() {
|
|||||||
helpPageId="vZWERwf8U3nx"
|
helpPageId="vZWERwf8U3nx"
|
||||||
bodyStyle={{ display: "flex", height: "80vh" }}
|
bodyStyle={{ display: "flex", height: "80vh" }}
|
||||||
header={
|
header={
|
||||||
(!!revisions?.length && <Button text={t("revisions.delete_all_revisions")} size="small" style={{ padding: "0 10px" }}
|
!!revisions?.length && (
|
||||||
onClick={async () => {
|
<>
|
||||||
const text = t("revisions.confirm_delete_all");
|
{["text", "code", "mermaid"].includes(currentRevision?.type ?? "") && (
|
||||||
|
<FormToggle
|
||||||
|
currentValue={showDiff}
|
||||||
|
onChange={(newValue) => setShowDiff(newValue)}
|
||||||
|
switchOnName={t("revisions.diff_on")}
|
||||||
|
switchOffName={t("revisions.diff_off")}
|
||||||
|
switchOnTooltip={t("revisions.diff_on_hint")}
|
||||||
|
switchOffTooltip={t("revisions.diff_off_hint")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
text={t("revisions.delete_all_revisions")}
|
||||||
|
size="small"
|
||||||
|
style={{ padding: "0 10px" }}
|
||||||
|
onClick={async () => {
|
||||||
|
const text = t("revisions.confirm_delete_all");
|
||||||
|
|
||||||
if (note && await dialog.confirm(text)) {
|
if (note && await dialog.confirm(text)) {
|
||||||
await server.remove(`notes/${note.noteId}/revisions`);
|
await server.remove(`notes/${note.noteId}/revisions`);
|
||||||
setRevisions([]);
|
setRevisions([]);
|
||||||
setCurrentRevision(undefined);
|
setCurrentRevision(undefined);
|
||||||
toast.showMessage(t("revisions.revisions_deleted"));
|
toast.showMessage(t("revisions.revisions_deleted"));
|
||||||
}
|
}
|
||||||
}}/>)
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
footer={<RevisionFooter note={note} />}
|
footer={<RevisionFooter note={note} />}
|
||||||
footerStyle={{ paddingTop: 0, paddingBottom: 0 }}
|
footerStyle={{ paddingTop: 0, paddingBottom: 0 }}
|
||||||
onHidden={() => {
|
onHidden={() => {
|
||||||
setShown(false);
|
setShown(false);
|
||||||
|
setShowDiff(false);
|
||||||
setNote(undefined);
|
setNote(undefined);
|
||||||
setCurrentRevision(undefined);
|
setCurrentRevision(undefined);
|
||||||
setRevisions(undefined);
|
setRevisions(undefined);
|
||||||
@ -92,10 +118,13 @@ export default function RevisionsDialog() {
|
|||||||
marginLeft: "20px",
|
marginLeft: "20px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
maxWidth: "calc(100% - 150px)",
|
||||||
minWidth: 0
|
minWidth: 0
|
||||||
}}>
|
}}>
|
||||||
<RevisionPreview
|
<RevisionPreview
|
||||||
|
noteContent={noteContent}
|
||||||
revisionItem={currentRevision}
|
revisionItem={currentRevision}
|
||||||
|
showDiff={showDiff}
|
||||||
setShown={setShown}
|
setShown={setShown}
|
||||||
onRevisionDeleted={() => {
|
onRevisionDeleted={() => {
|
||||||
setRefreshCounter(c => c + 1);
|
setRefreshCounter(c => c + 1);
|
||||||
@ -121,8 +150,10 @@ function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: Re
|
|||||||
</FormList>);
|
</FormList>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RevisionPreview({ revisionItem, setShown, onRevisionDeleted }: {
|
function RevisionPreview({noteContent, revisionItem, showDiff, setShown, onRevisionDeleted }: {
|
||||||
|
noteContent?: string,
|
||||||
revisionItem?: RevisionItem,
|
revisionItem?: RevisionItem,
|
||||||
|
showDiff: boolean,
|
||||||
setShown: Dispatch<StateUpdater<boolean>>,
|
setShown: Dispatch<StateUpdater<boolean>>,
|
||||||
onRevisionDeleted?: () => void
|
onRevisionDeleted?: () => void
|
||||||
}) {
|
}) {
|
||||||
@ -179,7 +210,7 @@ function RevisionPreview({ revisionItem, setShown, onRevisionDeleted }: {
|
|||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
<div className="revision-content use-tn-links" style={{ overflow: "auto", wordBreak: "break-word" }}>
|
<div className="revision-content use-tn-links" style={{ overflow: "auto", wordBreak: "break-word" }}>
|
||||||
<RevisionContent revisionItem={revisionItem} fullRevision={fullRevision} />
|
<RevisionContent noteContent={noteContent} revisionItem={revisionItem} fullRevision={fullRevision} showDiff={showDiff}/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -197,12 +228,15 @@ const CODE_STYLE: CSSProperties = {
|
|||||||
whiteSpace: "pre-wrap"
|
whiteSpace: "pre-wrap"
|
||||||
};
|
};
|
||||||
|
|
||||||
function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: RevisionItem, fullRevision?: RevisionPojo }) {
|
function RevisionContent({ noteContent, revisionItem, fullRevision, showDiff }: { noteContent?:string, revisionItem?: RevisionItem, fullRevision?: RevisionPojo, showDiff: boolean}) {
|
||||||
const content = fullRevision?.content;
|
const content = fullRevision?.content;
|
||||||
if (!revisionItem || !content) {
|
if (!revisionItem || !content) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showDiff) {
|
||||||
|
return <RevisionContentDiff noteContent={noteContent} itemContent={content} itemType={revisionItem.type}/>
|
||||||
|
}
|
||||||
switch (revisionItem.type) {
|
switch (revisionItem.type) {
|
||||||
case "text":
|
case "text":
|
||||||
return <RevisionContentText content={content} />
|
return <RevisionContentText content={content} />
|
||||||
@ -267,6 +301,48 @@ function RevisionContentText({ content }: { content: string | Buffer<ArrayBuffer
|
|||||||
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RevisionContentDiff({ noteContent, itemContent, itemType }: {
|
||||||
|
noteContent?: string,
|
||||||
|
itemContent: string | Buffer<ArrayBufferLike> | undefined,
|
||||||
|
itemType: string
|
||||||
|
}) {
|
||||||
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!noteContent || typeof itemContent !== "string") {
|
||||||
|
if (contentRef.current) {
|
||||||
|
contentRef.current.textContent = t("revisions.diff_not_available");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let processedNoteContent = noteContent;
|
||||||
|
let processedItemContent = itemContent;
|
||||||
|
|
||||||
|
if (itemType === "text") {
|
||||||
|
processedNoteContent = utils.formatHtml(noteContent);
|
||||||
|
processedItemContent = utils.formatHtml(itemContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const diff = diffWords(processedNoteContent, processedItemContent);
|
||||||
|
const diffHtml = diff.map(part => {
|
||||||
|
if (part.added) {
|
||||||
|
return `<span class="revision-diff-added">${utils.escapeHtml(part.value)}</span>`;
|
||||||
|
} else if (part.removed) {
|
||||||
|
return `<span class="revision-diff-removed">${utils.escapeHtml(part.value)}</span>`;
|
||||||
|
} else {
|
||||||
|
return utils.escapeHtml(part.value);
|
||||||
|
}
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
if (contentRef.current) {
|
||||||
|
contentRef.current.innerHTML = diffHtml;
|
||||||
|
}
|
||||||
|
}, [noteContent, itemContent, itemType]);
|
||||||
|
|
||||||
|
return <div ref={contentRef} className="ck-content" style={{ whiteSpace: "pre-wrap" }}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
function RevisionFooter({ note }: { note?: FNote }) {
|
function RevisionFooter({ note }: { note?: FNote }) {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return <></>;
|
return <></>;
|
||||||
|
@ -8,6 +8,7 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
|||||||
import attributeService from "../services/attributes.js";
|
import attributeService from "../services/attributes.js";
|
||||||
import FindInText from "./find_in_text.js";
|
import FindInText from "./find_in_text.js";
|
||||||
import FindInCode from "./find_in_code.js";
|
import FindInCode from "./find_in_code.js";
|
||||||
|
import { isIMEComposing } from "../services/shortcuts.js";
|
||||||
import FindInHtml from "./find_in_html.js";
|
import FindInHtml from "./find_in_html.js";
|
||||||
import type { EventData } from "../components/app_context.js";
|
import type { EventData } from "../components/app_context.js";
|
||||||
|
|
||||||
@ -162,6 +163,11 @@ export default class FindWidget extends NoteContextAwareWidget {
|
|||||||
this.$replaceButton.on("click", () => this.replace());
|
this.$replaceButton.on("click", () => this.replace());
|
||||||
|
|
||||||
this.$input.on("keydown", async (e) => {
|
this.$input.on("keydown", async (e) => {
|
||||||
|
// Skip processing during IME composition
|
||||||
|
if (isIMEComposing(e.originalEvent as KeyboardEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((e.metaKey || e.ctrlKey) && (e.key === "F" || e.key === "f")) {
|
if ((e.metaKey || e.ctrlKey) && (e.key === "F" || e.key === "f")) {
|
||||||
// If ctrl+f is pressed when the findbox is shown, select the
|
// If ctrl+f is pressed when the findbox is shown, select the
|
||||||
// whole input to find
|
// whole input to find
|
||||||
|
@ -8,6 +8,7 @@ import "./note_title.css";
|
|||||||
import { isLaunchBarConfig } from "../services/utils";
|
import { isLaunchBarConfig } from "../services/utils";
|
||||||
import appContext from "../components/app_context";
|
import appContext from "../components/app_context";
|
||||||
import branches from "../services/branches";
|
import branches from "../services/branches";
|
||||||
|
import { isIMEComposing } from "../services/shortcuts";
|
||||||
|
|
||||||
export default function NoteTitleWidget() {
|
export default function NoteTitleWidget() {
|
||||||
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
|
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
|
||||||
@ -78,6 +79,12 @@ export default function NoteTitleWidget() {
|
|||||||
spacedUpdate.scheduleUpdate();
|
spacedUpdate.scheduleUpdate();
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
// Skip processing if IME is composing to prevent interference
|
||||||
|
// with text input in CJK languages
|
||||||
|
if (isIMEComposing(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Focus on the note content when pressing enter.
|
// Focus on the note content when pressing enter.
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -23,15 +23,21 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
|||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
noteSwitchedAndActivatedEvent() {
|
noteSwitchedAndActivatedEvent({ noteContext }: EventData<"setNoteContext">) {
|
||||||
|
this.noteContext = noteContext;
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
noteSwitchedEvent() {
|
noteSwitchedEvent({ noteContext }: EventData<"setNoteContext">) {
|
||||||
|
this.noteContext = noteContext;
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
activeContextChangedEvent() {
|
activeContextChangedEvent({ noteContext }: EventData<"setNoteContext">) {
|
||||||
|
this.noteContext = noteContext;
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import linkService from "../services/link.js";
|
|||||||
import froca from "../services/froca.js";
|
import froca from "../services/froca.js";
|
||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import shortcutService from "../services/shortcuts.js";
|
import shortcutService, { isIMEComposing } from "../services/shortcuts.js";
|
||||||
import { t } from "../services/i18n.js";
|
import { t } from "../services/i18n.js";
|
||||||
import { Dropdown, Tooltip } from "bootstrap";
|
import { Dropdown, Tooltip } from "bootstrap";
|
||||||
|
|
||||||
@ -180,6 +180,14 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
|
|
||||||
if (utils.isMobile()) {
|
if (utils.isMobile()) {
|
||||||
this.$searchString.keydown((e) => {
|
this.$searchString.keydown((e) => {
|
||||||
|
// Skip processing if IME is composing to prevent interference
|
||||||
|
// with text input in CJK languages
|
||||||
|
// Note: jQuery wraps the native event, so we access originalEvent
|
||||||
|
const originalEvent = e.originalEvent as KeyboardEvent;
|
||||||
|
if (originalEvent && isIMEComposing(originalEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.which === 13) {
|
if (e.which === 13) {
|
||||||
if (this.$dropdownMenu.is(":visible")) {
|
if (this.$dropdownMenu.is(":visible")) {
|
||||||
this.search(); // just update already visible dropdown
|
this.search(); // just update already visible dropdown
|
||||||
|
@ -13,6 +13,7 @@ import attribute_parser, { Attribute } from "../../../services/attribute_parser"
|
|||||||
import ActionButton from "../../react/ActionButton";
|
import ActionButton from "../../react/ActionButton";
|
||||||
import { escapeQuotes, getErrorMessage } from "../../../services/utils";
|
import { escapeQuotes, getErrorMessage } from "../../../services/utils";
|
||||||
import link from "../../../services/link";
|
import link from "../../../services/link";
|
||||||
|
import { isIMEComposing } from "../../../services/shortcuts";
|
||||||
import froca from "../../../services/froca";
|
import froca from "../../../services/froca";
|
||||||
import contextMenu from "../../../menus/context_menu";
|
import contextMenu from "../../../menus/context_menu";
|
||||||
import type { CommandData, FilteredCommandNames } from "../../../components/app_context";
|
import type { CommandData, FilteredCommandNames } from "../../../components/app_context";
|
||||||
@ -287,6 +288,11 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
|
|||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
style="position: relative; padding-top: 10px; padding-bottom: 10px"
|
style="position: relative; padding-top: 10px; padding-bottom: 10px"
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
|
// Skip processing during IME composition
|
||||||
|
if (isIMEComposing(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
// allow autocomplete to fill the result textarea
|
// allow autocomplete to fill the result textarea
|
||||||
setTimeout(() => save(), 100);
|
setTimeout(() => save(), 100);
|
||||||
|
@ -266,9 +266,20 @@ function Performance() {
|
|||||||
label={t("ui-performance.enable-backdrop-effects")}
|
label={t("ui-performance.enable-backdrop-effects")}
|
||||||
currentValue={backdropEffectsEnabled} onChange={setBackdropEffectsEnabled}
|
currentValue={backdropEffectsEnabled} onChange={setBackdropEffectsEnabled}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{isElectron() && <SmoothScrollEnabledOption />}
|
||||||
|
|
||||||
</OptionsSection>
|
</OptionsSection>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SmoothScrollEnabledOption() {
|
||||||
|
const [ smoothScrollEnabled, setSmoothScrollEnabled ] = useTriliumOptionBool("smoothScrollEnabled");
|
||||||
|
|
||||||
|
return <FormCheckbox
|
||||||
|
label={`${t("ui-performance.enable-smooth-scroll")} ${t("ui-performance.app-restart-required")}`}
|
||||||
|
currentValue={smoothScrollEnabled} onChange={setSmoothScrollEnabled}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
function MaxContentWidth() {
|
function MaxContentWidth() {
|
||||||
const [ maxContentWidth, setMaxContentWidth ] = useTriliumOption("maxContentWidth");
|
const [ maxContentWidth, setMaxContentWidth ] = useTriliumOption("maxContentWidth");
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { EventData } from "../../components/app_context.js";
|
import type { EventData } from "../../components/app_context.js";
|
||||||
import type FNote from "../../entities/fnote.js";
|
import type FNote from "../../entities/fnote.js";
|
||||||
import AbstractCodeTypeWidget from "./abstract_code_type_widget.js";
|
import AbstractCodeTypeWidget from "./abstract_code_type_widget.js";
|
||||||
|
import utils from "../../services/utils.js";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="note-detail-readonly-code note-detail-printable">
|
<div class="note-detail-readonly-code note-detail-printable">
|
||||||
@ -33,7 +34,7 @@ export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
|
|||||||
if (!blob) return;
|
if (!blob) return;
|
||||||
|
|
||||||
const isFormattable = note.type === "text" && this.noteContext?.viewScope?.viewMode === "source";
|
const isFormattable = note.type === "text" && this.noteContext?.viewScope?.viewMode === "source";
|
||||||
const content = isFormattable ? this.format(blob.content) : blob.content;
|
const content = isFormattable ? utils.formatHtml(blob.content) : blob.content;
|
||||||
|
|
||||||
this._update(note, content);
|
this._update(note, content);
|
||||||
this.show();
|
this.show();
|
||||||
@ -54,52 +55,4 @@ export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
|
|||||||
|
|
||||||
resolve(this.$editor);
|
resolve(this.$editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
format(html: string) {
|
|
||||||
let indent = "\n";
|
|
||||||
const tab = "\t";
|
|
||||||
let i = 0;
|
|
||||||
let pre: { indent: string; tag: string }[] = [];
|
|
||||||
|
|
||||||
html = html
|
|
||||||
.replace(new RegExp("<pre>((.|\\t|\\n|\\r)+)?</pre>"), function (x) {
|
|
||||||
pre.push({ indent: "", tag: x });
|
|
||||||
return "<--TEMPPRE" + i++ + "/-->";
|
|
||||||
})
|
|
||||||
.replace(new RegExp("<[^<>]+>[^<]?", "g"), function (x) {
|
|
||||||
let ret;
|
|
||||||
const tagRegEx = /<\/?([^\s/>]+)/.exec(x);
|
|
||||||
let tag = tagRegEx ? tagRegEx[1] : "";
|
|
||||||
let p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x);
|
|
||||||
|
|
||||||
if (p) {
|
|
||||||
const pInd = parseInt(p[1]);
|
|
||||||
pre[pInd].indent = indent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"].indexOf(tag) >= 0) {
|
|
||||||
// self closing tag
|
|
||||||
ret = indent + x;
|
|
||||||
} else {
|
|
||||||
if (x.indexOf("</") < 0) {
|
|
||||||
//open tag
|
|
||||||
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length);
|
|
||||||
else ret = indent + x;
|
|
||||||
!p && (indent += tab);
|
|
||||||
} else {
|
|
||||||
//close tag
|
|
||||||
indent = indent.substr(0, indent.length - 1);
|
|
||||||
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length);
|
|
||||||
else ret = indent + x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (i = pre.length; i--;) {
|
|
||||||
html = html.replace("<--TEMPPRE" + i + "/-->", pre[i].tag.replace("<pre>", "<pre>\n").replace("</pre>", pre[i].indent + "</pre>"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,12 @@ async function main() {
|
|||||||
electron.app.commandLine.appendSwitch("enable-experimental-web-platform-features");
|
electron.app.commandLine.appendSwitch("enable-experimental-web-platform-features");
|
||||||
electron.app.commandLine.appendSwitch("lang", options.getOptionOrNull("formattingLocale") ?? "en");
|
electron.app.commandLine.appendSwitch("lang", options.getOptionOrNull("formattingLocale") ?? "en");
|
||||||
|
|
||||||
|
// Disable smooth scroll if the option is set
|
||||||
|
const smoothScrollEnabled = options.getOptionOrNull("smoothScrollEnabled");
|
||||||
|
if (smoothScrollEnabled === "false") {
|
||||||
|
electron.app.commandLine.appendSwitch("disable-smooth-scrolling");
|
||||||
|
}
|
||||||
|
|
||||||
// Electron 36 crashes with "Using GTK 2/3 and GTK 4 in the same process is not supported" on some distributions.
|
// Electron 36 crashes with "Using GTK 2/3 and GTK 4 in the same process is not supported" on some distributions.
|
||||||
// See https://github.com/electron/electron/issues/46538 for more info.
|
// See https://github.com/electron/electron/issues/46538 for more info.
|
||||||
if (process.platform === "linux") {
|
if (process.platform === "linux") {
|
||||||
|
@ -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",
|
||||||
|
@ -74,7 +74,10 @@
|
|||||||
"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",
|
||||||
|
"edit-readonly-note": "Edytuj notatkę tylko do odczytu",
|
||||||
|
"other": "Inne"
|
||||||
},
|
},
|
||||||
"keyboard_action_names": {
|
"keyboard_action_names": {
|
||||||
"zoom-in": "Powiększ",
|
"zoom-in": "Powiększ",
|
||||||
|
@ -65,6 +65,7 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
|
|||||||
"monthlyBackupEnabled",
|
"monthlyBackupEnabled",
|
||||||
"motionEnabled",
|
"motionEnabled",
|
||||||
"shadowsEnabled",
|
"shadowsEnabled",
|
||||||
|
"smoothScrollEnabled",
|
||||||
"backdropEffectsEnabled",
|
"backdropEffectsEnabled",
|
||||||
"maxContentWidth",
|
"maxContentWidth",
|
||||||
"compressImages",
|
"compressImages",
|
||||||
|
@ -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),
|
||||||
|
@ -155,7 +155,7 @@ const defaultOptions: DefaultOption[] = [
|
|||||||
{ name: "motionEnabled", value: "true", isSynced: false },
|
{ name: "motionEnabled", value: "true", isSynced: false },
|
||||||
{ name: "shadowsEnabled", value: "true", isSynced: false },
|
{ name: "shadowsEnabled", value: "true", isSynced: false },
|
||||||
{ name: "backdropEffectsEnabled", value: "true", isSynced: false },
|
{ name: "backdropEffectsEnabled", value: "true", isSynced: false },
|
||||||
|
{ name: "smoothScrollEnabled", value: "true", isSynced: false },
|
||||||
|
|
||||||
// Internationalization
|
// Internationalization
|
||||||
{ name: "locale", value: "en", isSynced: true },
|
{ name: "locale", value: "en", isSynced: true },
|
||||||
|
@ -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
7
docs/.pages
vendored
Normal 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
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,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",
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/browser": "^3.0.5",
|
"@vitest/browser": "^3.0.5",
|
||||||
"@vitest/coverage-istanbul": "^3.0.5",
|
"@vitest/coverage-istanbul": "^3.0.5",
|
||||||
"ckeditor5": "46.0.2",
|
"ckeditor5": "46.0.3",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-ckeditor5": ">=9.1.0",
|
"eslint-config-ckeditor5": ">=9.1.0",
|
||||||
"http-server": "^14.1.0",
|
"http-server": "^14.1.0",
|
||||||
@ -42,7 +42,7 @@
|
|||||||
"webdriverio": "^9.0.7"
|
"webdriverio": "^9.0.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ckeditor5": "46.0.2"
|
"ckeditor5": "46.0.3"
|
||||||
},
|
},
|
||||||
"author": "Elian Doran <contact@eliandoran.me>",
|
"author": "Elian Doran <contact@eliandoran.me>",
|
||||||
"license": "GPL-2.0-or-later",
|
"license": "GPL-2.0-or-later",
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/browser": "^3.0.5",
|
"@vitest/browser": "^3.0.5",
|
||||||
"@vitest/coverage-istanbul": "^3.0.5",
|
"@vitest/coverage-istanbul": "^3.0.5",
|
||||||
"ckeditor5": "46.0.2",
|
"ckeditor5": "46.0.3",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-ckeditor5": ">=9.1.0",
|
"eslint-config-ckeditor5": ">=9.1.0",
|
||||||
"http-server": "^14.1.0",
|
"http-server": "^14.1.0",
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"webdriverio": "^9.0.7"
|
"webdriverio": "^9.0.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ckeditor5": "46.0.2"
|
"ckeditor5": "46.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./scripts/build-dist.mjs",
|
"build": "node ./scripts/build-dist.mjs",
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/browser": "^3.0.5",
|
"@vitest/browser": "^3.0.5",
|
||||||
"@vitest/coverage-istanbul": "^3.0.5",
|
"@vitest/coverage-istanbul": "^3.0.5",
|
||||||
"ckeditor5": "46.0.2",
|
"ckeditor5": "46.0.3",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-ckeditor5": ">=9.1.0",
|
"eslint-config-ckeditor5": ">=9.1.0",
|
||||||
"http-server": "^14.1.0",
|
"http-server": "^14.1.0",
|
||||||
@ -45,7 +45,7 @@
|
|||||||
"webdriverio": "^9.0.7"
|
"webdriverio": "^9.0.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ckeditor5": "46.0.2"
|
"ckeditor5": "46.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./scripts/build-dist.mjs",
|
"build": "node ./scripts/build-dist.mjs",
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/browser": "^3.0.5",
|
"@vitest/browser": "^3.0.5",
|
||||||
"@vitest/coverage-istanbul": "^3.0.5",
|
"@vitest/coverage-istanbul": "^3.0.5",
|
||||||
"ckeditor5": "46.0.2",
|
"ckeditor5": "46.0.3",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-ckeditor5": ">=9.1.0",
|
"eslint-config-ckeditor5": ">=9.1.0",
|
||||||
"http-server": "^14.1.0",
|
"http-server": "^14.1.0",
|
||||||
@ -46,7 +46,7 @@
|
|||||||
"webdriverio": "^9.0.7"
|
"webdriverio": "^9.0.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ckeditor5": "46.0.2"
|
"ckeditor5": "46.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./scripts/build-dist.mjs",
|
"build": "node ./scripts/build-dist.mjs",
|
||||||
@ -71,6 +71,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ckeditor/ckeditor5-icons": "46.0.2"
|
"@ckeditor/ckeditor5-icons": "46.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/browser": "^3.0.5",
|
"@vitest/browser": "^3.0.5",
|
||||||
"@vitest/coverage-istanbul": "^3.0.5",
|
"@vitest/coverage-istanbul": "^3.0.5",
|
||||||
"ckeditor5": "46.0.2",
|
"ckeditor5": "46.0.3",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-ckeditor5": ">=9.1.0",
|
"eslint-config-ckeditor5": ">=9.1.0",
|
||||||
"http-server": "^14.1.0",
|
"http-server": "^14.1.0",
|
||||||
@ -45,7 +45,7 @@
|
|||||||
"webdriverio": "^9.0.7"
|
"webdriverio": "^9.0.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"ckeditor5": "46.0.2"
|
"ckeditor5": "46.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./scripts/build-dist.mjs",
|
"build": "node ./scripts/build-dist.mjs",
|
||||||
|
@ -11,11 +11,11 @@
|
|||||||
"@triliumnext/ckeditor5-keyboard-marker": "workspace:*",
|
"@triliumnext/ckeditor5-keyboard-marker": "workspace:*",
|
||||||
"@triliumnext/ckeditor5-math": "workspace:*",
|
"@triliumnext/ckeditor5-math": "workspace:*",
|
||||||
"@triliumnext/ckeditor5-mermaid": "workspace:*",
|
"@triliumnext/ckeditor5-mermaid": "workspace:*",
|
||||||
"ckeditor5": "46.0.2",
|
"ckeditor5": "46.0.3",
|
||||||
"ckeditor5-premium-features": "46.0.2"
|
"ckeditor5-premium-features": "46.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@smithy/middleware-retry": "4.1.20",
|
"@smithy/middleware-retry": "4.2.0",
|
||||||
"@types/jquery": "3.5.33"
|
"@types/jquery": "3.5.33"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
|
|||||||
motionEnabled: boolean;
|
motionEnabled: boolean;
|
||||||
shadowsEnabled: boolean;
|
shadowsEnabled: boolean;
|
||||||
backdropEffectsEnabled: boolean;
|
backdropEffectsEnabled: boolean;
|
||||||
|
smoothScrollEnabled: boolean;
|
||||||
codeNoteTheme: string;
|
codeNoteTheme: string;
|
||||||
|
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
|
3535
pnpm-lock.yaml
generated
3535
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
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
|
342
scripts/fix-mkdocs-structure.ts
Normal file
342
scripts/fix-mkdocs-structure.ts
Normal 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());
|
@ -59,12 +59,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./packages/share-theme"
|
"path": "./packages/share-theme"
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./apps/website"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./apps/desktop-e2e"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user