Compare commits

..

1 Commits

Author SHA1 Message Date
perf3ct
79dda887a6 feat(docs): add asyncapi websocket docs 2025-08-20 19:01:24 +00:00
457 changed files with 19114 additions and 21211 deletions

View File

@@ -1,6 +1,6 @@
root = true
[*.{js,ts,.tsx}]
[*.{js,ts}]
charset = utf-8
end_of_line = lf
indent_size = 4

1
.env Normal file
View File

@@ -0,0 +1 @@
NODE_OPTIONS=--max_old_space_size=4096

View File

@@ -86,7 +86,7 @@ runs:
APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }}
WINDOWS_SIGN_EXECUTABLE: ${{ env.WINDOWS_SIGN_EXECUTABLE }}
TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
run: pnpm nx --project=desktop electron-forge:make -- --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
# Add DMG signing step
- name: Sign DMG

View File

@@ -10,7 +10,7 @@ runs:
steps:
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
@@ -23,7 +23,7 @@ runs:
shell: bash
run: |
pnpm run chore:update-build-info
pnpm run --filter server package
pnpm nx --project=server package
- name: Prepare artifacts
shell: bash
run: |

40
.github/instructions/nx.instructions.md vendored Normal file
View File

@@ -0,0 +1,40 @@
---
applyTo: '**'
---
// This file is automatically generated by Nx Console
You are in an nx workspace using Nx 21.3.9 and pnpm as the package manager.
You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user:
# General Guidelines
- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture
- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration
- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors
- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool
# Generation Guidelines
If the user wants to generate something, use the following flow:
- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable
- get the available generators using the 'nx_generators' tool
- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them
- get generator details using the 'nx_generator_schema' tool
- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure
- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic
- open the generator UI using the 'nx_open_generate_ui' tool
- wait for the user to finish the generator
- read the generator log file using the 'nx_read_generator_log' tool
- use the information provided in the log file to answer the user's question or continue with what they were doing
# Running Tasks Guidelines
If the user wants help with tasks or commands (which include keywords like "test", "build", "lint", or other similar actions), use the following flow:
- Use the 'nx_current_running_tasks_details' tool to get the list of tasks (this can include tasks that were completed, stopped or failed).
- If there are any tasks, ask the user if they would like help with a specific task then use the 'nx_current_running_task_output' tool to get the terminal output for that task/command
- Use the terminal output from 'nx_current_running_task_output' to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary
- If the user would like to rerun the task or command, always use `nx run <taskId>` to rerun in the terminal. This will ensure that the task will run in the nx context and will be run the same way it originally executed
- If the task was marked as "continuous" do not offer to rerun the task. This task is already running and the user can see the output in the terminal. You can use 'nx_current_running_task_output' to get the output of the task to verify the output.

View File

@@ -1,190 +0,0 @@
# 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'
- 'validate-docs-links.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'
- 'validate-docs-links.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"
- name: Validate Documentation Links
run: |
# Run the TypeScript link validation script
pnpm tsx validate-docs-links.ts
# Install wrangler globally to avoid workspace issues
- name: Install Wrangler
run: |
npm install -g wrangler
# Deploy using Wrangler (use pre-installed wrangler)
- name: Deploy to Cloudflare Pages
id: deploy
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy site --project-name=trilium-docs --branch=${{ github.ref_name }}
wranglerVersion: '' # Use pre-installed version
# Deploy preview for PRs
- name: Deploy Preview to Cloudflare Pages
id: preview-deployment
if: github.event_name == 'pull_request'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy site --project-name=trilium-docs --branch=pr-${{ github.event.pull_request.number }}
wranglerVersion: '' # Use pre-installed version
# Post deployment URL as PR comment
- name: Comment PR with Preview URL
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.issue.number;
// Construct preview URL based on Cloudflare Pages pattern
const previewUrl = `https://pr-${prNumber}.trilium-docs.pages.dev`;
const mainUrl = 'https://docs.triliumnotes.org';
// Check if we already commented
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const botComment = comments.data.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Documentation preview is ready')
);
const commentBody = `📚 Documentation preview is ready!\n\n🔗 Preview URL: ${previewUrl}\n📖 Production URL: ${mainUrl}\n\n✅ All checks passed\n\n_This preview will be updated automatically with new commits._`;
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
} else {
// Create new comment
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: commentBody
});
}

View File

@@ -19,24 +19,45 @@ permissions:
pull-requests: write # for PR comments
jobs:
check-affected:
name: Check affected jobs (NX)
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # needed for https://github.com/marketplace/actions/nx-set-shas
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4
- name: Check affected
run: pnpm nx affected --verbose -t typecheck build rebuild-deps test-build
test_dev:
name: Test development
runs-on: ubuntu-latest
needs:
- check-affected
steps:
- name: Checkout the repository
uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm typecheck
- name: Run the unit tests
run: pnpm run test:all
@@ -45,6 +66,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- test_dev
- check-affected
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
@@ -53,7 +75,7 @@ jobs:
- name: Update build info
run: pnpm run chore:update-build-info
- name: Trigger client build
run: pnpm client:build
run: pnpm nx run client:build
- name: Send client bundle stats to RelativeCI
if: false
uses: relative-ci/agent-action@v3
@@ -61,7 +83,7 @@ jobs:
webpackStatsFile: ./apps/client/dist/webpack-stats.json
key: ${{ secrets.RELATIVE_CI_CLIENT_KEY }}
- name: Trigger server build
run: pnpm run server:build
run: pnpm nx run server:build
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
@@ -73,6 +95,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- build_docker
- check-affected
strategy:
matrix:
include:
@@ -89,7 +112,7 @@ jobs:
- name: Update build info
run: pnpm run chore:update-build-info
- name: Trigger build
run: pnpm server:build
run: pnpm nx run server:build
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV

View File

@@ -44,7 +44,7 @@ jobs:
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
@@ -82,7 +82,7 @@ jobs:
require-healthy: true
- name: Run Playwright tests
run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpm --filter=server-e2e e2e
run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpm exec nx run server-e2e:e2e
- name: Upload Playwright trace
if: failure()
@@ -144,7 +144,7 @@ jobs:
uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'

View File

@@ -51,12 +51,13 @@ jobs:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4
- name: Update nightly version
run: npm run chore:ci-update-nightly-version
- name: Run the build
@@ -78,7 +79,7 @@ jobs:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Publish release
uses: softprops/action-gh-release@v2.3.3
uses: softprops/action-gh-release@v2.3.2
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false
@@ -119,7 +120,7 @@ jobs:
arch: ${{ matrix.arch }}
- name: Publish release
uses: softprops/action-gh-release@v2.3.3
uses: softprops/action-gh-release@v2.3.2
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false

View File

@@ -19,8 +19,14 @@ jobs:
filter: tree:0
fetch-depth: 0
# This enables task distribution via Nx Cloud
# Run this command as early as possible, before dependencies are installed
# Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun
# Connect your workspace by running "nx connect" and uncomment this line to enable task distribution
# - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci"
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v5
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
@@ -28,12 +34,10 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps
- uses: nrwl/nx-set-shas@v4
- run: pnpm --filter server-e2e e2e
- name: Upload test report
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e report
path: apps/server-e2e/test-output
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
# - run: npx nx-cloud record -- echo Hello World
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
# When you enable task distribution, run the e2e-ci task instead of e2e
- run: pnpm exec nx affected -t e2e --exclude desktop-e2e

View File

@@ -35,12 +35,13 @@ jobs:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v5
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4
- name: Run the build
uses: ./.github/actions/build-electron
with:
@@ -114,7 +115,7 @@ jobs:
path: upload
- name: Publish stable release
uses: softprops/action-gh-release@v2.3.3
uses: softprops/action-gh-release@v2.3.2
with:
draft: false
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md

11
.gitignore vendored
View File

@@ -1,5 +1,4 @@
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
/.cache
# compiled output
dist
@@ -33,11 +32,14 @@ testem.log
.DS_Store
Thumbs.db
.nx/cache
.nx/workspace-data
vite.config.*.timestamp*
vitest.config.*.timestamp*
test-output
apps/*/data*
apps/*/data
apps/*/out
upload
@@ -45,7 +47,4 @@ upload
*.tsbuildinfo
/result
.svelte-kit
# docs
site/
.svelte-kit

2
.nvmrc
View File

@@ -1 +1 @@
22.19.0
22.18.0

View File

@@ -5,6 +5,7 @@
"lokalise.i18n-ally",
"ms-azuretools.vscode-docker",
"ms-playwright.playwright",
"nrwl.angular-console",
"redhat.vscode-yaml",
"tobermory.es6-string-html",
"vitest.explorer",

8
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"servers": {
"nx-mcp": {
"type": "http",
"url": "http://localhost:9461/mcp"
}
}
}

View File

@@ -35,5 +35,6 @@
"docs/**/*.png": true,
"apps/server/src/assets/doc_notes/**": true,
"apps/edit-docs/demo/**": true
}
},
"nxConsole.generateAiAgentRules": true
}

View File

@@ -20,10 +20,5 @@
"scope": "typescript",
"prefix": "jqf",
"body": ["private $${1:name}!: JQuery<HTMLElement>;"]
},
"region": {
"scope": "css",
"prefix": "region",
"body": ["/* #region ${1:name} */\n$0\n/* #endregion */"]
}
}

View File

@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Overview
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using pnpm, with multiple applications and shared packages.
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using NX, with multiple applications and shared packages.
## Development Commands
@@ -14,9 +14,12 @@ Trilium Notes is a hierarchical note-taking application with advanced features l
### Running Applications
- `pnpm run server:start` - Start development server (http://localhost:8080)
- `pnpm nx run server:serve` - Alternative server start command
- `pnpm nx run desktop:serve` - Run desktop Electron app
- `pnpm run server:start-prod` - Run server in production mode
### Building
- `pnpm nx build <project>` - Build specific project (server, client, desktop, etc.)
- `pnpm run client:build` - Build client application
- `pnpm run server:build` - Build server application
- `pnpm run electron:build` - Build desktop application
@@ -25,8 +28,13 @@ Trilium Notes is a hierarchical note-taking application with advanced features l
- `pnpm test:all` - Run all tests (parallel + sequential)
- `pnpm test:parallel` - Run tests that can run in parallel
- `pnpm test:sequential` - Run tests that must run sequentially (server, ckeditor5-mermaid, ckeditor5-math)
- `pnpm nx test <project>` - Run tests for specific project
- `pnpm coverage` - Generate coverage reports
### Linting & Type Checking
- `pnpm nx run <project>:lint` - Lint specific project
- `pnpm nx run <project>:typecheck` - Type check specific project
## Architecture Overview
### Monorepo Structure
@@ -86,6 +94,7 @@ Frontend uses a widget system (`apps/client/src/widgets/`):
- `apps/server/src/assets/db/schema.sql` - Core database structure
4. **Configuration**:
- `nx.json` - NX workspace configuration
- `package.json` - Project dependencies and scripts
## Note Types and Features
@@ -145,7 +154,7 @@ Trilium provides powerful user scripting capabilities:
- Update schema in `apps/server/src/assets/db/schema.sql`
## Build System Notes
- Uses pnpm for monorepo management
- Uses NX for monorepo management with build caching
- Vite for fast development builds
- ESBuild for production optimization
- pnpm workspaces for dependency management

View File

@@ -142,7 +142,7 @@ Download the repository, install dependencies using `pnpm` and then run the envi
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
pnpm install
pnpm edit-docs:edit-docs
pnpm nx run edit-docs:edit-docs
```
### Building the Executable
@@ -151,7 +151,7 @@ Download the repository, install dependencies using `pnpm` and then build the de
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
pnpm install
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
```
For more details, see the [development docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).

View File

@@ -35,13 +35,13 @@
"chore:generate-openapi": "tsx bin/generate-openapi.js"
},
"devDependencies": {
"@playwright/test": "1.55.0",
"@stylistic/eslint-plugin": "5.3.1",
"@playwright/test": "1.54.2",
"@stylistic/eslint-plugin": "5.2.3",
"@types/express": "5.0.3",
"@types/node": "22.18.1",
"@types/node": "22.17.2",
"@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.2.4",
"eslint": "9.35.0",
"eslint": "9.33.0",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.4",
@@ -49,7 +49,7 @@
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"tslib": "2.8.1",
"typedoc": "0.28.12",
"typedoc": "0.28.10",
"typedoc-plugin-missing-exports": "4.1.0"
},
"optionalDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.98.1",
"version": "0.98.0",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",
@@ -9,13 +9,8 @@
"email": "contact@eliandoran.me",
"url": "https://github.com/TriliumNext/Notes"
},
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
"test": "vitest",
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
"@eslint/js": "9.35.0",
"@eslint/js": "9.33.0",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
@@ -24,7 +19,7 @@
"@fullcalendar/multimonth": "6.1.19",
"@fullcalendar/timegrid": "6.1.19",
"@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.0",
"@mermaid-js/layout-elk": "0.1.9",
"@mind-elixir/node-menu": "5.0.0",
"@popperjs/core": "2.11.8",
"@triliumnext/ckeditor5": "workspace:*",
@@ -33,15 +28,15 @@
"@triliumnext/highlightjs": "workspace:*",
"@triliumnext/share-theme": "workspace:*",
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"bootstrap": "5.3.7",
"boxicons": "2.1.4",
"dayjs": "1.11.18",
"dayjs": "1.11.13",
"dayjs-plugin-utc": "0.1.2",
"debounce": "2.2.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"force-graph": "1.50.1",
"globals": "16.3.0",
"i18next": "25.5.2",
"i18next": "25.3.6",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
"jquery.fancytree": "2.38.5",
@@ -51,13 +46,13 @@
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "16.2.1",
"mermaid": "11.11.0",
"mind-elixir": "5.1.1",
"marked": "16.2.0",
"mermaid": "11.10.0",
"mind-elixir": "5.0.6",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.1",
"react-i18next": "15.7.3",
"react-i18next": "15.6.1",
"split.js": "1.6.5",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
@@ -67,14 +62,27 @@
"@ckeditor/ckeditor5-inspector": "5.0.0",
"@preact/preset-vite": "2.10.2",
"@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.33",
"@types/jquery": "3.5.32",
"@types/leaflet": "1.9.20",
"@types/leaflet-gpx": "1.3.8",
"@types/leaflet-gpx": "1.3.7",
"@types/mark.js": "8.11.12",
"@types/tabulator-tables": "6.2.10",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "18.0.1",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.2"
"vite-plugin-static-copy": "3.1.1"
},
"nx": {
"name": "client",
"targets": {
"serve": {
"dependsOn": [
"^build"
]
},
"circular-deps": {
"command": "pnpx dpdm -T {projectRoot}/src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
}
}
}
}

View File

@@ -1,6 +1,6 @@
import froca from "../services/froca.js";
import RootCommandExecutor from "./root_command_executor.js";
import Entrypoints from "./entrypoints.js";
import Entrypoints, { type SqlExecuteResults } from "./entrypoints.js";
import options from "../services/options.js";
import utils, { hasTouchBar } from "../services/utils.js";
import zoomComponent from "./zoom.js";
@@ -31,14 +31,16 @@ import { StartupChecks } from "./startup_checks.js";
import type { CreateNoteOpts } from "../services/note_create.js";
import { ColumnComponent } from "tabulator-tables";
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
import type RootContainer from "../widgets/containers/root_container.js";
import { SqlExecuteResults } from "@triliumnext/commons";
interface Layout {
getRootWidget: (appContext: AppContext) => RootContainer;
getRootWidget: (appContext: AppContext) => RootWidget;
}
export interface BeforeUploadListener extends Component {
interface RootWidget extends Component {
render: () => JQuery<HTMLElement>;
}
interface BeforeUploadListener extends Component {
beforeUnloadEvent(): boolean;
}
@@ -83,6 +85,7 @@ export type CommandMappings = {
focusTree: CommandData;
focusOnTitle: CommandData;
focusOnDetail: CommandData;
focusOnSearchDefinition: Required<CommandData>;
searchNotes: CommandData & {
searchString?: string;
ancestorNoteId?: string | null;
@@ -90,11 +93,6 @@ export type CommandMappings = {
closeTocCommand: CommandData;
closeHlt: CommandData;
showLaunchBarSubtree: CommandData;
showHiddenSubtree: CommandData;
showSQLConsoleHistory: CommandData;
logout: CommandData;
switchToMobileVersion: CommandData;
switchToDesktopVersion: CommandData;
showRevisions: CommandData & {
noteId?: string | null;
};
@@ -140,7 +138,6 @@ export type CommandMappings = {
showLeftPane: CommandData;
showAttachments: CommandData;
showSearchHistory: CommandData;
showShareSubtree: CommandData;
hoistNote: CommandData & { noteId: string };
leaveProtectedSession: CommandData;
enterProtectedSession: CommandData;
@@ -326,7 +323,6 @@ export type CommandMappings = {
printActiveNote: CommandData;
exportAsPdf: CommandData;
openNoteExternally: CommandData;
openNoteCustom: CommandData;
renderActiveNote: CommandData;
unhoist: CommandData;
reloadFrontendApp: CommandData;
@@ -530,7 +526,7 @@ export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMapp
export class AppContext extends Component {
isMainWindow: boolean;
components: Component[];
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
beforeUnloadListeners: WeakRef<BeforeUploadListener>[];
tabManager!: TabManager;
layout?: Layout;
noteTreeWidget?: NoteTreeWidget;
@@ -623,7 +619,7 @@ export class AppContext extends Component {
component.triggerCommand(commandName, { $el: $(this) });
});
this.child(rootWidget as Component);
this.child(rootWidget);
this.triggerEvent("initialRenderComplete", {});
}
@@ -653,17 +649,13 @@ export class AppContext extends Component {
return $(el).closest(".component").prop("component");
}
addBeforeUnloadListener(obj: BeforeUploadListener | (() => boolean)) {
addBeforeUnloadListener(obj: BeforeUploadListener) {
if (typeof WeakRef !== "function") {
// older browsers don't support WeakRef
return;
}
if (typeof obj === "object") {
this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
} else {
this.beforeUnloadListeners.push(obj);
}
this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
}
}
@@ -673,29 +665,25 @@ const appContext = new AppContext(window.glob.isMainWindow);
$(window).on("beforeunload", () => {
let allSaved = true;
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter((wr) => typeof wr === "function" || !!wr.deref());
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter((wr) => !!wr.deref());
for (const listener of appContext.beforeUnloadListeners) {
if (typeof listener === "object") {
const component = listener.deref();
for (const weakRef of appContext.beforeUnloadListeners) {
const component = weakRef.deref();
if (!component) {
continue;
}
if (!component) {
continue;
}
if (!component.beforeUnloadEvent()) {
console.log(`Component ${component.componentId} is not finished saving its state.`);
allSaved = false;
}
} else {
if (!listener()) {
allSaved = false;
}
if (!component.beforeUnloadEvent()) {
console.log(`Component ${component.componentId} is not finished saving its state.`);
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
allSaved = false;
}
}
if (!allSaved) {
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
return "some string";
}
});

View File

@@ -1,8 +1,6 @@
import utils from "../services/utils.js";
import type { CommandMappings, CommandNames, EventData, EventNames } from "./app_context.js";
type EventHandler = ((data: any) => void);
/**
* Abstract class for all components in the Trilium's frontend.
*
@@ -21,7 +19,6 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
initialized: Promise<void> | null;
parent?: TypedComponent<any>;
_position!: number;
private listeners: Record<string, EventHandler[]> | null = {};
constructor() {
this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`;
@@ -79,14 +76,6 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
const promises: Promise<unknown>[] = [];
// Handle React children.
if (this.listeners?.[name]) {
for (const listener of this.listeners[name]) {
listener(data);
}
}
// Handle legacy children.
for (const child of this.children) {
const ret = child.handleEvent(name, data) as Promise<void>;
@@ -131,35 +120,6 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
return promise;
}
registerHandler<T extends EventNames>(name: T, handler: EventHandler) {
if (!this.listeners) {
this.listeners = {};
}
if (!this.listeners[name]) {
this.listeners[name] = [];
}
if (this.listeners[name].includes(handler)) {
return;
}
this.listeners[name].push(handler);
}
removeHandler<T extends EventNames>(name: T, handler: EventHandler) {
if (!this.listeners?.[name]?.includes(handler)) {
return;
}
this.listeners[name] = this.listeners[name]
.filter(listener => listener !== handler);
if (!this.listeners[name].length) {
delete this.listeners[name];
}
}
}
export default class Component extends TypedComponent<Component> {}

View File

@@ -10,7 +10,22 @@ import bundleService from "../services/bundle.js";
import froca from "../services/froca.js";
import linkService from "../services/link.js";
import { t } from "../services/i18n.js";
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
import type FNote from "../entities/fnote.js";
// TODO: Move somewhere else nicer.
export type SqlExecuteResults = string[][][];
// TODO: Deduplicate with server.
interface SqlExecuteResponse {
success: boolean;
error?: string;
results: SqlExecuteResults;
}
// TODO: Deduplicate with server.
interface CreateChildrenResponse {
note: FNote;
}
export default class Entrypoints extends Component {
constructor() {
@@ -19,7 +34,7 @@ export default class Entrypoints extends Component {
openDevToolsCommand() {
if (utils.isElectron()) {
utils.dynamicRequire("@electron/remote").getCurrentWindow().webContents.toggleDevTools();
utils.dynamicRequire("@electron/remote").getCurrentWindow().toggleDevTools();
}
}
@@ -109,7 +124,7 @@ export default class Entrypoints extends Component {
if (utils.isElectron()) {
// standard JS version does not work completely correctly in electron
const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents();
const activeIndex = webContents.navigationHistory.getActiveIndex();
const activeIndex = parseInt(webContents.navigationHistory.getActiveIndex());
webContents.goToIndex(activeIndex - 1);
} else {
@@ -121,7 +136,7 @@ export default class Entrypoints extends Component {
if (utils.isElectron()) {
// standard JS version does not work completely correctly in electron
const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents();
const activeIndex = webContents.navigationHistory.getActiveIndex();
const activeIndex = parseInt(webContents.navigationHistory.getActiveIndex());
webContents.goToIndex(activeIndex + 1);
} else {

View File

@@ -43,6 +43,8 @@ export default class RootCommandExecutor extends Component {
const noteContext = await appContext.tabManager.openTabWithNoteWithHoisting(searchNote.noteId, {
activate: true
});
appContext.triggerCommand("focusOnSearchDefinition", { ntxId: noteContext.ntxId });
}
async searchInSubtreeCommand({ notePath }: CommandListenerData<"searchInSubtree">) {

View File

@@ -8,9 +8,10 @@ import electronContextMenu from "./menus/electron_context_menu.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
import options from "./services/options.js";
import server from "./services/server.js";
import type ElectronRemote from "@electron/remote";
import type Electron from "electron";
import "bootstrap/dist/css/bootstrap.min.css";
import "./stylesheets/bootstrap.scss";
import "boxicons/css/boxicons.min.css";
import "autocomplete.js/index_jquery.js";

View File

@@ -64,7 +64,7 @@ export interface NoteMetaData {
/**
* Note is the main node and concept in Trilium.
*/
export default class FNote {
class FNote {
private froca: Froca;
noteId!: string;
@@ -1020,14 +1020,6 @@ export default class FNote {
return this.noteId.startsWith("_options");
}
isTriliumSqlite() {
return this.mime === "text/x-sqlite;schema=trilium";
}
isTriliumScript() {
return this.mime.startsWith("application/javascript");
}
/**
* Provides note's date metadata.
*/
@@ -1035,3 +1027,5 @@ export default class FNote {
return await server.get<NoteMetaData>(`notes/${this.noteId}/metadata`);
}
}
export default FNote;

View File

@@ -1,47 +1,78 @@
import FlexContainer from "../widgets/containers/flex_container.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import TabRowWidget from "../widgets/tab_row.js";
import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js";
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteTitleWidget from "../widgets/note_title.js";
import OwnedAttributeListWidget from "../widgets/ribbon_widgets/owned_attribute_list.js";
import NoteActionsWidget from "../widgets/buttons/note_actions.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import RibbonContainer from "../widgets/containers/ribbon_container.js";
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import InheritedAttributesWidget from "../widgets/ribbon_widgets/inherited_attribute_list.js";
import NoteListWidget from "../widgets/note_list.js";
import NoteIconWidget from "../widgets/note_icon.jsx";
import SearchDefinitionWidget from "../widgets/ribbon_widgets/search_definition.js";
import SqlResultWidget from "../widgets/sql_result.js";
import SqlTableSchemasWidget from "../widgets/sql_table_schemas.js";
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
import ImagePropertiesWidget from "../widgets/ribbon_widgets/image_properties.js";
import NotePropertiesWidget from "../widgets/ribbon_widgets/note_properties.js";
import NoteIconWidget from "../widgets/note_icon.js";
import SearchResultWidget from "../widgets/search_result.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
import SpacerWidget from "../widgets/spacer.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import LeftPaneToggleWidget from "../widgets/buttons/left_pane_toggle.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import BasicPropertiesWidget from "../widgets/ribbon_widgets/basic_properties.js";
import NoteInfoWidget from "../widgets/ribbon_widgets/note_info_widget.js";
import BookPropertiesWidget from "../widgets/ribbon_widgets/book_properties.js";
import NoteMapRibbonWidget from "../widgets/ribbon_widgets/note_map.js";
import NotePathsWidget from "../widgets/ribbon_widgets/note_paths.js";
import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import EditButton from "../widgets/floating_buttons/edit_button.js";
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
import SharedInfoWidget from "../widgets/shared_info.js";
import FindWidget from "../widgets/find.js";
import TocWidget from "../widgets/toc.js";
import HighlightsListWidget from "../widgets/highlights_list.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import RevisionsButton from "../widgets/buttons/revisions_button.js";
import CodeButtonsWidget from "../widgets/floating_buttons/code_buttons.js";
import ApiLogWidget from "../widgets/api_log.js";
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
import ScriptExecutorWidget from "../widgets/ribbon_widgets/script_executor.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import ScrollPadding from "../widgets/scroll_padding.js";
import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js";
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
import options from "../services/options.js";
import utils from "../services/utils.js";
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
import ContextualHelpButton from "../widgets/floating_buttons/help_button.js";
import CloseZenButton from "../widgets/close_zen_button.js";
import type { AppContext } from "../components/app_context.js";
import type { WidgetsByParent } from "../services/bundle.js";
import SwitchSplitOrientationButton from "../widgets/floating_buttons/switch_layout_button.js";
import ToggleReadOnlyButton from "../widgets/floating_buttons/toggle_read_only_button.js";
import PngExportButton from "../widgets/floating_buttons/png_export_button.js";
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
import { applyModals } from "./layout_commons.js";
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import SearchResult from "../widgets/search_result.jsx";
import GlobalMenu from "../widgets/buttons/global_menu.jsx";
import SqlResults from "../widgets/sql_result.js";
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
import ApiLog from "../widgets/api_log.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
export default class DesktopLayout {
@@ -76,9 +107,9 @@ export default class DesktopLayout {
new FlexContainer("row")
.class("tab-row-container")
.child(new FlexContainer("row").id("tab-row-left-spacer"))
.optChild(launcherPaneIsHorizontal, <LeftPaneToggle isHorizontalLayout={true} />)
.optChild(launcherPaneIsHorizontal, new LeftPaneToggleWidget(true))
.child(new TabRowWidget().class("full-width"))
.optChild(customTitleBarButtons, <TitleBarButtons />)
.optChild(customTitleBarButtons, new TitleBarButtonsWidget())
.css("height", "40px")
.css("background-color", "var(--launcher-pane-background-color)")
.setParent(appContext)
@@ -99,7 +130,7 @@ export default class DesktopLayout {
new FlexContainer("column")
.id("rest-pane")
.css("flex-grow", "1")
.optChild(!fullWidthTabBar, new FlexContainer("row").child(new TabRowWidget()).optChild(customTitleBarButtons, <TitleBarButtons />).css("height", "40px"))
.optChild(!fullWidthTabBar, new FlexContainer("row").child(new TabRowWidget()).optChild(customTitleBarButtons, new TitleBarButtonsWidget()).css("height", "40px"))
.child(
new FlexContainer("row")
.filling()
@@ -120,30 +151,69 @@ export default class DesktopLayout {
.css("min-height", "50px")
.css("align-items", "center")
.cssBlock(".title-row > * { margin: 5px; }")
.child(<NoteIconWidget />)
.child(<NoteTitleWidget />)
.child(new NoteIconWidget())
.child(new NoteTitleWidget())
.child(new SpacerWidget(0, 1))
.child(new MovePaneButton(true))
.child(new MovePaneButton(false))
.child(new ClosePaneButton())
.child(new CreatePaneButton())
)
.child(<Ribbon />)
.child(<SharedInfo />)
.child(
new RibbonContainer()
// the order of the widgets matter. Some of these want to "activate" themselves
// when visible. When this happens to multiple of them, the first one "wins".
// promoted attributes should always win.
.ribbon(new ClassicEditorToolbar())
.ribbon(new ScriptExecutorWidget())
.ribbon(new SearchDefinitionWidget())
.ribbon(new EditedNotesWidget())
.ribbon(new BookPropertiesWidget())
.ribbon(new NotePropertiesWidget())
.ribbon(new FilePropertiesWidget())
.ribbon(new ImagePropertiesWidget())
.ribbon(new BasicPropertiesWidget())
.ribbon(new OwnedAttributeListWidget())
.ribbon(new InheritedAttributesWidget())
.ribbon(new NotePathsWidget())
.ribbon(new NoteMapRibbonWidget())
.ribbon(new SimilarNotesWidget())
.ribbon(new NoteInfoWidget())
.button(new RevisionsButton())
.button(new NoteActionsWidget())
)
.child(new SharedInfoWidget())
.child(new WatchedFileUpdateStatusWidget())
.child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
.child(
new FloatingButtons()
.child(new RefreshButton())
.child(new SwitchSplitOrientationButton())
.child(new ToggleReadOnlyButton())
.child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new GeoMapButtons())
.child(new CopyImageReferenceButton())
.child(new SvgExportButton())
.child(new PngExportButton())
.child(new BacklinksWidget())
.child(new ContextualHelpButton())
.child(new HideFloatingButtonsButton())
)
.child(
new ScrollingContainer()
.filling()
.child(new PromotedAttributesWidget())
.child(<SqlTableSchemas />)
.child(new SqlTableSchemasWidget())
.child(new NoteDetailWidget())
.child(new NoteListWidget(false))
.child(<SearchResult />)
.child(<SqlResults />)
.child(<ScrollPadding />)
.child(new SearchResultWidget())
.child(new SqlResultWidget())
.child(new ScrollPaddingWidget())
)
.child(<ApiLog />)
.child(new ApiLogWidget())
.child(new FindWidget())
.child(
...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC
@@ -162,11 +232,11 @@ export default class DesktopLayout {
)
)
)
.child(<CloseZenModeButton />)
.child(new CloseZenButton())
// Desktop-specific dialogs.
.child(<PasswordNoteSetDialog />)
.child(<UploadAttachmentsDialog />);
.child(new PasswordNoteSetDialog())
.child(new UploadAttachmentsDialog());
applyModals(rootContainer);
return rootContainer;
@@ -176,18 +246,14 @@ export default class DesktopLayout {
let launcherPane;
if (isHorizontal) {
launcherPane = new FlexContainer("row")
.css("height", "53px")
.class("horizontal")
.child(new LauncherContainer(true))
.child(<GlobalMenu isHorizontalLayout={true} />);
launcherPane = new FlexContainer("row").css("height", "53px").class("horizontal").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true));
} else {
launcherPane = new FlexContainer("column")
.css("width", "53px")
.class("vertical")
.child(<GlobalMenu isHorizontalLayout={false} />)
.child(new GlobalMenuWidget(false))
.child(new LauncherContainer(false))
.child(<LeftPaneToggle isHorizontalLayout={false} />);
.child(new LeftPaneToggleWidget(false));
}
launcherPane.id("launcher-pane");

View File

@@ -24,48 +24,48 @@ import InfoDialog from "../widgets/dialogs/info.js";
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
import PopupEditorDialog from "../widgets/dialogs/popup_editor.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import NoteIconWidget from "../widgets/note_icon";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import NoteIconWidget from "../widgets/note_icon.js";
import NoteTitleWidget from "../widgets/note_title.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import NoteListWidget from "../widgets/note_list.js";
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
import NoteTitleWidget from "../widgets/note_title.jsx";
import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js";
import { CallToActionDialog } from "../widgets/dialogs/call_to_action.jsx";
export function applyModals(rootContainer: RootContainer) {
rootContainer
.child(<BulkActionsDialog />)
.child(<AboutDialog />)
.child(<HelpDialog />)
.child(<RecentChangesDialog />)
.child(<BranchPrefixDialog />)
.child(<SortChildNotesDialog />)
.child(<IncludeNoteDialog />)
.child(<NoteTypeChooserDialog />)
.child(<JumpToNoteDialog />)
.child(<AddLinkDialog />)
.child(<CloneToDialog />)
.child(<MoveToDialog />)
.child(<ImportDialog />)
.child(<ExportDialog />)
.child(<MarkdownImportDialog />)
.child(<ProtectedSessionPasswordDialog />)
.child(<RevisionsDialog />)
.child(<DeleteNotesDialog />)
.child(<InfoDialog />)
.child(<ConfirmDialog />)
.child(<PromptDialog />)
.child(<IncorrectCpuArchDialog />)
.child(new BulkActionsDialog())
.child(new AboutDialog())
.child(new HelpDialog())
.child(new RecentChangesDialog())
.child(new BranchPrefixDialog())
.child(new SortChildNotesDialog())
.child(new IncludeNoteDialog())
.child(new NoteTypeChooserDialog())
.child(new JumpToNoteDialog())
.child(new AddLinkDialog())
.child(new CloneToDialog())
.child(new MoveToDialog())
.child(new ImportDialog())
.child(new ExportDialog())
.child(new MarkdownImportDialog())
.child(new ProtectedSessionPasswordDialog())
.child(new RevisionsDialog())
.child(new DeleteNotesDialog())
.child(new InfoDialog())
.child(new ConfirmDialog())
.child(new PromptDialog())
.child(new IncorrectCpuArchDialog())
.child(new PopupEditorDialog()
.child(new FlexContainer("row")
.class("title-row")
.css("align-items", "center")
.cssBlock(".title-row > * { margin: 5px; }")
.child(<NoteIconWidget />)
.child(<NoteTitleWidget />))
.child(<PopupEditorFormattingToolbar />)
.child(new NoteIconWidget())
.child(new NoteTitleWidget()))
.child(new ClassicEditorToolbar())
.child(new PromotedAttributesWidget())
.child(new NoteDetailWidget())
.child(new NoteListWidget(true)))
.child(<CallToActionDialog />);
.child(new CallToActionDialog());
}

View File

@@ -3,27 +3,30 @@ import NoteTitleWidget from "../widgets/note_title.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import ToggleSidebarButtonWidget from "../widgets/mobile_widgets/toggle_sidebar_button.js";
import MobileDetailMenuWidget from "../widgets/mobile_widgets/mobile_detail_menu.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
import EditButton from "../widgets/floating_buttons/edit_button.js";
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
import NoteListWidget from "../widgets/note_list.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import type AppContext from "../components/app_context.js";
import TabRowWidget from "../widgets/tab_row.js";
import MobileEditorToolbar from "../widgets/type_widgets/ckeditor/mobile_editor_toolbar.js";
import RefreshButton from "../widgets/floating_buttons/refresh_button.js";
import MobileEditorToolbar from "../widgets/ribbon_widgets/mobile_editor_toolbar.js";
import { applyModals } from "./layout_commons.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
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 CloseZenButton from "../widgets/close_zen_button.js";
const MOBILE_CSS = `
<style>
@@ -132,33 +135,38 @@ export default class MobileLayout {
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
)
.child(
new ScreenContainer("detail", "row")
new ScreenContainer("detail", "column")
.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")
.child(
new NoteWrapperWidget()
.child(
new FlexContainer("row")
.contentSized()
.css("font-size", "larger")
.css("align-items", "center")
.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 />)
new FlexContainer("row")
.contentSized()
.css("font-size", "larger")
.css("align-items", "center")
.child(new ToggleSidebarButtonWidget().contentSized())
.child(new NoteTitleWidget().contentSized().css("position", "relative").css("padding-left", "0.5em"))
.child(new MobileDetailMenuWidget(true).contentSized())
)
.child(new SharedInfoWidget())
.child(
new FloatingButtons()
.child(new RefreshButton())
.child(new EditButton())
.child(new RelationMapButtons())
.child(new SvgExportButton())
.child(new BacklinksWidget())
.child(new HideFloatingButtonsButton())
)
.child(new PromotedAttributesWidget())
.child(
new ScrollingContainer()
.filling()
.contentSized()
.child(new NoteDetailWidget())
.child(new NoteListWidget(false))
.child(new FilePropertiesWidget().css("font-size", "smaller"))
)
.child(new MobileEditorToolbar())
)
)
.child(
@@ -166,25 +174,10 @@ export default class MobileLayout {
.contentSized()
.id("mobile-bottom-bar")
.child(new TabRowWidget().css("height", "40px"))
.child(new FlexContainer("row")
.class("horizontal")
.css("height", "53px")
.child(new LauncherContainer(true))
.child(<GlobalMenuWidget isHorizontalLayout />)
.id("launcher-pane"))
.child(new FlexContainer("row").class("horizontal").css("height", "53px").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true)).id("launcher-pane"))
)
.child(<CloseZenModeButton />);
.child(new CloseZenButton());
applyModals(rootContainer);
return rootContainer;
}
}
function FilePropertiesWrapper() {
const { note } = useNoteContext();
return (
<div>
{note?.type === "file" && <FilePropertiesTab note={note} />}
</div>
);
}

View File

@@ -1,4 +1,4 @@
import "bootstrap/dist/css/bootstrap.min.css";
import "./stylesheets/bootstrap.scss";
// @ts-ignore - module = undefined
// Required for correct loading of scripts in Electron

View File

@@ -1,7 +1,7 @@
import appContext from "./components/app_context.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import glob from "./services/glob.js";
import "bootstrap/dist/css/bootstrap.min.css";
import "./stylesheets/bootstrap.scss";
import "boxicons/css/boxicons.min.css";
import "autocomplete.js/index_jquery.js";

View File

@@ -2,7 +2,6 @@ import server from "./server.js";
import froca from "./froca.js";
import type FNote from "../entities/fnote.js";
import type { AttributeRow } from "./load_results.js";
import { AttributeType } from "@triliumnext/commons";
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
await server.put(`notes/${noteId}/attribute`, {
@@ -26,14 +25,6 @@ async function removeAttributeById(noteId: string, attributeId: string) {
await server.remove(`notes/${noteId}/attributes/${attributeId}`);
}
export async function removeOwnedAttributesByNameOrType(note: FNote, type: AttributeType, name: string) {
for (const attr of note.getOwnedAttributes()) {
if (attr.type === type && attr.name === name) {
await server.remove(`notes/${note.noteId}/attributes/${attr.attributeId}`);
}
}
}
/**
* Removes a label identified by its name from the given note, if it exists. Note that the label must be owned, i.e.
* it will not remove inherited attributes.
@@ -61,7 +52,7 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
* @param value the value of the attribute to set.
*/
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
if (value !== null && value !== undefined) {
if (value) {
// Create or update the attribute.
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
} else {

View File

@@ -18,7 +18,7 @@ import type FNote from "../entities/fnote.js";
import toast from "./toast.js";
import { BulkAction } from "@triliumnext/commons";
export const ACTION_GROUPS = [
const ACTION_GROUPS = [
{
title: t("bulk_actions.labels"),
actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction]

View File

@@ -48,6 +48,6 @@ function getUrl(docNameValue: string, language: string) {
// Cannot have spaces in the URL due to how JQuery.load works.
docNameValue = docNameValue.replaceAll(" ", "%20");
const basePath = window.glob.isDev ? window.glob.assetPath + "/.." : window.glob.assetPath;
const basePath = window.glob.isDev ? new URL(window.glob.assetPath).pathname : window.glob.assetPath;
return `${basePath}/doc_notes/${language}/${docNameValue}.html`;
}

View File

@@ -1,7 +1,7 @@
import { t } from "./i18n.js";
import toastService, { showError } from "./toast.js";
export function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
try {
$imageWrapper.attr("contenteditable", "true");
selectImage($imageWrapper.get(0));

View File

@@ -1,43 +0,0 @@
import { NoteType } from "@triliumnext/commons";
import { ViewTypeOptions } from "./note_list_renderer";
import FNote from "../entities/fnote";
export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
canvas: null,
code: null,
contentWidget: null,
doc: null,
file: null,
image: null,
launcher: null,
mermaid: null,
mindMap: null,
noteMap: null,
relationMap: null,
render: null,
search: null,
text: null,
webView: null,
aiChat: null
};
export const byBookType: Record<ViewTypeOptions, string | null> = {
list: "mULW0Q3VojwY",
grid: "8QqnMzx393bx",
calendar: "xWbu3jpNWapp",
table: "2FvYrpmOXm29",
geoMap: "81SGnPGMk7Xc",
board: "CtBQqbwXDx1w"
};
export function getHelpUrlForNote(note: FNote | null | undefined) {
if (note && note.type !== "book" && byNoteType[note.type]) {
return byNoteType[note.type];
} else if (note?.hasLabel("calendarRoot")) {
return "l0tKav7yLHGF";
} else if (note?.hasLabel("textSnippet")) {
return "pwc194wlRzcH";
} else if (note && note.type === "book") {
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
}
}

View File

@@ -62,10 +62,6 @@ async function getAction(actionName: string, silent = false) {
return action;
}
export function getActionSync(actionName: string) {
return keyboardActionRepo[actionName];
}
function updateDisplayedShortcuts($container: JQuery<HTMLElement>) {
//@ts-ignore
//TODO: each() does not support async callbacks.

View File

@@ -36,8 +36,6 @@ export interface Suggestion {
commandId?: string;
commandDescription?: string;
commandShortcut?: string;
attributeSnippet?: string;
highlightedAttributeSnippet?: string;
}
export interface Options {
@@ -325,33 +323,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
html += '</div>';
return html;
}
// Add special class for search-notes action
const actionClass = suggestion.action === "search-notes" ? "search-notes-action" : "";
// Choose appropriate icon based on action
let iconClass = suggestion.icon ?? "bx bx-note";
if (suggestion.action === "search-notes") {
iconClass = "bx bx-search";
} else if (suggestion.action === "create-note") {
iconClass = "bx bx-plus";
} else if (suggestion.action === "external-link") {
iconClass = "bx bx-link-external";
}
// Simplified HTML structure without nested divs
let html = `<div class="note-suggestion ${actionClass}">`;
html += `<span class="icon ${iconClass}"></span>`;
html += `<span class="text">`;
html += `<span class="search-result-title">${suggestion.highlightedNotePathTitle}</span>`;
// Add attribute snippet inline if available
if (suggestion.highlightedAttributeSnippet) {
html += `<span class="search-result-attributes">${suggestion.highlightedAttributeSnippet}</span>`;
}
html += `</span>`;
html += `</div>`;
return html;
return `<span class="${suggestion.icon ?? "bx bx-note"}"></span> ${suggestion.highlightedNotePathTitle}`;
}
},
// we can't cache identical searches because notes can be created / renamed, new recent notes can be added

View File

@@ -35,7 +35,7 @@ function download(url: string) {
}
}
export function downloadFileNote(noteId: string) {
function downloadFileNote(noteId: string) {
const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache
download(url);
@@ -163,7 +163,7 @@ async function openExternally(type: string, entityId: string, mime: string) {
}
}
export const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime);
const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime);
const openAttachmentExternally = async (attachmentId: string, mime: string) => await openExternally("attachments", attachmentId, mime);
function getHost() {

View File

@@ -10,10 +10,6 @@ let leftInstance: ReturnType<typeof Split> | null;
let rightPaneWidth: number;
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) {
if (leftInstance) {
leftInstance.destroy();
@@ -87,86 +83,7 @@ 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 {
setupLeftPaneResizer,
setupRightPaneResizer,
setupNoteSplitResizer,
delNoteSplitResizer,
moveNoteSplitResizer
setupRightPaneResizer
};

View File

@@ -218,7 +218,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
if (utils.isElectron()) {
const ipc = utils.dynamicRequire("electron").ipcRenderer;
ipc.on("server-response", async (_, arg: Arg) => {
ipc.on("server-response", async (event: string, arg: Arg) => {
if (arg.statusCode >= 200 && arg.statusCode < 300) {
handleSuccessfulResponse(arg);
} else {

View File

@@ -1,5 +1,5 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
import shortcuts, { keyMatches, matchesShortcut } from "./shortcuts.js";
// Mock utils module
vi.mock("./utils.js", () => ({
@@ -320,36 +320,4 @@ describe("shortcuts", () => {
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);
});
});
});

View File

@@ -40,24 +40,6 @@ for (let i = 1; i <= 19; 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) {
bindGlobalShortcut("", null, namespace);
}
@@ -86,13 +68,6 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
}
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)) {
e.preventDefault();
e.stopPropagation();

View File

@@ -1,6 +1,5 @@
import dayjs from "dayjs";
import type { ViewScope } from "./link.js";
import FNote from "../entities/fnote";
const SVG_MIME = "image/svg+xml";
@@ -149,7 +148,7 @@ export function isElectron() {
return !!(window && window.process && window.process.type);
}
export function isMac() {
function isMac() {
return navigator.platform.indexOf("Mac") > -1;
}
@@ -186,11 +185,7 @@ export function escapeQuotes(value: string) {
return value.replaceAll('"', "&quot;");
}
export function formatSize(size: number | null | undefined) {
if (size === null || size === undefined) {
return "";
}
function formatSize(size: number) {
size = Math.max(Math.round(size / 1024), 1);
if (size < 1024) {
@@ -297,55 +292,7 @@ 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() {
async function clearBrowserCache() {
if (isElectron()) {
const win = dynamicRequire("@electron/remote").getCurrentWindow();
await win.webContents.session.clearCache();
@@ -359,13 +306,7 @@ function copySelectionToClipboard() {
}
}
type dynamicRequireMappings = {
"@electron/remote": typeof import("@electron/remote"),
"electron": typeof import("electron"),
"child_process": typeof import("child_process")
};
export function dynamicRequire<T extends keyof dynamicRequireMappings>(moduleName: T): Awaited<dynamicRequireMappings[T]>{
export function dynamicRequire(moduleName: string) {
if (typeof __non_webpack_require__ !== "undefined") {
return __non_webpack_require__(moduleName);
} else {
@@ -629,7 +570,8 @@ function copyHtmlToClipboard(content: string) {
document.removeEventListener("copy", listener);
}
export function createImageSrcUrl(note: FNote) {
// TODO: Set to FNote once the file is ported.
function createImageSrcUrl(note: { noteId: string; title: string }) {
return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`;
}
@@ -798,7 +740,7 @@ function isUpdateAvailable(latestVersion: string | null | undefined, currentVers
return compareVersions(latestVersion, currentVersion) > 0;
}
export function isLaunchBarConfig(noteId: string) {
function isLaunchBarConfig(noteId: string) {
return ["_lbRoot", "_lbAvailableLaunchers", "_lbVisibleLaunchers", "_lbMobileRoot", "_lbMobileAvailableLaunchers", "_lbMobileVisibleLaunchers"].includes(noteId);
}
@@ -846,38 +788,6 @@ export function arrayEqual<T>(a: T[], b: T[]) {
return true;
}
type Indexed<T extends object> = T & { index: number };
/**
* Given an object array, alters every object in the array to have an index field assigned to it.
*
* @param items the objects to be numbered.
* @returns the same object for convenience, with the type changed to indicate the new index field.
*/
export function numberObjectsInPlace<T extends object>(items: T[]): Indexed<T>[] {
let index = 0;
for (const item of items) {
(item as Indexed<T>).index = index++;
}
return items as Indexed<T>[];
}
export function mapToKeyValueArray<K extends string | number | symbol, V>(map: Record<K, V>) {
const values: { key: K, value: V }[] = [];
for (const [ key, value ] of Object.entries(map)) {
values.push({ key: key as K, value: value as V });
}
return values;
}
export function getErrorMessage(e: unknown) {
if (e && typeof e === "object" && "message" in e && typeof e.message === "string") {
return e.message;
} else {
return "Unknown error";
}
}
export default {
reloadFrontendApp,
restartDesktopApp,
@@ -903,7 +813,6 @@ export default {
getNoteTypeClass,
getMimeTypeClass,
isHtmlEmpty,
formatHtml,
clearBrowserCache,
copySelectionToClipboard,
dynamicRequire,

View File

@@ -1,4 +1,4 @@
import "bootstrap/dist/css/bootstrap.min.css";
import "./stylesheets/bootstrap.scss";
import "./stylesheets/auth.css";
// @TriliumNextTODO: is this even needed anymore?

View File

@@ -1,7 +1,7 @@
import "jquery";
import utils from "./services/utils.js";
import ko from "knockout";
import "bootstrap/dist/css/bootstrap.min.css";
import "./stylesheets/bootstrap.scss";
// TriliumNextTODO: properly make use of below types
// type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | "";

View File

@@ -1,6 +1,6 @@
import "normalize.css";
import "boxicons/css/boxicons.min.css";
import "@triliumnext/ckeditor5/src/theme/ck-content.css";
import "@triliumnext/ckeditor5/content.css";
import "@triliumnext/share-theme/styles/index.css";
import "@triliumnext/share-theme/scripts/index.js";

View File

@@ -0,0 +1,2 @@
/* Import all of Bootstrap's CSS */
@use "bootstrap/scss/bootstrap";

View File

@@ -28,28 +28,6 @@
--ck-mention-list-max-height: 500px;
}
body#trilium-app.motion-disabled *,
body#trilium-app.motion-disabled *::before,
body#trilium-app.motion-disabled *::after {
/* Disable transitions and animations */
transition: none !important;
animation: none !important;
}
body#trilium-app.shadows-disabled *,
body#trilium-app.shadows-disabled *::before,
body#trilium-app.shadows-disabled *::after {
/* Disable shadows */
box-shadow: none !important;
}
body#trilium-app.backdrop-effects-disabled *,
body#trilium-app.backdrop-effects-disabled *::before,
body#trilium-app.backdrop-effects-disabled *::after {
/* Disable backdrop effects */
backdrop-filter: none !important;
}
.table {
--bs-table-bg: transparent !important;
}
@@ -176,11 +154,6 @@ label.tn-checkbox + label.tn-checkbox {
margin-left: 12px;
}
label.tn-radio input[type="radio"],
label.tn-checkbox input[type="checkbox"] {
margin-right: .5em;
}
#left-pane input,
#left-pane select,
#left-pane textarea {
@@ -382,7 +355,7 @@ body.desktop .tabulator-popup-container {
@supports (animation-fill-mode: forwards) {
/* Delay the opening of submenus */
body.desktop:not(.motion-disabled) .dropdown-submenu .dropdown-menu {
body.desktop .dropdown-submenu .dropdown-menu {
opacity: 0;
animation-fill-mode: forwards;
animation-delay: var(--submenu-opening-delay);
@@ -442,20 +415,14 @@ body #context-menu-container .dropdown-item > span {
align-items: center;
}
.dropdown-item span.keyboard-shortcut {
.dropdown-menu kbd {
flex-grow: 1;
text-align: right;
}
.dropdown-menu kbd {
color: var(--muted-text-color);
border: none;
background-color: transparent;
box-shadow: none;
padding-bottom: 0;
padding: 0;
flex-grow: 1;
text-align: right;
}
.dropdown-item,
@@ -873,34 +840,10 @@ table.promoted-attributes-in-tooltip th {
.aa-dropdown-menu .aa-suggestion {
cursor: pointer;
padding: 6px 16px;
padding: 5px;
margin: 0;
}
.aa-dropdown-menu .aa-suggestion .icon {
display: inline-block;
line-height: inherit;
vertical-align: top;
}
.aa-dropdown-menu .aa-suggestion .text {
display: inline-block;
width: calc(100% - 20px);
padding-left: 4px;
}
.aa-dropdown-menu .aa-suggestion .search-result-title {
display: block;
}
.aa-dropdown-menu .aa-suggestion .search-result-attributes {
display: block;
font-size: 0.8em;
color: var(--muted-text-color);
opacity: 0.6;
line-height: 1;
}
.aa-dropdown-menu .aa-suggestion p {
padding: 0;
margin: 0;
@@ -988,11 +931,6 @@ div[data-notify="container"] {
font-family: var(--monospace-font-family);
}
svg.ck-icon .note-icon {
color: var(--main-text-color);
font-size: 20px;
}
.ck-content {
--ck-content-font-family: var(--detail-font-family);
--ck-content-font-size: 1.1em;
@@ -1134,7 +1072,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
.toast-body {
white-space: preserve-breaks;
overflow: hidden;
}
.ck-mentions .ck-button {
@@ -1243,10 +1180,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
cursor: row-resize;
}
.hidden-ext.note-split + .gutter {
display: none;
}
#context-menu-cover.show {
position: fixed;
top: 0;
@@ -1468,7 +1401,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
cursor: pointer;
border: none;
color: var(--launcher-pane-text-color);
background: transparent;
background-color: var(--launcher-pane-background-color);
flex-shrink: 0;
}
@@ -1776,6 +1709,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
}
.note-split {
flex-basis: 0; /* so that each split has same width */
margin-left: auto;
margin-right: auto;
}
@@ -1839,42 +1773,20 @@ textarea {
font-size: 1em;
}
.jump-to-note-dialog .modal-dialog {
max-width: 900px;
width: 90%;
}
.jump-to-note-dialog .modal-header {
align-items: center;
}
.jump-to-note-dialog .modal-body {
padding: 0;
min-height: 200px;
}
.jump-to-note-results .aa-dropdown-menu {
max-height: calc(80vh - 200px);
width: 100%;
max-width: none;
overflow-y: auto;
overflow-x: hidden;
text-overflow: ellipsis;
box-shadow: none;
}
.jump-to-note-results {
width: 100%;
max-height: 40vh;
}
.jump-to-note-results .aa-suggestions {
padding: 0;
width: 100%;
}
.jump-to-note-results .aa-dropdown-menu .aa-suggestion:hover,
.jump-to-note-results .aa-dropdown-menu .aa-cursor {
background-color: var(--hover-item-background-color, #f8f9fa);
padding: 1rem;
}
/* Command palette styling */
@@ -1892,24 +1804,8 @@ textarea {
.jump-to-note-dialog .aa-cursor .command-suggestion,
.jump-to-note-dialog .aa-suggestion:hover .command-suggestion {
background-color: transparent;
}
.jump-to-note-dialog .show-in-full-search,
.jump-to-note-results .show-in-full-search {
border-top: 1px solid var(--main-border-color);
padding-top: 12px;
margin-top: 12px;
}
.jump-to-note-results .aa-suggestion .search-notes-action {
border-top: 1px solid var(--main-border-color);
margin-top: 8px;
padding-top: 8px;
}
.jump-to-note-results .aa-suggestion:has(.search-notes-action)::after {
display: none;
border-left-color: var(--link-color);
background-color: var(--hover-background-color);
}
.jump-to-note-dialog .command-icon {
@@ -2364,27 +2260,16 @@ footer.webview-footer button {
padding: 1px 10px 1px 10px;
}
/* Search result highlighting */
.search-result-title b,
.search-result-content b {
font-weight: 900;
color: var(--admonition-warning-accent-color);
}
/* Customized icons */
.bx-tn-toc::before {
content: "\ec24";
transform: rotate(180deg);
}
/* CK Edito */
/* Insert text snippet: limit the width of the listed items to avoid overly long names */
:root body.desktop div.ck-template-form li.ck-list__item .ck-template-form__text-part > span {
max-width: 25vw;
overflow: hidden;
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;
}

View File

@@ -13,13 +13,12 @@
--theme-style: dark;
--native-titlebar-background: #00000000;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: #272727;
--main-text-color: #ccc;
--main-border-color: #454545;
--subtle-border-color: #313131;
--dropdown-border-color: #404040;
--dropdown-border-color: #292929;
--dropdown-shadow-opacity: 0.6;
--dropdown-item-icon-destructive-color: #de6e5b;
--disabled-tooltip-icon-color: #7fd2ef;
@@ -90,7 +89,6 @@
--menu-text-color: #e3e3e3;
--menu-background-color: #222222d9;
--menu-background-color-no-backdrop: #1b1b1b;
--menu-item-icon-color: #8c8c8c;
--menu-item-disabled-opacity: 0.5;
--menu-item-keyboard-shortcut-color: #ffffff8f;
@@ -122,8 +120,6 @@
--quick-search-focus-border: #80808095;
--quick-search-focus-background: #ffffff1f;
--quick-search-focus-color: white;
--quick-search-result-content-background: #0000004d;
--quick-search-result-highlight-color: #a4d995;
--left-pane-collapsed-border-color: #0009;
--left-pane-background-color: #1f1f1f;
@@ -148,7 +144,6 @@
--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-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-background-color: #282828;
@@ -157,8 +152,6 @@
--launcher-pane-horiz-button-hover-background: #ffffff1c;
--launcher-pane-horiz-button-hover-shadow: unset;
--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;
--sync-status-error-pulse-color: #f47871;

View File

@@ -13,7 +13,6 @@
--theme-style: light;
--native-titlebar-background: #ffffff00;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: white;
--main-text-color: black;
@@ -84,7 +83,6 @@
--menu-text-color: #272727;
--menu-background-color: #ffffffd9;
--menu-background-color-no-backdrop: #fdfdfd;
--menu-item-icon-color: #727272;
--menu-item-disabled-opacity: 0.6;
--menu-item-keyboard-shortcut-color: #666666a8;
@@ -116,17 +114,15 @@
--quick-search-focus-border: #00000029;
--quick-search-focus-background: #ffffff80;
--quick-search-focus-color: #000;
--quick-search-result-content-background: #0000000f;
--quick-search-result-highlight-color: #c65050;
--left-pane-collapsed-border-color: #0000000d;
--left-pane-background-color: #f2f2f2;
--left-pane-text-color: #383838;
--left-pane-item-hover-background: rgba(0, 0, 0, 0.032);
--left-pane-item-hover-background: #eaeaea;
--left-pane-item-selected-background: white;
--left-pane-item-selected-color: black;
--left-pane-item-selected-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
--left-pane-item-action-button-background: rgba(0, 0, 0, 0.11);
--left-pane-item-action-button-background: #d7d7d7;
--left-pane-item-action-button-color: inherit;
--left-pane-item-action-button-hover-background: white;
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
@@ -142,7 +138,6 @@
--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-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-background-color: #fafafa;
@@ -150,8 +145,6 @@
--launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background);
--launcher-pane-horiz-button-hover-shadow: unset;
--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;
--sync-status-error-pulse-color: #ff5528;

View File

@@ -83,12 +83,6 @@
--tab-note-icons: true;
}
body.backdrop-effects-disabled {
/* Backdrop effects are disabled, replace the menu background color with the
* no-backdrop fallback color */
--menu-background-color: var(--menu-background-color-no-backdrop);
}
/*
* MENUS
*
@@ -197,17 +191,13 @@ html body .dropdown-item[disabled] {
/* Menu item keyboard shortcut */
.dropdown-item kbd {
margin-left: 16px;
font-family: unset !important;
font-size: unset !important;
color: var(--menu-item-keyboard-shortcut-color) !important;
padding-top: 0;
}
.dropdown-item span.keyboard-shortcut {
color: var(--menu-item-keyboard-shortcut-color) !important;
margin-left: 16px;
}
.dropdown-divider {
position: relative;
border-color: transparent !important;
@@ -329,8 +319,6 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
#toast-container .toast .toast-body {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
/*
@@ -542,9 +530,10 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
}
/* List item */
.jump-to-note-dialog .aa-suggestion,
.note-detail-empty .aa-suggestion {
.jump-to-note-dialog .aa-suggestions div,
.note-detail-empty .aa-suggestions div {
border-radius: 6px;
padding: 6px 12px;
color: var(--menu-text-color);
cursor: default;
}

View File

@@ -5,8 +5,7 @@
button.btn.btn-primary,
button.btn.btn-secondary,
button.btn.btn-sm:not(.select-button),
button.btn.btn-success,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text {
button.btn.btn-success {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -22,8 +21,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .c
button.btn.btn-primary:hover,
button.btn.btn-secondary:hover,
button.btn.btn-sm:not(.select-button):hover,
button.btn.btn-success:hover,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):hover {
button.btn.btn-success:hover {
background: var(--cmd-button-hover-background-color);
color: var(--cmd-button-hover-text-color);
}
@@ -31,8 +29,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .c
button.btn.btn-primary:active,
button.btn.btn-secondary:active,
button.btn.btn-sm:not(.select-button):active,
button.btn.btn-success:active,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):active {
button.btn.btn-success:active {
opacity: 0.85;
box-shadow: unset;
background: var(--cmd-button-background-color) !important;
@@ -43,16 +40,14 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .c
button.btn.btn-primary:disabled,
button.btn.btn-secondary:disabled,
button.btn.btn-sm:not(.select-button):disabled,
button.btn.btn-success:disabled,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text.ck-disabled {
button.btn.btn-success:disabled {
opacity: var(--cmd-button-disabled-opacity);
}
button.btn.btn-primary:focus-visible,
button.btn.btn-secondary:focus-visible,
button.btn.btn-sm:not(.select-button):focus-visible,
button.btn.btn-success:focus-visible,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):focus-visible {
button.btn.btn-success:focus-visible {
outline: 2px solid var(--input-focus-outline-color);
}
@@ -154,11 +149,8 @@ input[type="password"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
:root input.ck.ck-input-text,
:root input.ck.ck-input-number,
textarea.form-control,
textarea,
:root textarea.ck.ck-textarea,
.tn-input-field {
outline: 3px solid transparent;
outline-offset: 6px;
@@ -175,11 +167,8 @@ input[type="password"]:hover,
input[type="date"]:hover,
input[type="time"]:hover,
input[type="datetime-local"]:hover,
:root input.ck.ck-input-text:not([readonly="true"]):hover,
:root input.ck.ck-input-number:not([readonly="true"]):hover,
textarea.form-control:hover,
textarea:hover,
:root textarea.ck.ck-textarea:hover,
.tn-input-field:hover {
background: var(--input-hover-background);
color: var(--input-hover-color);
@@ -192,11 +181,8 @@ input[type="password"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="datetime-local"]:focus,
:root input.ck.ck-input-text:focus,
:root input.ck.ck-input-number:focus,
textarea.form-control:focus,
textarea:focus,
:root textarea.ck.ck-textarea:focus,
.tn-input-field:focus,
.tn-input-field:focus-within {
box-shadow: unset;
@@ -469,7 +455,6 @@ optgroup {
left: 0;
width: var(--box-size);
height: 100%;
margin: unset;
opacity: 0 !important;
}

View File

@@ -4,7 +4,6 @@
:root {
--ck-font-face: var(--main-font-family);
--ck-input-label-height: 1.5em;
}
/*
@@ -308,11 +307,6 @@
fill: black !important;
}
/* Hex color input box prefix */
:root .ck.ck-color-selector .ck-color-picker__hash-view {
margin-top: var(--ck-input-label-height);
}
/* Numbered list */
:root .ck.ck-list-properties_with-numbered-properties .ck.ck-list-styles-list {
@@ -369,86 +363,19 @@
color: var(--accent);
}
/* Text snippet dropdown */
/* Action buttons */
div.ck-template-form {
padding: 8px;
:root .ck-link-actions button.ck-button,
:root .ck-link-form button.ck-button {
--ck-border-radius: 6px;
background: transparent;
box-shadow: unset;
}
div.ck-template-form .ck-labeled-field-view {
margin-bottom: 8px;
}
/* Template item */
:root div.ck-template-form li.ck-list__item button.ck-template-button {
padding: 4px 8px;
}
/* Template icon */
:root .ck-template-form .ck-button__icon {
--ck-spacing-medium: 2px;
}
:root div.ck-template-form .note-icon {
color: var(--menu-item-icon-color);
}
/* Template name */
div.ck-template-form .ck-template-form__text-part {
color: var(--hover-item-text-color);
font-size: .9rem;
}
div.ck-template-form .ck-template-form__text-part mark {
background: unset;
color: var(--quick-search-result-highlight-color);
font-weight: bold;
}
/* Template description */
:root div.ck-template-form .ck-template-form__description {
opacity: .5;
font-size: .9em;
}
/* Messages */
div.ck-template-form .ck-search__info > span {
line-height: initial;
color: var(--muted-text-color);
}
div.ck-template-form .ck-search__info span:nth-child(2) {
display: block;
opacity: .5;
margin-top: 8px;
font-size: .9em;
}
/* Link dropdown */
:root .ck.ck-form.ck-link-form ul.ck-link-form__providers-list {
border-top: none;
}
/* Math popup */
.ck-math-form .ck-labeled-field-view {
--ck-input-label-height: 0;
margin-inline-end: 8px;
}
/* Emoji dropdown */
.ck-emoji-picker-form .ck-emoji__search .ck-button_with-text:not(.ck-list-item-button) {
margin-top: var(--ck-input-label-height);
}
/* Find and replace dialog */
.ck-find-and-replace-form .ck-find-and-replace-form__inputs button {
margin-top: var(--ck-input-label-height);
:root .ck-link-actions button.ck-button:hover,
:root .ck-link-form button.ck-button:hover {
background: var(--hover-item-background-color);
}
/* Mention list (the autocompletion list for emojis, labels and relations) */
@@ -465,58 +392,6 @@ div.ck-template-form .ck-search__info span:nth-child(2) {
background: transparent;
}
/*
* FORMS
*/
/*
* Buttons
*/
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck-button_with-text {
--ck-color-text: var(--cmd-button-text-color);
min-width: 60px;
font-weight: 500;
}
/*
* Text boxes
*/
.ck.ck-labeled-field-view {
padding-top: var(--ck-input-label-height) !important; /* Create space for the label */
}
.ck.ck-labeled-field-view > .ck.ck-labeled-field-view__input-wrapper > label.ck.ck-label {
/* Move the label above the text box regardless of the text box state */
transform: translate(0, calc(-.2em - var(--ck-input-label-height))) !important;
padding-left: 0 !important;
background: transparent;
font-size: .85em;
font-weight: 600;
}
:root input.ck.ck-input-text[readonly="true"] {
cursor: not-allowed;
background: var(--input-background-color);
}
/* Forms */
:root .ck.ck-form__row.ck-form__row_with-submit > :not(:first-child) {
margin-inline-start: 16px;
}
.ck.ck-form__row_with-submit button {
margin-top: var(--ck-input-label-height);
}
.ck.ck-form__header {
border-bottom: none;
}
/*
* EDITOR'S CONTENT
*/

View File

@@ -96,6 +96,7 @@
background: var(--background) !important;
color: var(--color) !important;
line-height: unset;
cursor: help;
}
.sql-table-schemas-widget .sql-table-schemas button:hover,
@@ -105,6 +106,18 @@
--color: var(--main-text-color);
}
/* Tooltip */
.tooltip .table-schema {
font-family: var(--monospace-font-family);
font-size: .85em;
}
/* Data type */
.tooltip .table-schema td:nth-child(2) {
color: var(--muted-text-color);
}
/*
* NOTE MAP
*/

View File

@@ -36,23 +36,32 @@ body.mobile {
/* #region Mica */
body.background-effects.platform-win32 {
--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);
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
--tab-background-color: var(--window-background-color-bgfx);
--new-tab-button-background: var(--window-background-color-bgfx);
--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;
}
@media (prefers-color-scheme: dark) {
body.background-effects.platform-win32 {
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.5);
--launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.09);
}
}
body.background-effects.platform-win32.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--left-pane-background-color: transparent;
--left-pane-item-hover-background: rgba(127, 127, 127, 0.05);
--background-material: mica;
}
body.background-effects.platform-win32,
body.background-effects.platform-win32 #root-widget {
background: var(--window-background-color-bgfx) !important;
body.background-effects.platform-win32 #root-widget,
body.background-effects.platform-win32 #launcher-pane .launcher-button {
background: transparent !important;
}
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container,
@@ -81,7 +90,7 @@ body.background-effects.zen #root-widget {
* Gutter
*/
.gutter {
.gutter {
background: var(--gutter-color) !important;
transition: background 150ms ease-out;
}
@@ -321,6 +330,7 @@ body.layout-horizontal > .horizontal {
*/
.calendar-dropdown-widget {
width: unset !important;
padding: 12px;
color: var(--calendar-color);
user-select: none;
@@ -566,18 +576,25 @@ div.quick-search .search-button.show {
* Quick search results
*/
div.quick-search .dropdown-menu {
--quick-search-item-delimiter-color: transparent;
--menu-item-icon-vert-offset: -.065em;
}
/* Item */
.quick-search .dropdown-menu *.dropdown-item {
padding: 8px 12px !important;
}
.quick-search .quick-search-item-icon {
vertical-align: text-bottom;
/* Note icon */
.quick-search .dropdown-menu .dropdown-item > .bx {
position: relative;
top: 1px;
}
/* Note title */
.quick-search .dropdown-menu .dropdown-item > a {
color: var(--menu-text-color);
}
.quick-search .dropdown-menu .dropdown-item > a:hover {
--hover-item-background-color: transparent;
text-decoration: underline;
}
/* Note path */
@@ -588,24 +605,6 @@ div.quick-search .dropdown-menu {
overflow: hidden;
}
/* Note content snippet */
:root .quick-search .search-result-content {
background-color: var(--quick-search-result-content-background);
border-radius: 4px;
}
/* Highlighted search terms */
:root .quick-search .search-result-title b,
:root .quick-search .search-result-content b,
:root .quick-search .search-result-attributes b {
color: var(--quick-search-result-highlight-color);
font-weight: 600;
}
.quick-search div.dropdown-divider {
margin: 8px 0;
}
/*
* TREE PANE
*/
@@ -878,80 +877,6 @@ body.layout-horizontal .tab-row-container {
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
body.electron.background-effects.layout-horizontal .tab-row-container {
border-bottom: unset !important;
}
body.electron.background-effects.layout-horizontal .note-tab-wrapper {
top: 1px;
}
body.electron.background-effects.layout-horizontal .tab-row-container .toggle-button {
position: relative;
}
body.electron.background-effects.layout-horizontal .tab-row-container .toggle-button:after {
content: "";
position: absolute;
bottom: 0;
left: -10px;
right: -10px;
top: 29px;
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-left,
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-right {
position: relative;
}
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-left:after,
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-right:after {
content: "";
position: absolute;
bottom: 0;
left: 0px;
right: 0px;
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
body.electron.background-effects.layout-horizontal .tab-row-container .note-tab[active]:before {
content: "";
position: absolute;
bottom: 0;
left: -32768px;
top: var(--tab-height);
right: calc(100% - 1px);
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
body.electron.background-effects.layout-horizontal .tab-row-container .note-tab[active]:after {
content: "";
position: absolute;
bottom: 0;
left: 100%;
top: var(--tab-height);
right: 0;
width: 100vw;
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
body.electron.background-effects.layout-horizontal .tab-row-container .note-new-tab:before {
content: "";
position: absolute;
bottom: 0;
left: -4px;
top: calc(var(--tab-height), -1);
right: 0;
width: 100vw;
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
body.layout-vertical.electron.platform-darwin .tab-row-container {
border-bottom: 1px solid var(--subtle-border-color);
}
@@ -1167,11 +1092,6 @@ 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 */
}
.split-note-container-widget > .gutter {
background: var(--root-background) !important;
transition: background 150ms ease-out;
}
/*
* Ribbon & note header
*/
@@ -1180,6 +1100,10 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
margin-bottom: 0 !important;
}
.note-split:not(.hidden-ext) + .note-split:not(.hidden-ext) {
border-left: 4px solid var(--root-background);
}
@keyframes note-entrance {
from {
opacity: 0;
@@ -1451,7 +1375,7 @@ div.floating-buttons-children .floating-button:active {
}
/* The first visible floating button */
div.floating-buttons-children > *:first-child {
div.floating-buttons-children > *:nth-child(1 of .visible) {
--border-radius: var(--border-radius-size) 0 0 var(--border-radius-size);
border-radius: var(--border-radius);
}
@@ -1553,6 +1477,13 @@ div.floating-buttons-children .close-floating-buttons:has(.close-floating-button
padding-inline-start: 8px;
}
/* Copy image reference */
.floating-buttons .copy-image-reference-button .hidden-image-copy {
/* Take out of the the hidden image from flexbox to prevent the layout being affected */
position: absolute;
}
/* Code, relation map buttons */
.floating-buttons .code-buttons-widget,

View File

@@ -732,8 +732,7 @@
"note_type": "笔记类型",
"editable": "可编辑",
"basic_properties": "基本属性",
"language": "语言",
"configure_code_notes": "配置代码注释..."
"language": "语言"
},
"book_properties": {
"view_type": "视图类型",
@@ -849,7 +848,7 @@
"debug": "调试",
"debug_description": "调试将打印额外的调试信息到控制台,以帮助调试复杂查询",
"action": "操作",
"search_button": "搜索",
"search_button": "搜索 <kbd>回车</kbd>",
"search_execute": "搜索并执行操作",
"save_to_note": "保存到笔记",
"search_parameters": "搜索参数",
@@ -1440,9 +1439,9 @@
"open-in-popup": "快速编辑"
},
"shared_info": {
"help_link": "访问 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a> 获取帮助。",
"shared_publicly": "笔记已在 {{- link}} 上公开分享",
"shared_locally": "此笔记在本地通过 {{- link}} 进行共享。"
"shared_publicly": "此笔记已公开分享于",
"shared_locally": "笔记已在本地分享",
"help_link": "访问 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a> 获取帮助。"
},
"note_types": {
"text": "文本",
@@ -1520,8 +1519,7 @@
"hoist-this-note-workspace": "聚焦此笔记(工作区)",
"refresh-saved-search-results": "刷新保存的搜索结果",
"create-child-note": "创建子笔记",
"unhoist": "取消聚焦",
"toggle-sidebar": "切换侧边栏"
"unhoist": "取消聚焦"
},
"title_bar_buttons": {
"window-on-top": "保持此窗口置顶"
@@ -1873,12 +1871,7 @@
"selected_provider": "已选提供商",
"selected_provider_description": "选择用于聊天和补全功能的AI提供商",
"select_model": "选择模型...",
"select_provider": "选择提供商...",
"ai_enabled": "已启用 AI 功能",
"ai_disabled": "已禁用 AI 功能",
"no_models_found_online": "找不到模型。请检查您的 API 密钥及设置。",
"no_models_found_ollama": "找不到 Ollama 模型。请确认 Ollama 是否正在运行。",
"error_fetching": "获取模型失败:{{error}}"
"select_provider": "选择提供商..."
},
"code-editor-options": {
"title": "编辑器"
@@ -2006,21 +1999,5 @@
"next_theme_message": "当前使用旧版主题,要试用新主题吗?",
"next_theme_button": "试用新主题",
"dismiss": "关闭"
},
"settings": {
"related_settings": "相关设置"
},
"settings_appearance": {
"related_code_blocks": "文本笔记中代码块的色彩方案",
"related_code_notes": "代码笔记的色彩方案"
},
"units": {
"percentage": "%"
},
"ui-performance": {
"title": "性能",
"enable-motion": "启用过渡和动画",
"enable-shadows": "启用阴影",
"enable-backdrop-effects": "启用菜单、弹窗和面板的背景效果"
}
}

View File

@@ -202,12 +202,11 @@
"okButton": "OK"
},
"jump_to_note": {
"search_button": "Suche im Volltext",
"search_placeholder": "Suche nach Notiz anhand ihres Titels oder gib > ein für Kommandos..."
"search_button": "Suche im Volltext"
},
"markdown_import": {
"dialog_title": "Markdown-Import",
"modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“",
"modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“.",
"import_button": "Importieren",
"import_success": "Markdown-Inhalt wurde in das Dokument importiert."
},
@@ -218,26 +217,21 @@
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
"move_button": "Zur ausgewählten Notiz wechseln",
"error_no_path": "Kein Weg, auf den man sich bewegen kann.",
"move_success_message": "Ausgewählte Notizen wurden verschoben in "
"move_success_message": "Ausgewählte Notizen wurden verschoben"
},
"note_type_chooser": {
"modal_title": "Wähle den Notiztyp aus",
"modal_body": "Wähle den Notiztyp / die Vorlage der neuen Notiz:",
"templates": "Vorlagen",
"change_path_prompt": "Ändern wo die neue Notiz erzeugt wird:",
"search_placeholder": "Durchsuche Pfad nach Namen (Standard falls leer)",
"builtin_templates": "Eingebaute Vorlage"
"templates": "Vorlagen"
},
"password_not_set": {
"title": "Das Passwort ist nicht festgelegt",
"body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt.",
"body2": "Um Notizen zu schützen, klicke den unteren Button um den Optionsdialog zu öffnen und dein Passwort festzulegen.",
"go_to_password_options": "Gehe zu Passwortoptionen"
"body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt."
},
"prompt": {
"title": "Eingabeaufforderung",
"title": "Prompt",
"ok": "OK",
"defaultTitle": "Eingabeaufforderung"
"defaultTitle": "Prompt"
},
"protected_session_password": {
"modal_title": "Geschützte Sitzung",
@@ -271,12 +265,10 @@
"maximum_revisions": "Maximale Revisionen für aktuelle Notiz: {{number}}.",
"settings": "Einstellungen für Notizrevisionen",
"download_button": "Herunterladen",
"mime": "MIME: ",
"mime": "MIME:",
"file_size": "Dateigröße:",
"preview": "Vorschau:",
"preview_not_available": "Für diesen Notiztyp ist keine Vorschau verfügbar.",
"restore_button": "Wiederherstellen",
"delete_button": "Löschen"
"preview_not_available": "Für diesen Notiztyp ist keine Vorschau verfügbar."
},
"sort_child_notes": {
"sort_children_by": "Unternotizen sortieren nach...",
@@ -356,7 +348,7 @@
"sorted": "Hält untergeordnete Notizen alphabetisch nach Titel sortiert",
"sort_direction": "ASC (Standard) oder DESC",
"sort_folders_first": "Ordner (Notizen mit Unternotizen) sollten oben sortiert werden",
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen)",
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen).",
"hide_promoted_attributes": "Heraufgestufte Attribute für diese Notiz ausblenden",
"read_only": "Der Editor befindet sich im schreibgeschützten Modus. Funktioniert nur für Text- und Codenotizen.",
"auto_read_only_disabled": "Text-/Codenotizen können automatisch in den Lesemodus versetzt werden, wenn sie zu groß sind. Du kannst dieses Verhalten für jede einzelne Notiz deaktivieren, indem du diese Beschriftung zur Notiz hinzufügst",
@@ -379,10 +371,10 @@
"inbox": "Standard-Inbox-Position für neue Notizen wenn du eine Notiz über den \"Neue Notiz\"-Button in der Seitenleiste erstellst, wird die Notiz als untergeordnete Notiz der Notiz erstellt, die mit dem <code>#inbox</code>-Label markiert ist.",
"workspace_inbox": "Standard-Posteingangsspeicherort für neue Notizen, wenn sie zu einem Vorgänger dieser Arbeitsbereichsnotiz verschoben werden",
"sql_console_home": "Standardspeicherort der SQL-Konsolennotizen",
"bookmark_folder": "Notizen mit dieser Bezeichnung werden in den Lesezeichen als Ordner angezeigt (und ermöglichen den Zugriff auf ihre untergeordneten Ordner)",
"bookmark_folder": "Notizen mit dieser Bezeichnung werden in den Lesezeichen als Ordner angezeigt (und ermöglichen den Zugriff auf ihre untergeordneten Ordner).",
"share_hidden_from_tree": "Diese Notiz ist im linken Navigationsbaum ausgeblendet, kann aber weiterhin über ihre URL aufgerufen werden",
"share_external_link": "Die Notiz dient als Link zu einer externen Website im Freigabebaum",
"share_alias": "Lege einen Alias fest, mit dem die Notiz unter https://your_trilium_host/share/[dein_alias] verfügbar sein wird",
"share_alias": "Lege einen Alias fest, unter dem die Notiz unter https://your_trilium_host/share/[dein_alias] verfügbar sein wird.",
"share_omit_default_css": "Das Standard-CSS für die Freigabeseite wird weggelassen. Verwende es, wenn du umfangreiche Stylingänderungen vornimmst.",
"share_root": "Markiert eine Notiz, die im /share-Root bereitgestellt wird.",
"share_description": "Definiere Text, der dem HTML-Meta-Tag zur Beschreibung hinzugefügt werden soll",
@@ -398,7 +390,7 @@
"color": "Definiert die Farbe der Notiz im Notizbaum, in Links usw. Verwende einen beliebigen gültigen CSS-Farbwert wie „rot“ oder #a13d5f",
"keyboard_shortcut": "Definiert eine Tastenkombination, die sofort zu dieser Notiz springt. Beispiel: „Strg+Alt+E“. Erfordert ein Neuladen des Frontends, damit die Änderung wirksam wird.",
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Unterbaum nicht angezeigt werden kann.",
"execute_button": "Titel der Schaltfläche, welche die aktuelle Codenotiz ausführt",
"execute_button": "Titel der Schaltfläche, die die aktuelle Codenotiz ausführt",
"execute_description": "Längere Beschreibung der aktuellen Codenotiz, die zusammen mit der Schaltfläche „Ausführen“ angezeigt wird",
"exclude_from_note_map": "Notizen mit dieser Bezeichnung werden in der Notizenkarte ausgeblendet",
"new_notes_on_top": "Neue Notizen werden oben in der übergeordneten Notiz erstellt, nicht unten.",
@@ -413,10 +405,10 @@
"run_on_branch_change": "wird ausgeführt, wenn ein Zweig aktualisiert wird.",
"run_on_branch_deletion": "wird ausgeführt, wenn ein Zweig gelöscht wird. Der Zweig ist eine Verknüpfung zwischen der übergeordneten Notiz und der untergeordneten Notiz und wird z. B. gelöscht. beim Verschieben der Notiz (alter Zweig/Link wird gelöscht).",
"run_on_attribute_creation": "wird ausgeführt, wenn für die Notiz ein neues Attribut erstellt wird, das diese Beziehung definiert",
"run_on_attribute_change": " wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
"run_on_attribute_change": "wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
"relation_template": "Die Attribute der Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Der Inhalt und der Unterbaum der Notiz werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
"inherit": "Die Attribute einer Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributvererbung in der Dokumentation.",
"render_note": "Notizen vom Typ \"HTML-Notiz rendern\" werden mit einer Code-Notiz (HTML oder Skript) gerendert, und es ist notwendig, über diese Beziehung anzugeben, welche Notiz gerendert werden soll",
"render_note": "Notizen vom Typ \"HTML-Notiz rendern\" werden mit einer Code-Notiz (HTML oder Skript) gerendert, und es ist notwendig, über diese Beziehung anzugeben, welche Notiz gerendert werden soll.",
"widget_relation": "Das Ziel dieser Beziehung wird ausgeführt und als Widget in der Seitenleiste gerendert",
"share_css": "CSS-Hinweis, der in die Freigabeseite eingefügt wird. Die CSS-Notiz muss sich ebenfalls im gemeinsamen Unterbaum befinden. Erwäge auch die Verwendung von „share_hidden_from_tree“ und „share_omit_default_css“.",
"share_js": "JavaScript-Hinweis, der in die Freigabeseite eingefügt wird. Die JS-Notiz muss sich ebenfalls im gemeinsamen Unterbaum befinden. Erwäge die Verwendung von „share_hidden_from_tree“.",
@@ -426,8 +418,7 @@
"other_notes_with_name": "Other notes with {{attributeType}} name \"{{attributeName}}\"",
"and_more": "... und {{count}} mehr.",
"print_landscape": "Beim Export als PDF, wird die Seitenausrichtung Querformat anstatt Hochformat verwendet.",
"print_page_size": "Beim Export als PDF, wird die Größe der Seite angepasst. Unterstützte Größen: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
"color_type": "Farbe"
"print_page_size": "Beim Export als PDF, wird die Größe der Seite angepasst. Unterstützte Größen: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>."
},
"attribute_editor": {
"help_text_body1": "Um ein Label hinzuzufügen, gebe einfach z.B. ein. <code>#rock</code> oder wenn du auch einen Wert hinzufügen möchten, dann z.B. <code>#year = 2024</code>",
@@ -499,9 +490,9 @@
"to": "nach",
"target_parent_note": "Ziel-Übergeordnetenotiz",
"on_all_matched_notes": "Auf allen übereinstimmenden Notizen",
"move_note_new_parent": "Verschiebe die Notiz in die neue übergeordnete Notiz, wenn die Notiz nur eine übergeordnete Notiz hat (d. h. der alte Zweig wird entfernt und ein neuer Zweig in die neue übergeordnete Notiz erstellt)",
"move_note_new_parent": "Verschiebe die Notiz in die neue übergeordnete Notiz, wenn die Notiz nur eine übergeordnete Notiz hat (d. h. der alte Zweig wird entfernt und ein neuer Zweig in die neue übergeordnete Notiz erstellt).",
"clone_note_new_parent": "Notiz auf die neue übergeordnete Notiz klonen, wenn die Notiz mehrere Klone/Zweige hat (es ist nicht klar, welcher Zweig entfernt werden soll)",
"nothing_will_happen": "Es passiert nichts, wenn die Notiz nicht zur Zielnotiz verschoben werden kann (z.B. wenn dies einen Kreislauf in der Baumstruktur erzeugen würde)"
"nothing_will_happen": "Es passiert nichts, wenn die Notiz nicht zur Zielnotiz verschoben werden kann (d. h. dies würde einen Baumzyklus erzeugen)."
},
"rename_note": {
"rename_note": "Notiz umbenennen",
@@ -509,10 +500,10 @@
"new_note_title": "neuer Notiztitel",
"click_help_icon": "Klicke rechts auf das Hilfesymbol, um alle Optionen anzuzeigen",
"evaluated_as_js_string": "Der angegebene Wert wird als JavaScript-String ausgewertet und kann somit über die injizierte <code>note</code>-Variable mit dynamischem Inhalt angereichert werden (Notiz wird umbenannt). Beispiele:",
"example_note": "<code>Notiz</code> alle übereinstimmenden Notizen werden in „Notiz“ umbenannt",
"example_note": "<code>Notiz</code> alle übereinstimmenden Notizen werden in „Notiz“ umbenannt.",
"example_new_title": "<code>NEU: ${note.title}</code> Übereinstimmende Notiztitel erhalten das Präfix „NEU:“",
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> übereinstimmende Notizen werden mit dem Erstellungsmonat und -datum der Notiz vorangestellt",
"api_docs": "Siehe API-Dokumente für <a href='https://zadam.github.io/trilium/backend_api/Note.html'>Notiz</a> und seinen <a href='https://day.js.org/ docs/en/display/format'>dateCreatedObj / utcDateCreatedObj properties</a> für Details."
"api_docs": "Siehe API-Dokumente für <a hrefu003d'https://zadam.github.io/trilium/backend_api/Note.html'>Notiz</a> und seinen <a hrefu003d'https://day.js.org/ docs/en/display/format'>dateCreatedObj / utcDateCreatedObj-Eigenschaften</a> für Details."
},
"add_relation": {
"add_relation": "Beziehung hinzufügen",
@@ -586,8 +577,7 @@
"september": "September",
"october": "Oktober",
"november": "November",
"december": "Dezember",
"cannot_find_week_note": "Wochennotiz kann nicht gefunden werden"
"december": "Dezember"
},
"close_pane_button": {
"close_this_pane": "Schließe diesen Bereich"
@@ -709,14 +699,14 @@
"zoom_out_title": "Herauszoomen"
},
"zpetne_odkazy": {
"backlink": "{{count}} Rückverlinkung",
"backlinks": "{{count}} Rückverlinkungen",
"backlink": "{{count}} Backlink",
"backlinks": "{{count}} Backlinks",
"relation": "Beziehung"
},
"mobile_detail_menu": {
"insert_child_note": "Untergeordnete Notiz einfügen",
"delete_this_note": "Diese Notiz löschen",
"error_cannot_get_branch_id": "BranchId für notePath „{{notePath}}“ kann nicht abgerufen werden",
"error_cannot_get_branch_id": "BranchId für notePath „{{notePath}}“ kann nicht abgerufen werden.",
"error_unrecognized_command": "Unbekannter Befehl {{command}}"
},
"note_icon": {
@@ -728,9 +718,7 @@
"basic_properties": {
"note_type": "Notiztyp",
"editable": "Bearbeitbar",
"basic_properties": "Grundlegende Eigenschaften",
"language": "Sprache",
"configure_code_notes": "Code-Notizen konfigurieren..."
"basic_properties": "Grundlegende Eigenschaften"
},
"book_properties": {
"view_type": "Ansichtstyp",
@@ -741,11 +729,7 @@
"collapse": "Einklappen",
"expand": "Ausklappen",
"invalid_view_type": "Ungültiger Ansichtstyp „{{type}}“",
"calendar": "Kalender",
"book_properties": "Sammlungseigenschaften",
"table": "Tabelle",
"geo-map": "Weltkarte",
"board": "Tafel"
"calendar": "Kalender"
},
"edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
@@ -821,9 +805,7 @@
"unknown_label_type": "Unbekannter Labeltyp „{{type}}“",
"unknown_attribute_type": "Unbekannter Attributtyp „{{type}}“",
"add_new_attribute": "Neues Attribut hinzufügen",
"remove_this_attribute": "Entferne dieses Attribut",
"unset-field-placeholder": "nicht gesetzt",
"remove_color": "Entferne Farblabel"
"remove_this_attribute": "Entferne dieses Attribut"
},
"script_executor": {
"query": "Abfrage",
@@ -846,7 +828,7 @@
"debug": "debuggen",
"debug_description": "Debug gibt zusätzliche Debuginformationen in die Konsole aus, um das Debuggen komplexer Abfragen zu erleichtern",
"action": "Aktion",
"search_button": "Suchen",
"search_button": "Suchen <kbd>Eingabetaste</kbd>",
"search_execute": "Aktionen suchen und ausführen",
"save_to_note": "Als Notiz speichern",
"search_parameters": "Suchparameter",
@@ -885,7 +867,7 @@
"include_archived_notes": "Füge archivierte Notizen hinzu"
},
"limit": {
"limit": "Limitierung",
"limit": "Limit",
"take_first_x_results": "Nehmen Sie nur die ersten X angegebenen Ergebnisse."
},
"order_by": {
@@ -935,7 +917,7 @@
"attachment_detail": {
"open_help_page": "Hilfeseite zu Anhängen öffnen",
"owning_note": "Eigentümernotiz: ",
"you_can_also_open": ", Du kannst auch das öffnen ",
"you_can_also_open": ", Du kannst auch das öffnen",
"list_of_all_attachments": "Liste aller Anhänge",
"attachment_deleted": "Dieser Anhang wurde gelöscht."
},
@@ -960,8 +942,7 @@
"enter_workspace": "Betrete den Arbeitsbereich {{title}}"
},
"file": {
"file_preview_not_available": "Für dieses Dateiformat ist keine Dateivorschau verfügbar.",
"too_big": "Die Vorschau zeigt aus Effizienzgründen nur die ersten {{maxNumChars}} Zeichen der Datei an. Lade die Datei herunter und öffne sie extern um den gesamten Inhalt zu sehen."
"file_preview_not_available": "Für dieses Dateiformat ist keine Dateivorschau verfügbar."
},
"protected_session": {
"enter_password_instruction": "Um die geschützte Notiz anzuzeigen, musst du dein Passwort eingeben:",
@@ -1000,7 +981,7 @@
"web_view": {
"web_view": "Webansicht",
"embed_websites": "Notiz vom Typ Web View ermöglicht das Einbetten von Websites in Trilium.",
"create_label": "Um zu beginnen, erstelle bitte ein Label mit einer URL-Adresse, die eingebettet werden soll, z. B. #webViewSrc=\"https://www.google.com\""
"create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Aktualisieren"
@@ -1026,7 +1007,7 @@
"error_creating_anonymized_database": "Die anonymisierte Datenbank konnte nicht erstellt werden. Überprüfe die Backend-Protokolle auf Details",
"successfully_created_fully_anonymized_database": "Vollständig anonymisierte Datenbank in {{anonymizedFilePath}} erstellt",
"successfully_created_lightly_anonymized_database": "Leicht anonymisierte Datenbank in {{anonymizedFilePath}} erstellt",
"no_anonymized_database_yet": "Noch keine anonymisierte Datenbank."
"no_anonymized_database_yet": "Noch keine anonymisierte Datenbank"
},
"database_integrity_check": {
"title": "Datenbankintegritätsprüfung",
@@ -1047,7 +1028,7 @@
"failed": "Synchronisierung fehlgeschlagen: {{message}}"
},
"vacuum_database": {
"title": "Datenbank aufräumen",
"title": "Vakuumdatenbank",
"description": "Dadurch wird die Datenbank neu erstellt, was normalerweise zu einer kleineren Datenbankdatei führt. Es werden keine Daten tatsächlich geändert.",
"button_text": "Vakuumdatenbank",
"vacuuming_database": "Datenbank wird geleert...",
@@ -1082,8 +1063,7 @@
"max_width_label": "Maximale Inhaltsbreite in Pixel",
"apply_changes_description": "Um Änderungen an der Inhaltsbreite anzuwenden, klicke auf",
"reload_button": "Frontend neu laden",
"reload_description": "Änderungen an den Darstellungsoptionen",
"max_width_unit": "Pixel"
"reload_description": "Änderungen an den Darstellungsoptionen"
},
"native_title_bar": {
"title": "Native Titelleiste (App-Neustart erforderlich)",
@@ -1106,10 +1086,7 @@
"layout-vertical-title": "Vertikal",
"layout-horizontal-title": "Horizontal",
"layout-vertical-description": "Startleiste ist auf der linken Seite (standard)",
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert.",
"auto_theme": "Alt (Folge dem Farbschema des Systems)",
"light_theme": "Alt (Hell)",
"dark_theme": "Alt (Dunkel)"
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert."
},
"zoom_factor": {
"title": "Zoomfaktor (nur Desktop-Build)",
@@ -1118,8 +1095,7 @@
"code_auto_read_only_size": {
"title": "Automatische schreibgeschützte Größe",
"description": "Die automatische schreibgeschützte Notizgröße ist die Größe, ab der Notizen im schreibgeschützten Modus angezeigt werden (aus Leistungsgründen).",
"label": "Automatische schreibgeschützte Größe (Codenotizen)",
"unit": "Zeichen"
"label": "Automatische schreibgeschützte Größe (Codenotizen)"
},
"code_mime_types": {
"title": "Verfügbare MIME-Typen im Dropdown-Menü"
@@ -1138,13 +1114,12 @@
"download_images_description": "Eingefügter HTML-Code kann Verweise auf Online-Bilder enthalten. Trilium findet diese Verweise und lädt die Bilder herunter, sodass sie offline verfügbar sind.",
"enable_image_compression": "Bildkomprimierung aktivieren",
"max_image_dimensions": "Maximale Breite/Höhe eines Bildes in Pixel (die Größe des Bildes wird geändert, wenn es diese Einstellung überschreitet).",
"jpeg_quality_description": "JPEG-Qualität (10 schlechteste Qualität, 100 beste Qualität, 50 85 wird empfohlen)",
"max_image_dimensions_unit": "Pixel"
"jpeg_quality_description": "JPEG-Qualität (10 schlechteste Qualität, 100 beste Qualität, 50 85 wird empfohlen)"
},
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "Zeitüberschreitung beim Löschen von Anhängen",
"attachment_auto_deletion_description": "Anhänge werden automatisch gelöscht (und gelöscht), wenn sie nach einer definierten Zeitspanne nicht mehr in ihrer Notiz referenziert werden.",
"erase_attachments_after": "Nicht verwendete Anhänge löschen nach:",
"erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
"erase_unused_attachments_now": "Lösche jetzt nicht verwendete Anhangnotizen",
"unused_attachments_erased": "Nicht verwendete Anhänge wurden gelöscht."
@@ -1155,7 +1130,7 @@
},
"note_erasure_timeout": {
"note_erasure_timeout_title": "Beachte das Zeitlimit für die Löschung",
"note_erasure_description": "Gelöschte Notizen (und Attribute, Notizrevisionen...) werden zunächst nur als gelöscht markiert und können über den Dialog „Zuletzt verwendete Notizen” wiederhergestellt werden. Nach einer bestimmten Zeit werden gelöschte Notizen „gelöscht”, was bedeutet, dass ihr Inhalt nicht mehr wiederhergestellt werden kann. Mit dieser Einstellung können Sie die Zeitspanne zwischen dem Löschen und dem endgültigen Löschen der Notiz festlegen.",
"note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.",
"erase_notes_after": "Notizen löschen nach:",
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
"erase_deleted_notes_now": "Jetzt gelöschte Notizen löschen",
@@ -1171,8 +1146,7 @@
"note_revisions_snapshot_limit_description": "Das Limit für Notizrevision-Snapshots bezieht sich auf die maximale Anzahl von Revisionen, die für jede Notiz gespeichert werden können. Dabei bedeutet -1, dass es kein Limit gibt, und 0 bedeutet, dass alle Revisionen gelöscht werden. Du kannst das maximale Limit für Revisionen einer einzelnen Notiz über das Label #versioningLimit festlegen.",
"snapshot_number_limit_label": "Limit der Notizrevision-Snapshots:",
"erase_excess_revision_snapshots": "Überschüssige Revision-Snapshots jetzt löschen",
"erase_excess_revision_snapshots_prompt": "Überschüssige Revision-Snapshots wurden gelöscht.",
"snapshot_number_limit_unit": "Momentaufnahmen"
"erase_excess_revision_snapshots_prompt": "Überschüssige Revision-Snapshots wurden gelöscht."
},
"search_engine": {
"title": "Suchmaschine",
@@ -1214,29 +1188,19 @@
"title": "Inhaltsverzeichnis",
"description": "Das Inhaltsverzeichnis wird in Textnotizen angezeigt, wenn die Notiz mehr als eine definierte Anzahl von Überschriften enthält. Du kannst diese Nummer anpassen:",
"disable_info": "Du kannst diese Option auch verwenden, um TOC effektiv zu deaktivieren, indem du eine sehr hohe Zahl festlegst.",
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Inhaltsverzeichnis) unter Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“).",
"unit": "Überschriften"
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Inhaltsverzeichnis) unter Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
},
"text_auto_read_only_size": {
"title": "Automatische schreibgeschützte Größe",
"description": "Die automatische schreibgeschützte Notizgröße ist die Größe, ab der Notizen im schreibgeschützten Modus angezeigt werden (aus Leistungsgründen).",
"label": "Automatische schreibgeschützte Größe (Textnotizen)",
"unit": "Zeichen"
"label": "Automatische schreibgeschützte Größe (Textnotizen)"
},
"i18n": {
"title": "Lokalisierung",
"language": "Sprache",
"first-day-of-the-week": "Erster Tag der Woche",
"sunday": "Sonntag",
"monday": "Montag",
"first-week-of-the-year": "Erste Woche des Jahres",
"first-week-contains-first-day": "Erste Woche enthält den ersten Tag des Jahres",
"first-week-contains-first-thursday": "Erste Woche enthält den ersten Donnerstag des Jahres",
"first-week-has-minimum-days": "Erste Woche hat Mindestanzahl an Tagen",
"min-days-in-first-week": "Mindestanzahl an Tagen in erster Woche",
"first-week-info": "Die erste Woche, die den ersten Donnerstag des Jahres enthält, basiert auf dem Standard <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a>.",
"first-week-warning": "Das Ändern der Optionen für die erste Woche kann zu Duplikaten mit bestehenden Wochen-Notizen führen. Bestehende Wochen-Notizen werden nicht entsprechend aktualisiert.",
"formatting-locale": "Datums- und Zahlenformat"
"monday": "Montag"
},
"backup": {
"automatic_backup": "Automatische Sicherung",
@@ -1339,8 +1303,7 @@
"test_title": "Synchronisierungstest",
"test_description": "Dadurch werden die Verbindung und der Handshake zum Synchronisierungsserver getestet. Wenn der Synchronisierungsserver nicht initialisiert ist, wird er dadurch für die Synchronisierung mit dem lokalen Dokument eingerichtet.",
"test_button": "Teste die Synchronisierung",
"handshake_failed": "Handshake des Synchronisierungsservers fehlgeschlagen, Fehler: {{message}}",
"timeout_unit": "Millisekunden"
"handshake_failed": "Handshake des Synchronisierungsservers fehlgeschlagen, Fehler: {{message}}"
},
"api_log": {
"close": "Schließen"
@@ -1381,7 +1344,7 @@
"unhoist-note": "Notiz-Fokus aufheben",
"edit-branch-prefix": "Zweig-Präfix bearbeiten",
"advanced": "Erweitert",
"expand-subtree": "Unterzweig aufklappen",
"expand-subtree": "Notizbaum ausklappen",
"collapse-subtree": "Notizbaum einklappen",
"sort-by": "Sortieren nach...",
"recent-changes-in-subtree": "Kürzliche Änderungen im Notizbaum",
@@ -1398,14 +1361,13 @@
"duplicate": "Duplizieren",
"export": "Exportieren",
"import-into-note": "In Notiz importieren",
"apply-bulk-actions": "Massenaktionen anwenden",
"apply-bulk-actions": "Massenaktionen ausführen",
"converted-to-attachments": "{{count}} Notizen wurden als Anhang konvertiert.",
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?",
"open-in-popup": "Schnellbearbeitung"
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?"
},
"shared_info": {
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf",
"shared_locally": "Diese Notiz ist lokal geteilt auf",
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@@ -1415,7 +1377,7 @@
"relation-map": "Beziehungskarte",
"note-map": "Notizkarte",
"render-note": "Render Notiz",
"mermaid-diagram": "Mermaid Diagramm",
"mermaid-diagram": "Mermaid Diagram",
"canvas": "Canvas",
"web-view": "Webansicht",
"mind-map": "Mind Map",
@@ -1425,13 +1387,8 @@
"doc": "Dokument",
"widget": "Widget",
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
"geo-map": "Geo-Karte",
"beta-feature": "Beta",
"book": "Sammlung",
"ai-chat": "KI Chat",
"task-list": "Aufgabenliste",
"new-feature": "Neu",
"collections": "Sammlungen"
"geo-map": "Geo Map",
"beta-feature": "Beta"
},
"protect_note": {
"toggle-on": "Notiz schützen",
@@ -1484,8 +1441,7 @@
"hoist-this-note-workspace": "Diese Notiz fokussieren (Arbeitsbereich)",
"refresh-saved-search-results": "Gespeicherte Suchergebnisse aktualisieren",
"create-child-note": "Unternotiz anlegen",
"unhoist": "Fokus verlassen",
"toggle-sidebar": "Seitenleiste ein-/ausblenden"
"unhoist": "Entfokussieren"
},
"title_bar_buttons": {
"window-on-top": "Dieses Fenster immer oben halten"
@@ -1542,9 +1498,7 @@
},
"clipboard": {
"cut": "Notiz(en) wurden in die Zwischenablage ausgeschnitten.",
"copied": "Notiz(en) wurden in die Zwischenablage kopiert.",
"copy_failed": "Speichern in Zwischenablage aufgrund von Berechtigungsproblemen gescheitert.",
"copy_success": "In Zwischenablage kopiert."
"copied": "Notiz(en) wurden in die Zwischenablage kopiert."
},
"entrypoints": {
"note-revision-created": "Notizrevision wurde erstellt.",
@@ -1588,15 +1542,13 @@
},
"highlighting": {
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
"color-scheme": "Farbschema",
"title": "Code-Blöcke"
"color-scheme": "Farbschema"
},
"code_block": {
"word_wrapping": "Wortumbruch",
"theme_none": "Keine Syntax-Hervorhebung",
"theme_group_light": "Helle Themen",
"theme_group_dark": "Dunkle Themen",
"copy_title": "Kopiere in Zwischenablage"
"theme_group_dark": "Dunkle Themen"
},
"classic_editor_toolbar": {
"title": "Format"
@@ -1609,11 +1561,11 @@
"label": "Format Toolbar",
"floating": {
"title": "Schwebend",
"description": "Werkzeuge erscheinen in Cursornähe;"
"description": "Werkzeuge erscheinen in Cursornähe"
},
"fixed": {
"title": "Fixiert",
"description": "Werkzeuge erscheinen im \"Format\" Tab."
"description": "Werkzeuge erscheinen im \"Format\" Tab"
},
"multiline-toolbar": "Toolbar wenn nötig in mehreren Zeilen darstellen."
}
@@ -1634,8 +1586,7 @@
"link_context_menu": {
"open_note_in_new_tab": "Notiz in neuen Tab öffnen",
"open_note_in_new_split": "Notiz in neuen geteilten Tab öffnen",
"open_note_in_new_window": "Notiz in neuen Fenster öffnen",
"open_note_in_popup": "Schnellbearbeitung"
"open_note_in_new_window": "Notiz in neuen Fenster öffnen"
},
"electron_integration": {
"desktop-application": "Desktop Anwendung",
@@ -1655,8 +1606,7 @@
"full-text-search": "Volltextsuche"
},
"note_tooltip": {
"note-has-been-deleted": "Notiz wurde gelöscht.",
"quick-edit": "Schnellbearbeitung"
"note-has-been-deleted": "Notiz wurde gelöscht."
},
"geo-map": {
"create-child-note-title": "Neue Unternotiz anlegen und zur Karte hinzufügen",
@@ -1665,8 +1615,7 @@
},
"geo-map-context": {
"open-location": "Ort öffnen",
"remove-from-map": "Von Karte entfernen",
"add-note": "Markierung an dieser Position erstellen"
"remove-from-map": "Von Karte entfernen"
},
"help-button": {
"title": "Relevante Hilfeseite öffnen"
@@ -1678,351 +1627,9 @@
"days": "Tage"
},
"time_selector": {
"invalid_input": "Die eingegebene Zeit ist keine valide Zahl.",
"minimum_input": "Die eingegebene Zeit muss mindestens {{minimumSeconds}} Sekunden entsprechen."
"invalid_input": "Die eingegebene Zeit ist keine valide Zahl."
},
"modal": {
"close": "Schließen",
"help_title": "Zeige mehr Informationen zu diesem Fenster"
},
"ai_llm": {
"n_notes_queued": "{{ count }} Notiz zur Indizierung vorgemerkt",
"n_notes_queued_plural": "{{ count }} Notizen zur Indizierung vorgemerkt",
"notes_indexed": "{{ count }} Notiz indiziert",
"notes_indexed_plural": "{{ count }} Notizen indiziert",
"not_started": "Nicht gestartet",
"title": "KI Einstellungen",
"processed_notes": "Verarbeitete Notizen",
"total_notes": "Gesamt Notizen",
"progress": "Fortschritt",
"queued_notes": "Eingereihte Notizen",
"failed_notes": "Fehlgeschlagenen Notizen",
"last_processed": "Zuletzt verarbeitet",
"refresh_stats": "Statistiken neu laden",
"enable_ai_features": "Aktiviere KI/LLM Funktionen",
"enable_ai_description": "Aktiviere KI-Funktionen wie Notizzusammenfassungen, Inhaltserzeugung und andere LLM-Funktionen",
"openai_tab": "OpenAI",
"anthropic_tab": "Anthropic",
"voyage_tab": "Voyage AI",
"ollama_tab": "Ollama",
"enable_ai": "Aktiviere KI/LLM Funktionen",
"enable_ai_desc": "Aktiviere KI-Funktionen wie Notizzusammenfassungen, Inhaltserzeugung und andere LLM-Funktionen",
"provider_configuration": "KI-Anbieterkonfiguration",
"provider_precedence": "Anbieter Priorität",
"provider_precedence_description": "Komma-getrennte Liste von Anbieter in der Reihenfolge ihrer Priorität (z.B. 'openai, anthropic,ollama')",
"temperature": "Temperatur",
"temperature_description": "Regelt die Zufälligkeit in Antworten (0 = deterministisch, 2 = maximale Zufälligkeit)",
"system_prompt": "Systemaufforderung",
"system_prompt_description": "Standard Systemaufforderung für alle KI-Interaktionen",
"openai_configuration": "OpenAI Konfiguration",
"openai_settings": "OpenAI Einstellungen",
"api_key": "API Schlüssel",
"url": "Basis-URL",
"model": "Modell",
"anthropic_settings": "Anthropic Einstellungen",
"partial": "{{ percentage }}% verarbeitet",
"anthropic_api_key_description": "Dein Anthropic API-Key für den Zugriff auf Claude Modelle",
"anthropic_model_description": "Anthropic Claude Modell für Chat-Vervollständigung",
"voyage_settings": "Einstellungen für Voyage AI",
"ollama_url_description": "URL für die Ollama API (Standard: http://localhost:11434)",
"ollama_model_description": "Ollama Modell für Chat-Vervollständigung",
"anthropic_configuration": "Anthropic Konfiguration",
"voyage_configuration": "Voyage AI Konfiguration",
"voyage_url_description": "Standard: https://api.voyageai.com/v1",
"ollama_configuration": "Ollama Konfiguration",
"enable_ollama": "Aktiviere Ollama",
"enable_ollama_description": "Aktiviere Ollama für lokale KI Modell Nutzung",
"ollama_url": "Ollama URL",
"ollama_model": "Ollama Modell",
"refresh_models": "Aktualisiere Modelle",
"refreshing_models": "Aktualisiere...",
"enable_automatic_indexing": "Aktiviere automatische Indizierung",
"rebuild_index": "Index neu aufbauen",
"rebuild_index_error": "Fehler beim Neuaufbau des Index. Prüfe Log für mehr Informationen.",
"retry_failed": "Fehler: Notiz konnte nicht erneut eingereiht werden",
"max_notes_per_llm_query": "Max. Notizen je Abfrage",
"max_notes_per_llm_query_description": "Maximale Anzahl ähnlicher Notizen zum Einbinden als KI Kontext",
"active_providers": "Aktive Anbieter",
"disabled_providers": "Inaktive Anbieter",
"remove_provider": "Entferne Anbieter von Suche",
"restore_provider": "Anbieter zur Suche wiederherstellen",
"similarity_threshold": "Ähnlichkeitsschwelle",
"similarity_threshold_description": "Mindestähnlichkeitswert (0-1) für Notizen, die im Kontext für LLM-Abfragen berücksichtigt werden sollen",
"reprocess_index": "Suchindex neu erstellen",
"reprocessing_index": "Neuerstellung...",
"reprocess_index_started": "Suchindex-Optimierung wurde im Hintergrund gestartet",
"reprocess_index_error": "Fehler beim Wiederaufbau des Suchindex",
"index_rebuild_progress": "Fortschritt der Index-Neuerstellung",
"index_rebuilding": "Optimierung Index ({{percentage}}%)",
"index_rebuild_complete": "Index Optimierung abgeschlossen",
"index_rebuild_status_error": "Fehler bei Überprüfung Status Index Neuerstellung",
"never": "Niemals",
"processing": "Verarbeitung ({{percentage}}%)",
"refreshing": "Aktualisiere...",
"incomplete": "Unvollständig ({{percentage}}%)",
"complete": "Abgeschlossen (100%)",
"auto_refresh_notice": "Auto-Aktualisierung alle {{seconds}} Sekunden",
"note_queued_for_retry": "Notiz in Warteschlange für erneuten Versuch hinzugefügt",
"failed_to_retry_note": "Wiederholungsversuch fehlgeschlagen für Notiz",
"ai_settings": "KI Einstellungen",
"agent": {
"processing": "Verarbeite...",
"thinking": "Nachdenken...",
"loading": "Lade...",
"generating": "Generiere..."
},
"name": "KI",
"openai": "OpenAI",
"use_enhanced_context": "Benutze verbesserten Kontext",
"openai_api_key_description": "Dein OpenAPI-Key für den Zugriff auf den KI-Dienst",
"default_model": "Standardmodell",
"openai_model_description": "Beispiele: gpt-4o, gpt-4-turbo, gpt-3.5-turbo",
"base_url": "Basis URL",
"openai_url_description": "Standard: https://api.openai.com/v1",
"anthropic_url_description": "Basis URL für Anthropic API (Standard: https://api.anthropic.com)",
"ollama_settings": "Ollama Einstellungen",
"note_title": "Notiz Titel",
"error": "Fehler",
"last_attempt": "Letzter Versuch",
"actions": "Aktionen",
"retry": "Erneut versuchen",
"retry_queued": "Notiz für weiteren Versuch eingereiht",
"empty_key_warning": {
"anthropic": "Anthropic API-Key ist leer. Bitte gültigen API-Key eingeben.",
"openai": "OpenAI API-Key ist leer. Bitte gültigen API-Key eingeben.",
"voyage": "Voyage API-Key ist leer. Bitte gültigen API-Key eingeben.",
"ollama": "Ollama API-Key ist leer. Bitte gültigen API-Key eingeben."
},
"api_key_tooltip": "API-Key für den Zugriff auf den Dienst",
"failed_to_retry_all": "Wiederholungsversuch für Notizen fehlgeschlagen",
"all_notes_queued_for_retry": "Alle fehlgeschlagenen Notizen wurden zur Wiederholung in die Warteschlange gestellt",
"enhanced_context_description": "Versorgt die KI mit mehr Kontext aus der Notiz und den zugehörigen Notizen, um bessere Antworten zu ermöglichen",
"show_thinking": "Zeige Denkprozess",
"show_thinking_description": "Zeige den Denkprozess der KI",
"enter_message": "Geben Sie Ihre Nachricht ein...",
"error_contacting_provider": "Fehler beim Kontaktieren des KI-Anbieters. Bitte überprüfe die Einstellungen und die Internetverbindung.",
"error_generating_response": "Fehler beim Generieren der KI Antwort",
"index_all_notes": "Indiziere alle Notizen",
"index_status": "Indizierungsstatus",
"indexed_notes": "Indizierte Notizen",
"indexing_stopped": "Indizierung gestoppt",
"indexing_in_progress": "Indizierung in Bearbeitung...",
"last_indexed": "Zuletzt Indiziert",
"note_chat": "Notizen-Chat",
"sources": "Quellen",
"start_indexing": "Starte Indizierung",
"use_advanced_context": "Benutze erweiterten Kontext",
"ollama_no_url": "Ollama ist nicht konfiguriert. Bitte trage eine gültige URL ein.",
"chat": {
"root_note_title": "KI Chats",
"root_note_content": "Diese Notiz enthält gespeicherte KI-Chat-Unterhaltungen.",
"new_chat_title": "Neuer Chat",
"create_new_ai_chat": "Erstelle neuen KI Chat"
},
"create_new_ai_chat": "Erstelle neuen KI Chat",
"configuration_warnings": "Es wurden Probleme mit der KI Konfiguration festgestellt. Bitte überprüfe die Einstellungen.",
"experimental_warning": "Die LLM-Funktionen sind aktuell experimentell - sei an dieser Stelle gewarnt.",
"selected_provider": "Ausgewählter Anbieter",
"selected_provider_description": "Wähle einen KI-Anbieter für Chat- und Vervollständigungsfunktionen",
"select_model": "Wähle Modell...",
"select_provider": "Wähle Anbieter...",
"ai_enabled": "KI Funktionen aktiviert",
"ai_disabled": "KI Funktionen deaktiviert",
"no_models_found_online": "Keine Modelle gefunden. Bitte überprüfe den API-Key und die Einstellungen.",
"no_models_found_ollama": "Kein Ollama Modell gefunden. Bitte prüfe, ob Ollama gerade läuft.",
"error_fetching": "Fehler beim Abrufen der Modelle: {{error}}"
},
"zen_mode": {
"button_exit": "Verlasse Zen Modus"
},
"ui-performance": {
"title": "Leistung",
"enable-motion": "Aktiviere Übergänge und Animationen",
"enable-shadows": "Aktiviere Schatten",
"enable-backdrop-effects": "Aktiviere Hintergrundeffekte für Menüs, Pop-up Fenster und Panele"
},
"code-editor-options": {
"title": "Editor"
},
"custom_date_time_format": {
"title": "Benutzerdefiniertes Datums-/Zeitformat",
"description": "Passe das Format des Datums und der Uhrzeit an, die über <shortcut /> oder die Symbolleiste eingefügt werden. Die verfügbaren Format-Tokens sind unter <doc>Day.js docs</doc> zu finden.",
"format_string": "Format Zeichenfolge:",
"formatted_time": "Formatiertes Datum/Uhrzeit:"
},
"multi_factor_authentication": {
"title": "Multi-Faktor-Authentifizierung",
"description": "Die Multi-Faktor-Authentifizierung (MFA) bietet Ihrem Konto eine zusätzliche Sicherheitsebene. Anstatt sich lediglich mit einem Passwort anzumelden, müssen bei der MFA ein oder mehrere zusätzliche Nachweise erbracht werden, um die Identität zu bestätigen. Auf diese Weise kann selbst bei Bekanntwerden des Passworts, ohne die zweite Information nicht auf Ihr Konto zugegriffen werden. Das ist so, als würden Sie ein zusätzliches Schloss an einer Tür anbringen, wodurch es für andere viel schwieriger wird, einzubrechen.<br><br>Befolgen Sie bitte die nachstehenden Anweisungen, um MFA zu aktivieren. Wenn Sie die Konfiguration nicht korrekt vornehmen, erfolgt die Anmeldung weiterhin nur mit dem Passwort.",
"mfa_enabled": "Aktiviere Multi-Faktor-Authentifizierung",
"mfa_method": "MFA Methode",
"electron_disabled": "Multi-Faktor-Authentifizierung wird aktuell nicht in der Desktop-Version unterstützt.",
"totp_title": "Zeitbasiertes Einmalpasswort (TOTP)",
"totp_description": "TOTP (Zeitbasiertes Einmalpasswort) ist eine Sicherheitsfunktion, die einen einzigartigen, temporären Code generiert, der sich alle 30 Sekunden ändert. Sie verwenden diesen Code zusammen mit Ihrem Passwort, um sich bei Ihrem Konto anzumelden, wodurch es für andere Personen wesentlich schwieriger wird, darauf unbefugt zuzugreifen.",
"totp_secret_title": "Generiere TOTP Geheimnis",
"totp_secret_generate": "Generiere TOTP Geheimnis",
"totp_secret_regenerate": "TOTP-Geheimnis neu generieren",
"no_totp_secret_warning": "Um TOTP zu aktivieren, muss zunächst ein TOTP Geheimnis generiert werden.",
"totp_secret_description_warning": "Nach der Generierung des TOTP Geheimnisses ist eine Neuanmeldung mit dem TOTP Geheimnis erforderlich.",
"totp_secret_generated": "TOTP Geheimnis generiert",
"totp_secret_warning": "Bitte speichere das TOTP Geheimnis an einem sicheren Ort. Es wird nicht noch einmal angezeigt.",
"totp_secret_regenerate_confirm": "Möchten Sie das TOTP-Geheimnis wirklich neu generieren? Dadurch werden das bisherige TOTP-Geheimnis und alle vorhandenen Wiederherstellungscodes ungültig.",
"recovery_keys_title": "Einmalige Wiederherstellungsschlüssel",
"recovery_keys_description": "Einmalige Wiederherstellungsschlüssel werden verwendet, um sich anzumelden, falls Sie keinen Zugriff auf Ihre Authentifizierungscodes haben.",
"recovery_keys_description_warning": "Wiederherstellungsschlüssel werden nach dem Verlassen der Seite nicht erneut angezeigt. Bewahren Sie sie an einem sicheren Ort auf.<br>Wiederherstellungsschlüssel können nach ihrer Verwendung nicht erneut verwendet werden.",
"recovery_keys_error": "Fehler beim Generieren der Wiederherstellungscodes",
"recovery_keys_no_key_set": "Keine Wiederherstellungscodes eingerichtet",
"recovery_keys_generate": "Generiere Wiederherstellungscodes",
"recovery_keys_regenerate": "Wiederherstellungscodes erneut generieren",
"recovery_keys_used": "Verwendet: {{date}}",
"recovery_keys_unused": "Wiederherstellungscode {{index}} ist unbenutzt",
"oauth_title": "OAuth/OpenID",
"oauth_description": "OpenID ist ein standardisiertes Verfahren, mit dem Sie sich über ein Konto eines anderen Dienstes, beispielsweise Google, bei Websites anmelden können, um Ihre Identität zu bestätigen. Der Standardaussteller ist Google, Sie können jedoch jeden anderen OpenID-Anbieter auswählen. Weitere Informationen finden Sie <a href=\"#root/_hidden/_help/_help_Otzi9La2YAUX/_help_WOcw2SLH6tbX/_help_7DAiwaf8Z7Rz\">hier</a>. Befolgen Sie diese <a href=\"https://developers.google.com/identity/openid-connect/openid-connect\">Anweisungen</a>, um einen OpenID-Dienst über Google einzurichten.",
"oauth_description_warning": "Um OAuth/OpenID zu aktivieren, müssen Sie die OAuth/OpenID-Basis-URL, die Client-ID und den Client-Secret in der Datei config.ini festlegen und die Anwendung neu starten. Wenn Sie die Einstellungen über Umgebungsvariablen vornehmen möchten, legen Sie bitte TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID und TRILIUM_OAUTH_CLIENT_SECRET fest.",
"oauth_missing_vars": "Fehlende Einstellung: {{variables}}",
"oauth_user_account": "Benutzerkonto: ",
"oauth_user_email": "Benutzer E-Mail: ",
"oauth_user_not_logged_in": "Nicht eingeloggt!"
},
"share": {
"title": "Freigabeeinstellungen",
"redirect_bare_domain": "Hauptdomain zur Freigabeseite weiterleiten",
"redirect_bare_domain_description": "Anonyme Benutzer zur Freigabeseite weiterleiten, anstatt die Anmeldung anzuzeigen",
"show_login_link": "Zeige Anmeldelink im Design der Freigabeseite",
"show_login_link_description": "Füge einen Anmeldelink in der Fußzeile der Freigabeseite hinzu",
"check_share_root": "Status des Freigabe-Roots prüfen",
"share_root_found": "Freigabe-Root-Notiz '{{noteTitle}}' ist bereit",
"share_root_not_found": "Keine Notiz mit #shareRoot Label gefunden",
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht geteilt"
},
"tasks": {
"due": {
"today": "Heute",
"tomorrow": "Morgen",
"yesterday": "Gestern"
}
},
"content_widget": {
"unknown_widget": "Unbekanntes Widget für '{{id}}'."
},
"note_language": {
"not_set": "Nicht gesetzt",
"configure-languages": "Konfiguriere Sprachen..."
},
"content_language": {
"title": "Inhaltssprachen",
"description": "Wähle eine oder mehrere Sprachen aus, die in der Sprachauswahl im Abschnitt „Grundlegende Eigenschaften“ einer schreibgeschützten oder bearbeitbaren Textnotiz angezeigt werden sollen. Dadurch stehen Funktionen wie Rechtschreibprüfung oder Unterstützung für Rechts-nach-Links-Sprachen zur Verfügung."
},
"switch_layout_button": {
"title_vertical": "Bearbeitungsbereich nach unten verschieben",
"title_horizontal": "Bearbeitungsbereich nach links verschieben"
},
"toggle_read_only_button": {
"unlock-editing": "Bearbeitung freischalten",
"lock-editing": "Bearbeitung sperren"
},
"png_export_button": {
"button_title": "Exportiere Diagramm als PNG"
},
"svg": {
"export_to_png": "Das Diagramm konnte als PNG nicht exportiert werden."
},
"code_theme": {
"title": "Aussehen",
"word_wrapping": "Zeilenumbruch",
"color-scheme": "Farbschema"
},
"cpu_arch_warning": {
"title": "Bitte lade die ARM64-Version herunter",
"message_macos": "TriliumNext läuft aktuell über Rosetta 2. Nutzen Sie die Intel-Version (x64) auf einem Apple-Silicon-Mac, wird dadurch die Leistung und Akkulaufzeit deutlich beeinträchtigt.",
"message_windows": "TriliumNext läuft momentan in einer Emulation. Verwenden Sie eine Intel-Version (x64) auf einem Windows ARM Gerät, kann dadurch die Leistung und Akkulaufzeit deutlich beeinträchtigt werden.",
"recommendation": "Für ein optimales Erlebnis lade bitte die native ARM64-Version von TriliumNext von unserer Release-Seite herunter.",
"download_link": "Lade native Version herunter",
"continue_anyway": "Trotzdem fortfahren",
"dont_show_again": "Zeige diese Warnung nicht erneut"
},
"editorfeatures": {
"title": "Funktionen",
"emoji_completion_enabled": "Emoji-Autovervollständigung aktivieren",
"note_completion_enabled": "Automatisches Vervollständigen von Notizen aktivieren"
},
"table_view": {
"new-row": "Neue Zeile",
"new-column": "Neue Spalte",
"sort-column-by": "Sortiere nach '{{title}}'",
"sort-column-ascending": "Aufsteigend",
"sort-column-descending": "Absteigend",
"sort-column-clear": "Sortierung zurücksetzen",
"hide-column": "Spalte '{{title}}' ausblenden",
"show-hide-columns": "Zeige/verberge Spalten",
"row-insert-above": "Zeile oberhalb einfügen",
"row-insert-below": "Zeile unterhalb einfügen",
"row-insert-child": "Unternotiz einfügen",
"add-column-to-the-left": "Spalte links einfügen",
"add-column-to-the-right": "Spalte rechts einfügen",
"edit-column": "Spalte editieren",
"delete_column_confirmation": "Soll diese Spalte wirklich gelöscht werden? Das entsprechende Attribut wird aus allen Notizen entfernt.",
"delete-column": "Spalte entfernen",
"new-column-label": "Label",
"new-column-relation": "Beziehung"
},
"book_properties_config": {
"hide-weekends": "Wochenenden ausblenden",
"display-week-numbers": "Zeige Kalenderwoche",
"map-style": "Kartenstil:",
"max-nesting-depth": "Maximale Verschachtelungstiefe:",
"raster": "Raster",
"vector_light": "Vektor (Hell)",
"vector_dark": "Vektor (Dunkel)",
"show-scale": "Zeige Skalierung"
},
"table_context_menu": {
"delete_row": "Zeile entfernen"
},
"board_view": {
"delete-note": "Lösche Notiz",
"move-to": "Verschiebe zu",
"insert-above": "Oberhalb einfügen",
"insert-below": "Unterhalb einfügen",
"delete-column": "Spalte entfernen",
"delete-column-confirmation": "Soll die Spalte wirklich gelöscht werden? Abhängige Attribute werden auch in den Notizen unter dieser Spalte gelöscht.",
"new-item": "Neuer Artikel",
"add-column": "Spalte hinzufügen"
},
"command_palette": {
"tree-action-name": "Struktur: {{name}}",
"export_note_title": "Notiz exportieren",
"export_note_description": "aktuelle Notiz exportieren",
"show_attachments_title": "Zeige Anhänge",
"show_attachments_description": "Notizanhänge anzeigen",
"search_notes_title": "Suche Notiz",
"search_notes_description": "Öffne erweiterte Suche",
"search_subtree_title": "Im Unterzweig suchen",
"search_subtree_description": "Im aktuellen Unterzweig suchen",
"search_history_title": "Zeige Suchhistorie",
"search_history_description": "Zeige vorherige Suchen",
"configure_launch_bar_title": "Startleiste anpassen",
"configure_launch_bar_description": "Öffnen Sie die Einstellungen der Startleiste, um Elemente hinzuzufügen oder zu entfernen."
},
"content_renderer": {
"open_externally": "Öffne extern"
},
"call_to_action": {
"next_theme_title": "Teste das neue Trilium Design",
"next_theme_message": "Es wird aktuell das alte Design verwendet. Möchten Sie das neue Design ausprobieren?",
"next_theme_button": "Teste das neue Design",
"background_effects_title": "Hintergrundeffekte sind jetzt zuverlässig nutzbar",
"background_effects_message": "Auf Windows-Geräten sind die Hintergrundeffekte nun vollständig stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird. Diese Technik wird auch in anderen Anwendungen wie dem Windows-Explorer eingesetzt.",
"background_effects_button": "Aktiviere Hintergrundeffekte",
"dismiss": "Ablehnen"
},
"settings": {
"related_settings": "Ähnliche Einstellungen"
},
"settings_appearance": {
"related_code_blocks": "Farbschema für Code-Blöcke in Textnotizen",
"related_code_notes": "Farbschema für Code-Notizen"
},
"units": {
"percentage": "%"
"close": "Schließen"
}
}

View File

@@ -263,11 +263,6 @@
"confirm_delete_all": "Do you want to delete all revisions of this note?",
"no_revisions": "No revisions for this note yet...",
"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.",
"delete_button": "Delete",
"confirm_delete": "Do you want to delete this revision?",
@@ -737,8 +732,7 @@
"note_type": "Note type",
"editable": "Editable",
"basic_properties": "Basic Properties",
"language": "Language",
"configure_code_notes": "Configure code notes..."
"language": "Language"
},
"book_properties": {
"view_type": "View type",
@@ -854,7 +848,7 @@
"debug": "debug",
"debug_description": "Debug will print extra debugging information into the console to aid in debugging complex queries",
"action": "action",
"search_button": "Search",
"search_button": "Search <kbd>enter</kbd>",
"search_execute": "Search & Execute actions",
"save_to_note": "Save to note",
"search_parameters": "Search Parameters",
@@ -1119,14 +1113,6 @@
"layout-vertical-description": "launcher bar is on the left (default)",
"layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width."
},
"ui-performance": {
"title": "Performance",
"enable-motion": "Enable transitions and animations",
"enable-shadows": "Enable shadows",
"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": {
"not_started": "Not started",
"title": "AI Settings",
@@ -1609,8 +1595,8 @@
"open-in-popup": "Quick edit"
},
"shared_info": {
"shared_publicly": "This note is shared publicly on {{- link}}.",
"shared_locally": "This note is shared locally on {{- link}}.",
"shared_publicly": "This note is shared publicly on",
"shared_locally": "This note is shared locally on",
"help_link": "For help visit <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@@ -1689,8 +1675,7 @@
"hoist-this-note-workspace": "Hoist this note (workspace)",
"refresh-saved-search-results": "Refresh saved search results",
"create-child-note": "Create child note",
"unhoist": "Unhoist",
"toggle-sidebar": "Toggle sidebar"
"unhoist": "Unhoist"
},
"title_bar_buttons": {
"window-on-top": "Keep Window on Top"

View File

@@ -732,8 +732,7 @@
"note_type": "Tipo de nota",
"editable": "Editable",
"basic_properties": "Propiedades básicas",
"language": "Idioma",
"configure_code_notes": "Configurar notas de código..."
"language": "Idioma"
},
"book_properties": {
"view_type": "Tipo de vista",
@@ -849,7 +848,7 @@
"debug": "depurar",
"debug_description": "La depuración imprimirá información de depuración adicional en la consola para ayudar a depurar consultas complejas",
"action": "acción",
"search_button": "Buscar",
"search_button": "Buscar <kbd>Enter</kbd>",
"search_execute": "Buscar y ejecutar acciones",
"save_to_note": "Guardar en nota",
"search_parameters": "Parámetros de búsqueda",
@@ -1254,12 +1253,7 @@
"selected_provider": "Proveedor seleccionado",
"selected_provider_description": "Elija el proveedor de IA para el chat y características de completado",
"select_model": "Seleccionar modelo...",
"select_provider": "Seleccionar proveedor...",
"ai_enabled": "Características de IA activadas",
"ai_disabled": "Características de IA desactivadas",
"no_models_found_online": "No se encontraron modelos. Por favor, comprueba tu clave de API y la configuración.",
"no_models_found_ollama": "No se encontraron modelos de Ollama. Por favor, comprueba si Ollama se está ejecutando.",
"error_fetching": "Error al obtener los modelos: {{error}}"
"select_provider": "Seleccionar proveedor..."
},
"zoom_factor": {
"title": "Factor de zoom (solo versión de escritorio)",
@@ -1596,8 +1590,8 @@
"open-in-popup": "Edición rápida"
},
"shared_info": {
"shared_publicly": "Esta nota está compartida públicamente en {{- link}}",
"shared_locally": "Esta nota está compartida localmente en {{- link}}",
"shared_publicly": "Esta nota está compartida públicamente en",
"shared_locally": "Esta nota está compartida localmente en",
"help_link": "Para obtener ayuda visite <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@@ -2007,21 +2001,5 @@
"background_effects_message": "En los dispositivos Windows, los efectos de fondo ya son totalmente estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás. Esta técnica también se utiliza en otras aplicaciones como el Explorador de Windows.",
"background_effects_button": "Activar efectos de fondo",
"dismiss": "Desestimar"
},
"ui-performance": {
"title": "Rendimiento",
"enable-motion": "Habilitar transiciones y animaciones",
"enable-shadows": "Activar sombras",
"enable-backdrop-effects": "Habilitar efectos de fondo para menús, ventanas emergentes y paneles"
},
"settings": {
"related_settings": "Configuración relacionada"
},
"settings_appearance": {
"related_code_blocks": "Esquema de colores para bloques de código en notas de texto",
"related_code_notes": "Esquema de colores para notas de código"
},
"units": {
"percentage": "%"
}
}

View File

@@ -848,7 +848,7 @@
"debug": "debug",
"debug_description": "Debug imprimera des informations supplémentaires dans la console pour faciliter le débogage des requêtes complexes",
"action": "action",
"search_button": "Recherche",
"search_button": "Recherche <kbd>Entrée</kbd>",
"search_execute": "Rechercher et exécuter des actions",
"save_to_note": "Enregistrer dans la note",
"search_parameters": "Paramètres de recherche",
@@ -1389,8 +1389,8 @@
"convert-to-attachment-confirm": "Êtes-vous sûr de vouloir convertir les notes sélectionnées en pièces jointes de leurs notes parentes ?"
},
"shared_info": {
"shared_publicly": "Cette note est partagée publiquement sur {{- link}}",
"shared_locally": "Cette note est partagée localement sur {{- link}}",
"shared_publicly": "Cette note est partagée publiquement sur",
"shared_locally": "Cette note est partagée localement sur",
"help_link": "Pour obtenir de l'aide, visitez le <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@@ -1680,16 +1680,6 @@
"n_notes_queued_2": "",
"notes_indexed_0": "{{ count }} note indexée",
"notes_indexed_1": "{{ count }} notes indexées",
"notes_indexed_2": "",
"anthropic_url_description": "URL de base pour l'API Anthropic (par défaut : https ://api.anthropic.com)",
"anthropic_model_description": "Modèles Anthropic Claude pour la complétion",
"voyage_settings": "Réglages d'IA Voyage",
"ollama_settings": "Réglages Ollama",
"ollama_url_description": "URL pour l'API Ollama (par défaut: http://localhost:11434)",
"ollama_model_description": "Model Ollama utilisé pour la complétion",
"anthropic_configuration": "Configuration Anthropic",
"voyage_configuration": "Configuration IA Voyage",
"voyage_url_description": "Défaut: https://api.voyageai.com/v1",
"ollama_configuration": "Configuration Ollama"
"notes_indexed_2": ""
}
}

View File

@@ -1,353 +1,345 @@
{
"about": {
"app_version": "Versione dell'app:",
"db_version": "Versione DB:",
"sync_version": "Versione Sync:",
"data_directory": "Cartella dati:",
"title": "Informazioni su Trilium Notes",
"build_date": "Data della build:",
"build_revision": "Revisione della build:",
"homepage": "Homepage:"
},
"toast": {
"critical-error": {
"title": "Errore critico",
"message": "Si è verificato un errore critico che impedisce l'avvio dell'applicazione client:\n\n{{message}}\n\nQuesto è probabilmente causato da un errore di script inaspettato. Prova a avviare l'applicazione in modo sicuro e controlla il problema."
"about": {
"app_version": "Versione dell'app:",
"db_version": "Versione DB:",
"sync_version": "Versione Sync:",
"data_directory": "Cartella dati:",
"title": "Informazioni su Trilium Notes",
"build_date": "Data della build:",
"build_revision": "Revisione della build:",
"homepage": "Homepage:"
},
"bundle-error": {
"title": "Non si è riusciti a caricare uno script personalizzato",
"message": "Lo script della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}"
"toast": {
"critical-error": {
"title": "Errore critico",
"message": "Si è verificato un errore critico che impedisce l'avvio dell'applicazione client:\n\n{{message}}\n\nQuesto è probabilmente causato da un errore di script inaspettato. Prova a avviare l'applicazione in modo sicuro e controlla il problema."
},
"bundle-error": {
"title": "Non si è riusciti a caricare uno script personalizzato",
"message": "Lo script della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}"
},
"widget-error": {
"title": "Impossibile inizializzare un widget",
"message-custom": "Il widget personalizzato della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}",
"message-unknown": "Un widget sconosciuto non è stato inizializzato a causa di:\n\n{{message}}"
}
},
"widget-error": {
"title": "Impossibile inizializzare un widget",
"message-custom": "Il widget personalizzato della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}",
"message-unknown": "Un widget sconosciuto non è stato inizializzato a causa di:\n\n{{message}}"
"add_link": {
"add_link": "Aggiungi un collegamento",
"note": "Nota",
"search_note": "cerca una nota per nome",
"link_title_mirrors": "il titolo del collegamento rispecchia il titolo della nota corrente",
"link_title_arbitrary": "il titolo del collegamento può essere modificato arbitrariamente",
"link_title": "Titolo del collegamento",
"button_add_link": "Aggiungi il collegamento <kbd>invio</kbd>",
"help_on_links": "Aiuto sui collegamenti"
},
"branch_prefix": {
"edit_branch_prefix": "Modifica il prefisso del ramo",
"help_on_tree_prefix": "Aiuto sui prefissi dell'Albero",
"prefix": "Prefisso: ",
"save": "Salva",
"branch_prefix_saved": "Il prefisso del ramo è stato salvato."
},
"bulk_actions": {
"bulk_actions": "Azioni massive",
"affected_notes": "Note influenzate",
"include_descendants": "Includi i discendenti della nota selezionata",
"available_actions": "Azioni disponibili",
"chosen_actions": "Azioni scelte",
"execute_bulk_actions": "Esegui le azioni massive",
"bulk_actions_executed": "Le azioni massive sono state eseguite con successo.",
"none_yet": "Ancora nessuna... aggiungi una azione cliccando su una di quelle disponibili sopra.",
"labels": "Etichette",
"relations": "Relazioni",
"notes": "Note",
"other": "Altro"
},
"clone_to": {
"clone_notes_to": "Clona note in...",
"help_on_links": "Aiuto sui collegamenti",
"notes_to_clone": "Note da clonare",
"target_parent_note": "Nodo padre obiettivo",
"search_for_note_by_its_name": "cerca una nota per nome",
"cloned_note_prefix_title": "Le note clonate saranno mostrate nell'albero delle note con il dato prefisso",
"prefix_optional": "Prefisso (opzionale)",
"clone_to_selected_note": "Clona sotto la nota selezionata <kbd>invio</kbd>",
"no_path_to_clone_to": "Nessun percorso per clonare dentro.",
"note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\""
},
"confirm": {
"cancel": "Annulla",
"ok": "OK",
"confirmation": "Conferma",
"are_you_sure_remove_note": "Sei sicuro di voler rimuovere la nota \"{{title}}\" dalla mappa delle relazioni? ",
"if_you_dont_check": "Se non lo selezioni, la nota sarà rimossa solamente dalla mappa delle relazioni.",
"also_delete_note": "Rimuove anche la nota"
},
"delete_notes": {
"ok": "OK",
"close": "Chiudi",
"delete_notes_preview": "Anteprima di eliminazione delle note",
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere disfatto tramite i cambiamenti recenti)",
"erase_notes_description": "L'eliminazione normale (soft) marca le note come eliminate e potranno essere recuperate entro un certo lasso di tempo (dalla finestra dei cambiamenti recenti). Selezionando questa opzione le note si elimineranno immediatamente e non sarà possibile recuperarle.",
"erase_notes_warning": "Elimina le note in modo permanente (non potrà essere disfatto), compresi tutti i cloni. Ciò forzerà un nuovo caricamento dell'applicazione.",
"cancel": "Annulla",
"notes_to_be_deleted": "Le seguenti note saranno eliminate ({{- noteCount}})",
"no_note_to_delete": "Nessuna nota sarà eliminata (solo i cloni).",
"broken_relations_to_be_deleted": "Le seguenti relazioni saranno interrotte ed eliminate ({{- relationCount}})",
"deleted_relation_text": "La nota {{- note}} (da eliminare) è referenziata dalla relazione {{- relation}} originata da {{- source}}."
},
"info": {
"okButton": "OK",
"closeButton": "Chiudi"
},
"export": {
"close": "Chiudi",
"export_note_title": "Esporta la nota",
"export_status": "Stato dell'esportazione",
"export": "Esporta",
"choose_export_type": "Scegli prima il tipo di esportazione, per favore",
"export_in_progress": "Esportazione in corso: {{progressCount}}",
"export_finished_successfully": "Esportazione terminata con successo.",
"format_pdf": "PDF- allo scopo di stampa o esportazione.",
"export_type_subtree": "Questa nota e tutti i suoi discendenti",
"format_html": "HTML - raccomandato in quanto mantiene tutti i formati",
"format_html_zip": "HTML in archivio ZIP - questo è raccomandato in quanto conserva tutta la formattazione.",
"format_markdown": "MArkdown - questo conserva la maggior parte della formattazione."
},
"password_not_set": {
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
"body2": "Per proteggere le note, fare clic su <a class=\"open-password-options-button\" href=\"javascript:\">qui</a> per aprire la finestra di dialogo Opzioni e impostare la password."
},
"protected_session_password": {
"close_label": "Chiudi"
},
"abstract_bulk_action": {
"remove_this_search_action": "Rimuovi questa azione di ricerca"
},
"etapi": {
"new_token_title": "Nuovo token ETAPI",
"new_token_message": "Inserire il nuovo nome del token"
},
"electron_integration": {
"zoom-factor": "Fattore di ingrandimento",
"desktop-application": "Applicazione Desktop"
},
"note_autocomplete": {
"search-for": "Cerca \"{{term}}\"",
"create-note": "Crea e collega la nota figlia \"{{term}}\"",
"insert-external-link": "Inserisci il collegamento esterno a \"{{term}}\"",
"clear-text-field": "Pulisci il campo di testo",
"show-recent-notes": "Mostra le note recenti",
"full-text-search": "Ricerca full text"
},
"note_tooltip": {
"note-has-been-deleted": "La nota è stata eliminata.",
"quick-edit": "Modifica veloce"
},
"geo-map": {
"create-child-note-title": "Crea una nota figlia e aggiungila alla mappa",
"create-child-note-instruction": "Clicca sulla mappa per creare una nuova nota qui o premi Escape per uscire.",
"unable-to-load-map": "Impossibile caricare la mappa."
},
"geo-map-context": {
"open-location": "Apri la posizione",
"remove-from-map": "Rimuovi dalla mappa",
"add-note": "Aggiungi un marcatore in questa posizione"
},
"debug": {
"debug": "Debug"
},
"database_anonymization": {
"light_anonymization": "Anonimizzazione parziale",
"title": "Anonimizzazione del Database",
"full_anonymization": "Anonimizzazione completa",
"full_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà (rimuove tutti i contenuti delle note, lasciando solo la struttura e qualche metadato non sensibile) per condividerlo online allo scopo di debugging, senza paura di far trapelare i tuoi dati personali.",
"save_fully_anonymized_database": "Salva il database completamente anonimizzato",
"light_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà in parzialmente — in particolare, solo il contenuto delle note sarà rimosso, ma i titoli e gli attributi rimarranno. Inoltre, note con script personalizzati JS di frontend/backend e widget personalizzati lasciando rimarranno. Ciò mette a disposizione più contesto per il debug dei problemi.",
"choose_anonymization": "Puoi decidere da solo se fornire un database completamente o parzialmente anonimizzato. Anche un database completamente anonimizzato è molto utile, sebbene in alcuni casi i database parzialmente anonimizzati possono accelerare il processo di identificazione dei bug e la loro correzione.",
"no_anonymized_database_yet": "Nessun database ancora anonimizzato.",
"save_lightly_anonymized_database": "Salva il database parzialmente anonimizzato",
"successfully_created_fully_anonymized_database": "Database completamente anonimizzato creato in {{anonymizedFilePath}}",
"successfully_created_lightly_anonymized_database": "Database parzialmente anonimizzato creato in {{anonymizedFilePath}}"
},
"cpu_arch_warning": {
"title": "Per favore scarica la versione ARM64",
"continue_anyway": "Continua Comunque",
"dont_show_again": "Non mostrare più questo avviso",
"download_link": "Scarica la Versione Nativa"
},
"editorfeatures": {
"title": "Caratteristiche",
"emoji_completion_enabled": "Abilita il completamento automatico delle Emoji",
"note_completion_enabled": "Abilita il completamento automatico delle note"
},
"table_view": {
"new-row": "Nuova riga",
"new-column": "Nuova colonna",
"sort-column-by": "Ordina per \"{{title}}\"",
"sort-column-ascending": "Ascendente",
"sort-column-descending": "Discendente",
"sort-column-clear": "Cancella l'ordinamento",
"hide-column": "Nascondi la colonna \"{{title}}\"",
"show-hide-columns": "Mostra/nascondi le colonne",
"row-insert-above": "Inserisci una riga sopra",
"row-insert-below": "Inserisci una riga sotto"
},
"abstract_search_option": {
"remove_this_search_option": "Rimuovi questa opzione di ricerca",
"failed_rendering": "Opzione di ricerca di rendering non riuscita: {{dto}} con errore: {{error}} {{stack}}"
},
"ancestor": {
"label": "Antenato"
},
"add_label": {
"add_label": "Aggiungi etichetta",
"label_name_placeholder": "nome dell'etichetta",
"new_value_placeholder": "nuovo valore",
"to_value": "al valore"
},
"update_label_value": {
"to_value": "al valore",
"label_name_placeholder": "nome dell'etichetta"
},
"delete_label": {
"delete_label": "Elimina etichetta",
"label_name_placeholder": "nome dell'etichetta",
"label_name_title": "Sono ammessi i caratteri alfanumerici, il carattere di sottolineato e i due punti."
},
"tree-context-menu": {
"move-to": "Muovi in...",
"cut": "Taglia"
},
"electron_context_menu": {
"cut": "Taglia",
"copy": "Copia",
"paste": "Incolla",
"copy-link": "Copia collegamento",
"paste-as-plain-text": "Incolla come testo semplice"
},
"editing": {
"editor_type": {
"multiline-toolbar": "Mostra la barra degli strumenti su più linee se non entra."
}
},
"edit_button": {
"edit_this_note": "Modifica questa nota"
},
"shortcuts": {
"shortcuts": "Scorciatoie"
},
"shared_switch": {
"toggle-on-title": "Condividi la nota",
"toggle-off-title": "Non condividere la nota"
},
"search_string": {
"search_prefix": "Cerca:"
},
"attachment_detail": {
"open_help_page": "Apri la pagina di aiuto sugli allegati"
},
"search_definition": {
"ancestor": "antenato",
"debug": "debug",
"action": "azione",
"add_search_option": "Aggiungi un opzione di ricerca:",
"search_string": "cerca la stringa",
"limit": "limite"
},
"modal": {
"close": "Chiudi"
},
"board_view": {
"insert-below": "Inserisci sotto",
"delete-column": "Elimina la colonna",
"delete-column-confirmation": "Sei sicuro di vole eliminare questa colonna? Il corrispondente attributo sarà eliminato anche nelle note sotto questa colonna."
},
"backup": {
"enable_weekly_backup": "Abilita le archiviazioni settimanali",
"enable_monthly_backup": "Abilita le archiviazioni mensili",
"backup_recommendation": "Si raccomanda di mantenere attive le archiviazioni, sebbene ciò possa rendere l'avvio dell'applicazione lento con database grandi e/o dispositivi di archiviazione lenti.",
"backup_now": "Archivia adesso",
"backup_database_now": "Archivia il database adesso",
"existing_backups": "Backup esistenti",
"date-and-time": "Data e ora",
"path": "Percorso",
"database_backed_up_to": "Il database è stato archiviato in {{backupFilePath}}",
"enable_daily_backup": "Abilita le archiviazioni giornaliere",
"no_backup_yet": "Ancora nessuna archiviazione"
},
"backend_log": {
"refresh": "Aggiorna"
},
"consistency_checks": {
"find_and_fix_button": "Trova e correggi i problemi di coerenza",
"finding_and_fixing_message": "In cerca e correzione dei problemi di coerenza...",
"issues_fixed_message": "Qualsiasi problema di coerenza che possa essere stato trovato ora è corretto."
},
"database_integrity_check": {
"check_button": "Controllo dell'integrità del database",
"checking_integrity": "Controllo dell'integrità del database in corso...",
"title": "Controllo di Integrità del database",
"description": "Controllerà che il database non sia corrotto a livello SQLite. Può durare un po' di tempo, a seconda della grandezza del DB.",
"integrity_check_failed": "Controllo di integrità fallito: {{results}}"
},
"sync": {
"title": "Sincronizza",
"force_full_sync_button": "Forza una sincronizzazione completa",
"failed": "Sincronizzazione fallita: {{message}}"
},
"sync_2": {
"config_title": "Configurazione per la Sincronizzazione",
"proxy_label": "Server Proxy per la sincronizzazione (opzionale)",
"test_title": "Test di sincronizzazione",
"timeout": "Timeout per la sincronizzazione",
"timeout_unit": "millisecondi",
"save": "Salva",
"help": "Aiuto"
},
"search_engine": {
"save_button": "Salva"
},
"sql_table_schemas": {
"tables": "Tabelle"
},
"tab_row": {
"close_tab": "Chiudi la scheda",
"add_new_tab": "Aggiungi una nuova scheda",
"close": "Chiudi",
"close_other_tabs": "Chiudi le altre schede",
"close_right_tabs": "Chiudi le schede a destra",
"close_all_tabs": "Chiudi tutte le schede",
"reopen_last_tab": "Riapri l'ultima scheda chiusa",
"move_tab_to_new_window": "Sposta questa scheda in una nuova finestra",
"copy_tab_to_new_window": "Copia questa scheda in una nuova finestra",
"new_tab": "Nuova scheda"
},
"toc": {
"table_of_contents": "Sommario"
},
"table_of_contents": {
"title": "Sommario"
},
"tray": {
"title": "Vassoio di Sistema",
"enable_tray": "Abilita il vassoio (Trilium necessita di essere riavviato affinché la modifica abbia effetto)"
},
"heading_style": {
"title": "Stile dell'Intestazione",
"plain": "Normale",
"underline": "Sottolineato",
"markdown": "Stile Markdown"
},
"highlights_list": {
"title": "Punti salienti"
},
"highlights_list_2": {
"title": "Punti salienti",
"options": "Opzioni"
},
"quick-search": {
"placeholder": "Ricerca rapida",
"searching": "Ricerca in corso..."
}
},
"add_link": {
"add_link": "Aggiungi un collegamento",
"note": "Nota",
"search_note": "cerca una nota per nome",
"link_title_mirrors": "il titolo del collegamento rispecchia il titolo della nota corrente",
"link_title_arbitrary": "il titolo del collegamento può essere modificato arbitrariamente",
"link_title": "Titolo del collegamento",
"button_add_link": "Aggiungi il collegamento <kbd>invio</kbd>",
"help_on_links": "Aiuto sui collegamenti"
},
"branch_prefix": {
"edit_branch_prefix": "Modifica il prefisso del ramo",
"help_on_tree_prefix": "Aiuto sui prefissi dell'Albero",
"prefix": "Prefisso: ",
"save": "Salva",
"branch_prefix_saved": "Il prefisso del ramo è stato salvato."
},
"bulk_actions": {
"bulk_actions": "Azioni massive",
"affected_notes": "Note influenzate",
"include_descendants": "Includi i discendenti della nota selezionata",
"available_actions": "Azioni disponibili",
"chosen_actions": "Azioni scelte",
"execute_bulk_actions": "Esegui le azioni massive",
"bulk_actions_executed": "Le azioni massive sono state eseguite con successo.",
"none_yet": "Ancora nessuna... aggiungi una azione cliccando su una di quelle disponibili sopra.",
"labels": "Etichette",
"relations": "Relazioni",
"notes": "Note",
"other": "Altro"
},
"clone_to": {
"clone_notes_to": "Clona note in...",
"help_on_links": "Aiuto sui collegamenti",
"notes_to_clone": "Note da clonare",
"target_parent_note": "Nodo padre obiettivo",
"search_for_note_by_its_name": "cerca una nota per nome",
"cloned_note_prefix_title": "Le note clonate saranno mostrate nell'albero delle note con il dato prefisso",
"prefix_optional": "Prefisso (opzionale)",
"clone_to_selected_note": "Clona sotto la nota selezionata <kbd>invio</kbd>",
"no_path_to_clone_to": "Nessun percorso per clonare dentro.",
"note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\""
},
"confirm": {
"cancel": "Annulla",
"ok": "OK",
"confirmation": "Conferma",
"are_you_sure_remove_note": "Sei sicuro di voler rimuovere la nota \"{{title}}\" dalla mappa delle relazioni? ",
"if_you_dont_check": "Se non lo selezioni, la nota sarà rimossa solamente dalla mappa delle relazioni.",
"also_delete_note": "Rimuove anche la nota"
},
"delete_notes": {
"ok": "OK",
"close": "Chiudi",
"delete_notes_preview": "Anteprima di eliminazione delle note",
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere disfatto tramite i cambiamenti recenti)",
"erase_notes_description": "L'eliminazione normale (soft) marca le note come eliminate e potranno essere recuperate entro un certo lasso di tempo (dalla finestra dei cambiamenti recenti). Selezionando questa opzione le note si elimineranno immediatamente e non sarà possibile recuperarle.",
"erase_notes_warning": "Elimina le note in modo permanente (non potrà essere disfatto), compresi tutti i cloni. Ciò forzerà un nuovo caricamento dell'applicazione.",
"cancel": "Annulla",
"notes_to_be_deleted": "Le seguenti note saranno eliminate ({{- noteCount}})",
"no_note_to_delete": "Nessuna nota sarà eliminata (solo i cloni).",
"broken_relations_to_be_deleted": "Le seguenti relazioni saranno interrotte ed eliminate ({{- relationCount}})",
"deleted_relation_text": "La nota {{- note}} (da eliminare) è referenziata dalla relazione {{- relation}} originata da {{- source}}."
},
"info": {
"okButton": "OK",
"closeButton": "Chiudi"
},
"export": {
"close": "Chiudi",
"export_note_title": "Esporta la nota",
"export_status": "Stato dell'esportazione",
"export": "Esporta",
"choose_export_type": "Scegli prima il tipo di esportazione, per favore",
"export_in_progress": "Esportazione in corso: {{progressCount}}",
"export_finished_successfully": "Esportazione terminata con successo.",
"format_pdf": "PDF- allo scopo di stampa o esportazione.",
"export_type_subtree": "Questa nota e tutti i suoi discendenti",
"format_html": "HTML - raccomandato in quanto mantiene tutti i formati",
"format_html_zip": "HTML in archivio ZIP - questo è raccomandato in quanto conserva tutta la formattazione.",
"format_markdown": "MArkdown - questo conserva la maggior parte della formattazione.",
"export_type_single": "Solo questa nota, senza le sottostanti"
},
"password_not_set": {
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
"body2": "Per proteggere le note, fare clic su <a class=\"open-password-options-button\" href=\"javascript:\">qui</a> per aprire la finestra di dialogo Opzioni e impostare la password."
},
"protected_session_password": {
"close_label": "Chiudi"
},
"abstract_bulk_action": {
"remove_this_search_action": "Rimuovi questa azione di ricerca"
},
"etapi": {
"new_token_title": "Nuovo token ETAPI",
"new_token_message": "Inserire il nuovo nome del token"
},
"electron_integration": {
"zoom-factor": "Fattore di ingrandimento",
"desktop-application": "Applicazione Desktop"
},
"note_autocomplete": {
"search-for": "Cerca \"{{term}}\"",
"create-note": "Crea e collega la nota figlia \"{{term}}\"",
"insert-external-link": "Inserisci il collegamento esterno a \"{{term}}\"",
"clear-text-field": "Pulisci il campo di testo",
"show-recent-notes": "Mostra le note recenti",
"full-text-search": "Ricerca full text"
},
"note_tooltip": {
"note-has-been-deleted": "La nota è stata eliminata.",
"quick-edit": "Modifica veloce"
},
"geo-map": {
"create-child-note-title": "Crea una nota figlia e aggiungila alla mappa",
"create-child-note-instruction": "Clicca sulla mappa per creare una nuova nota qui o premi Escape per uscire.",
"unable-to-load-map": "Impossibile caricare la mappa."
},
"geo-map-context": {
"open-location": "Apri la posizione",
"remove-from-map": "Rimuovi dalla mappa",
"add-note": "Aggiungi un marcatore in questa posizione"
},
"debug": {
"debug": "Debug"
},
"database_anonymization": {
"light_anonymization": "Anonimizzazione parziale",
"title": "Anonimizzazione del Database",
"full_anonymization": "Anonimizzazione completa",
"full_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà (rimuove tutti i contenuti delle note, lasciando solo la struttura e qualche metadato non sensibile) per condividerlo online allo scopo di debugging, senza paura di far trapelare i tuoi dati personali.",
"save_fully_anonymized_database": "Salva il database completamente anonimizzato",
"light_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà in parzialmente — in particolare, solo il contenuto delle note sarà rimosso, ma i titoli e gli attributi rimarranno. Inoltre, note con script personalizzati JS di frontend/backend e widget personalizzati lasciando rimarranno. Ciò mette a disposizione più contesto per il debug dei problemi.",
"choose_anonymization": "Puoi decidere da solo se fornire un database completamente o parzialmente anonimizzato. Anche un database completamente anonimizzato è molto utile, sebbene in alcuni casi i database parzialmente anonimizzati possono accelerare il processo di identificazione dei bug e la loro correzione.",
"no_anonymized_database_yet": "Nessun database ancora anonimizzato.",
"save_lightly_anonymized_database": "Salva il database parzialmente anonimizzato",
"successfully_created_fully_anonymized_database": "Database completamente anonimizzato creato in {{anonymizedFilePath}}",
"successfully_created_lightly_anonymized_database": "Database parzialmente anonimizzato creato in {{anonymizedFilePath}}"
},
"cpu_arch_warning": {
"title": "Per favore scarica la versione ARM64",
"continue_anyway": "Continua Comunque",
"dont_show_again": "Non mostrare più questo avviso",
"download_link": "Scarica la Versione Nativa"
},
"editorfeatures": {
"title": "Caratteristiche",
"emoji_completion_enabled": "Abilita il completamento automatico delle Emoji",
"note_completion_enabled": "Abilita il completamento automatico delle note"
},
"table_view": {
"new-row": "Nuova riga",
"new-column": "Nuova colonna",
"sort-column-by": "Ordina per \"{{title}}\"",
"sort-column-ascending": "Ascendente",
"sort-column-descending": "Discendente",
"sort-column-clear": "Cancella l'ordinamento",
"hide-column": "Nascondi la colonna \"{{title}}\"",
"show-hide-columns": "Mostra/nascondi le colonne",
"row-insert-above": "Inserisci una riga sopra",
"row-insert-below": "Inserisci una riga sotto"
},
"abstract_search_option": {
"remove_this_search_option": "Rimuovi questa opzione di ricerca",
"failed_rendering": "Opzione di ricerca di rendering non riuscita: {{dto}} con errore: {{error}} {{stack}}"
},
"ancestor": {
"label": "Antenato"
},
"add_label": {
"add_label": "Aggiungi etichetta",
"label_name_placeholder": "nome dell'etichetta",
"new_value_placeholder": "nuovo valore",
"to_value": "al valore"
},
"update_label_value": {
"to_value": "al valore",
"label_name_placeholder": "nome dell'etichetta"
},
"delete_label": {
"delete_label": "Elimina etichetta",
"label_name_placeholder": "nome dell'etichetta",
"label_name_title": "Sono ammessi i caratteri alfanumerici, il carattere di sottolineato e i due punti."
},
"tree-context-menu": {
"move-to": "Muovi in...",
"cut": "Taglia"
},
"electron_context_menu": {
"cut": "Taglia",
"copy": "Copia",
"paste": "Incolla",
"copy-link": "Copia collegamento",
"paste-as-plain-text": "Incolla come testo semplice"
},
"editing": {
"editor_type": {
"multiline-toolbar": "Mostra la barra degli strumenti su più linee se non entra."
}
},
"edit_button": {
"edit_this_note": "Modifica questa nota"
},
"shortcuts": {
"shortcuts": "Scorciatoie"
},
"shared_switch": {
"toggle-on-title": "Condividi la nota",
"toggle-off-title": "Non condividere la nota"
},
"search_string": {
"search_prefix": "Cerca:"
},
"attachment_detail": {
"open_help_page": "Apri la pagina di aiuto sugli allegati"
},
"search_definition": {
"ancestor": "antenato",
"debug": "debug",
"action": "azione",
"add_search_option": "Aggiungi un opzione di ricerca:",
"search_string": "cerca la stringa",
"limit": "limite"
},
"modal": {
"close": "Chiudi"
},
"board_view": {
"insert-below": "Inserisci sotto",
"delete-column": "Elimina la colonna",
"delete-column-confirmation": "Sei sicuro di vole eliminare questa colonna? Il corrispondente attributo sarà eliminato anche nelle note sotto questa colonna."
},
"backup": {
"enable_weekly_backup": "Abilita le archiviazioni settimanali",
"enable_monthly_backup": "Abilita le archiviazioni mensili",
"backup_recommendation": "Si raccomanda di mantenere attive le archiviazioni, sebbene ciò possa rendere l'avvio dell'applicazione lento con database grandi e/o dispositivi di archiviazione lenti.",
"backup_now": "Archivia adesso",
"backup_database_now": "Archivia il database adesso",
"existing_backups": "Backup esistenti",
"date-and-time": "Data e ora",
"path": "Percorso",
"database_backed_up_to": "Il database è stato archiviato in {{backupFilePath}}",
"enable_daily_backup": "Abilita le archiviazioni giornaliere",
"no_backup_yet": "Ancora nessuna archiviazione"
},
"backend_log": {
"refresh": "Aggiorna"
},
"consistency_checks": {
"find_and_fix_button": "Trova e correggi i problemi di coerenza",
"finding_and_fixing_message": "In cerca e correzione dei problemi di coerenza...",
"issues_fixed_message": "Qualsiasi problema di coerenza che possa essere stato trovato ora è corretto."
},
"database_integrity_check": {
"check_button": "Controllo dell'integrità del database",
"checking_integrity": "Controllo dell'integrità del database in corso...",
"title": "Controllo di Integrità del database",
"description": "Controllerà che il database non sia corrotto a livello SQLite. Può durare un po' di tempo, a seconda della grandezza del DB.",
"integrity_check_failed": "Controllo di integrità fallito: {{results}}"
},
"sync": {
"title": "Sincronizza",
"force_full_sync_button": "Forza una sincronizzazione completa",
"failed": "Sincronizzazione fallita: {{message}}"
},
"sync_2": {
"config_title": "Configurazione per la Sincronizzazione",
"proxy_label": "Server Proxy per la sincronizzazione (opzionale)",
"test_title": "Test di sincronizzazione",
"timeout": "Timeout per la sincronizzazione",
"timeout_unit": "millisecondi",
"save": "Salva",
"help": "Aiuto"
},
"search_engine": {
"save_button": "Salva"
},
"sql_table_schemas": {
"tables": "Tabelle"
},
"tab_row": {
"close_tab": "Chiudi la scheda",
"add_new_tab": "Aggiungi una nuova scheda",
"close": "Chiudi",
"close_other_tabs": "Chiudi le altre schede",
"close_right_tabs": "Chiudi le schede a destra",
"close_all_tabs": "Chiudi tutte le schede",
"reopen_last_tab": "Riapri l'ultima scheda chiusa",
"move_tab_to_new_window": "Sposta questa scheda in una nuova finestra",
"copy_tab_to_new_window": "Copia questa scheda in una nuova finestra",
"new_tab": "Nuova scheda"
},
"toc": {
"table_of_contents": "Sommario"
},
"table_of_contents": {
"title": "Sommario"
},
"tray": {
"title": "Vassoio di Sistema",
"enable_tray": "Abilita il vassoio (Trilium necessita di essere riavviato affinché la modifica abbia effetto)"
},
"heading_style": {
"title": "Stile dell'Intestazione",
"plain": "Normale",
"underline": "Sottolineato",
"markdown": "Stile Markdown"
},
"highlights_list": {
"title": "Punti salienti"
},
"highlights_list_2": {
"title": "Punti salienti",
"options": "Opzioni"
},
"quick-search": {
"placeholder": "Ricerca rapida",
"searching": "Ricerca in corso..."
},
"help": {
"goUpDown": "su/giù nella lista delle note",
"collapseExpand": "collassa/espande il nodo",
"notSet": "non impostato",
"goBackForwards": "indietro/avanti nella cronologia",
"showJumpToNoteDialog": "mostra <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">la finestra di dialogo \"Salta alla nota\"<a>"
}
}

View File

@@ -185,7 +185,7 @@
"debug": "デバッグ",
"debug_description": "デバッグは複雑なクエリのデバッグを支援するために、追加のデバッグ情報をコンソールに出力します",
"action": "アクション",
"search_button": "検索",
"search_button": "検索 <kbd>Enter</kbd>",
"search_execute": "検索とアクションの実行",
"save_to_note": "ノートに保存",
"search_parameters": "検索パラメータ",
@@ -692,8 +692,7 @@
"placeholder_search": "ノート名で検索",
"dialog_title": "埋め込みノート",
"box_size_prompt": "埋め込みノート枠のサイズ:",
"button_include": "埋め込みノート",
"label_note": "ノート"
"button_include": "埋め込みノート"
},
"ancestor": {
"placeholder": "ノート名で検索"
@@ -977,6 +976,8 @@
},
"open-help-page": "ヘルプページを開く",
"shared_info": {
"shared_publicly": "このノートは一般公開されています",
"shared_locally": "このノートはローカルで共有されています",
"help_link": "ヘルプについては、<a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>をご覧ください。"
},
"highlights_list_2": {

View File

@@ -1,29 +0,0 @@
{
"about": {
"title": "Trilium Notes에 대해서",
"homepage": "홈페이지:",
"app_version": "앱 버전:",
"db_version": "DB 버전:",
"sync_version": "동기화 버전:",
"build_date": "빌드 날짜:",
"build_revision": "빌드 리비전:",
"data_directory": "데이터 경로:"
},
"toast": {
"critical-error": {
"title": "심각한 오류",
"message": "클라이언트 애플리케이션 시작 도중 심각한 오류가 발생했습니다:\n\n{{message}}\n\n이는 스크립트가 예기치 않게 실패하면서 발생한 것일 수 있습니다. 애플리케이션을 안전 모드로 시작한 뒤 문제를 해결해 보세요."
},
"widget-error": {
"title": "위젯 초기화 실패"
}
},
"add_link": {
"add_link": "링크 추가",
"note": "노트",
"search_note": "이름으로 노트 검색하기"
},
"branch_prefix": {
"save": "저장"
}
}

View File

@@ -1,9 +0,0 @@
{
"about": {
"title": "Over Trilium Notes",
"homepage": "Homepagina:",
"app_version": "App versie:",
"db_version": "DB Versie:",
"sync_version": "Sync Versie:"
}
}

View File

@@ -13,131 +13,29 @@
"critical-error": {
"title": "Błąd krytyczny",
"message": "Wystąpił krytyczny błąd uniemożliwiający uruchomienie aplikacji:\n\n{{message}}\n\nJest to spowodowane najprawdopodobniej niespodziewanym błędem skryptu. Spróbuj uruchomić aplikację ponownie w trybie bezpiecznym i zaadresuj problem."
},
"widget-error": {
"title": "Nie udało się zainicjować widżetu",
"message-custom": "Niestandardowy widżet z notatki o identyfikatorze \"{{id}}\", i tytule \"{{title}}\" nie mógł zostać zainicjowany z powodu:\n\n{{message}}",
"message-unknown": "Nieznany widżet nie mógł być zainicjowany z powodu:\n\n{{message}}"
},
"bundle-error": {
"title": "Nie udało się załadować niestandardowego skryptu",
"message": "Skrypt z notatki o identyfikatorze \"{{id}}\", tytule \"{{title}}: nie został uruchomiony z powodu:\n\n{{message}}"
}
},
"add_link": {
"add_link": "Dodaj link",
"note": "Notatka",
"search_note": "Wyszukaj notatkę po nazwie",
"link_title_arbitrary": "Tytuł linku można dowolnie zmieniać",
"link_title": "Tytuł linku",
"button_add_link": "Dodaj link",
"help_on_links": "Pomoc dotycząca linków",
"link_title_mirrors": "tytuł linku odzwierciedla tytuł obecnej notatki"
"add_link": "Dodaj link"
},
"branch_prefix": {
"save": "Zapisz",
"edit_branch_prefix": "Edytuj prefiks gałęzi",
"prefix": "Prefiks: ",
"branch_prefix_saved": "Zapisano prefiks gałęzi.",
"help_on_tree_prefix": "Pomoc dotycząca prefiksu drzewa"
"save": "Zapisz"
},
"bulk_actions": {
"labels": "Etykiety",
"notes": "Notatki",
"other": "Inne",
"relations": "Powiązania",
"bulk_actions": "Działania zbiorcze",
"include_descendants": "Uwzględnia rozwinięcia wybranych notatek",
"available_actions": "Dostępne działania",
"chosen_actions": "Wybrane działania",
"execute_bulk_actions": "Wykonaj zbiór działań",
"bulk_actions_executed": "Zbiór działań został wykonany prawidłowo.",
"none_yet": "Brak zaznaczonych działań... dodaj działanie poprzez kliknięcie jednej z dostępnych opcji powyżej."
"relations": "Powiązania"
},
"confirm": {
"ok": "OK",
"cancel": "Anuluj",
"confirmation": "Potwierdzenie",
"are_you_sure_remove_note": "Czy napewno chcesz usunąć notatkę \"{{title}}\" z mapy powiązań? ",
"if_you_dont_check": "Jeśli nie zaznaczysz tej opcji, notatka zostanie usunięta jedynie z mapy powiązań.",
"also_delete_note": "Usuń dodatkowo notatkę"
"cancel": "Anuluj"
},
"delete_notes": {
"cancel": "Anuluj",
"close": "Zamknij",
"delete_notes_preview": "Usuń podgląd notatek",
"delete_all_clones_description": "Usuń również wszystkie sklonowania (działanie może zostać cofnięte w ostatnich zmianach)",
"erase_notes_description": "Normalne (miękkie) usuwanie zaznacza jedynie notatki jako usunięte i można je przywrócić (w oknie dialogowym ostatnich zmian) przez wyznaczony okres czasu. Zaznaczenie tej opcji spowoduje natychmiastowe usunięcie notatek, bez możliwości ich przywrócenia.",
"erase_notes_warning": "Usuń notatki permanentnie (bez opcji ich przywrócenia), włączając wszystkie kopie. Działanie to wymaga ponownego uruchomienia aplikacji.",
"notes_to_be_deleted": "Następujące notatki zostaną usunięte ({{notesCount}})",
"no_note_to_delete": "Żadne notatki nie zostaną usunięte (jedynie kopie).",
"broken_relations_to_be_deleted": "Następujące powiązania zostaną uszkodzone i usunięte ({{ relationCount}})",
"ok": "OK",
"deleted_relation_text": "Notatka {{- note}} (do usunięcia) jest powiązana przez relację {{- relation}} pochodzącą z {{- source}}."
"close": "Zamknij"
},
"export": {
"close": "Zamknij",
"export_note_title": "Eksportuj notatkę",
"export_type_subtree": "Ta notatka oraz wszystkie podrzędne",
"format_html": "HTML - rekomendowany jako zachowujący całość formatowania",
"format_html_zip": "HTML w archiwum ZIP - rekomendowany jako zachowujący całość formatowania.",
"format_markdown": "Markdown - zachowuje większość formatowania.",
"format_opml": "OPML - format wymiany danych dla outlinerów zawierający tylko tekst. Formatowanie, obrazy i pliki nie są uwzględnione.",
"opml_version_1": "OPML v1.0 - tylko zwykły tekst",
"opml_version_2": "OPML v2.0 - umożliwia również HTML",
"export_type_single": "Tylko ta notatka, bez elementów podrzędnych",
"export": "Eksportuj",
"choose_export_type": "Wybierz najpierw rodzaj pliku do eksportu",
"export_status": "Status eksportu",
"export_in_progress": "Postęp eksportowania: {{progressCount}}",
"export_finished_successfully": "Eksportowanie zakończone.",
"format_pdf": "PDF - w celu drukowania lub udostępniania."
},
"clone_to": {
"clone_notes_to": "Sklonuj notatki do...",
"notes_to_clone": "Notatki do sklonowania",
"search_for_note_by_its_name": "Wyszukaj notatkę po jej nazwie",
"cloned_note_prefix_title": "Sklonowana notatka zostanie wyświetlona w drzewie notatki z podanym prefiksem",
"prefix_optional": "Prefiks (opcjonalne)",
"clone_to_selected_note": "Sklonuj do wybranej notatki",
"no_path_to_clone_to": "Brak ścieżki do sklonowania.",
"note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\"",
"help_on_links": "Pomoc dotycząca linków"
},
"help": {
"title": "Ściągawka",
"noteNavigation": "Nawigacja po notatkach",
"goUpDown": "przewijanie w górę/w dół w liście notatek",
"collapseExpand": "zwiń/rozwiń zbiór",
"notSet": "niezdefiniowany",
"goBackForwards": "przewijaj do tyłu/do przodu w historii",
"showJumpToNoteDialog": "pokaż <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"przejdź do dialogu</a>",
"scrollToActiveNote": "przewiń do aktywnej notatki",
"jumpToParentNote": "przejdź do głównej notatki",
"collapseWholeTree": "zwiń całe drzewko notatki",
"collapseSubTree": "zwiń gałąź notatki",
"tabShortcuts": "Skóry kart",
"newTabNoteLink": "link notatki otwiera notatkę w nowej karcie",
"newTabWithActivationNoteLink": "link notatki otwiera i aktywuje notatkę w nowej karcie",
"onlyInDesktop": "Tylko na komputerze stacjonarnym (wersja Electron)",
"openEmptyTab": "Otwórz pustą kartę",
"closeActiveTab": "zamknij aktywną kartę",
"activateNextTab": "aktywuj następną kartę",
"activatePreviousTab": "aktywuj poprzednią kartę",
"creatingNotes": "Tworzenie notatek",
"createNoteAfter": "Utwórz nową notatkę obok obecnie aktywnej",
"createNoteInto": "Utwórz nową podnotatkę w obecnie otwartej",
"editBranchPrefix": "edytuj <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefiks</a> aktywnej kopii notatki",
"movingCloningNotes": "Przenoszenie / kopiowanie notatek",
"moveNoteUpDown": "Przenieś notatkę w górę/w dół na liście notatek",
"moveNoteUpHierarchy": "Przenieś notatkę w górę w hierarchii",
"multiSelectNote": "Zaznacz wiele notatek powyżej/poniżej",
"selectAllNotes": "Wybierz wszystkie notatki na obecnym poziomie",
"selectNote": "Wybierz notatkę",
"copyNotes": "skopiuj obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">klonowania</a>)",
"cutNotes": "przytnij obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla przenoszenia notatek)",
"pasteNotes": "wklej notatkę jako podnotatka w obecnej notatce (rozumiane jako przenieś lub skopiuj, w zależności czy notatka była skopiowana czy wycięta)",
"deleteNotes": "usuń notatkę / gałąź",
"editingNotes": "Edytowanie notatek"
"close": "Zamknij"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,7 @@
"add_relation": {
"add_relation": "Adaugă relație",
"allowed_characters": "Sunt permise doar caractere alfanumerice, underline și două puncte.",
"create_relation_on_all_matched_notes": "Creează relația pentru toate notițele găsite.",
"create_relation_on_all_matched_notes": "Crează relația pentru toate notițele găsite",
"relation_name": "denumirea relației",
"target_note": "notița destinație",
"to": "către"
@@ -76,9 +76,9 @@
"attachment_erasure_timeout": {
"attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.",
"attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor",
"erase_attachments_after": "Șterge atașamentele neutilizate după:",
"erase_attachments_after": "Erase unused attachments after:",
"erase_unused_attachments_now": "Elimină atașamentele șterse acum",
"manual_erasing_description": "Puteți șterge atașamentele nefolosite manual (fără a lua în considerare timpul de mai sus):",
"manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe",
"unused_attachments_erased": "Atașamentele nefolosite au fost șterse."
},
"attachment_list": {
@@ -141,7 +141,7 @@
"hide_promoted_attributes": "Ascunde lista atributelor promovate pentru această notiță",
"hide_relations": "lista denumirilor relațiilor ce trebuie ascunse, delimitate prin virgulă. Toate celelalte vor fi afișate.",
"icon_class": "valoarea acestei etichete este adăugată ca o clasă CSS la iconița notiței din ierarhia notițelor, fapt ce poate ajuta la identificarea vizuală mai rapidă a notițelor. Un exemplu ar fi „bx bx-home” pentru iconițe preluate din boxicons. Poate fi folosită în notițe de tip șablon.",
"inbox": "locația implicită în care vor apărea noile notițe atunci când se crează o noitiță utilizând butonul „Crează notiță” din bara laterală, notițele vor fi create în interiorul notiței marcată cu eticheta <code>#inbox</code>.",
"inbox": "locația implicită în care vor apărea noile notițe atunci când se crează o noitiță utilizând butonul „Crează notiță” din bara laterală, notițele vor fi create în interiorul notiței cu această etichetă.",
"inherit": "atributele acestei notițe vor fi moștenite chiar dacă nu există o relație părinte-copil între notițe. A se vedea relația de tip șablon pentru un concept similar. De asemenea, a se vedea moștenirea atributelor în documentație.",
"inheritable": "Moștenibilă",
"inheritable_title": "Atributele moștenibile vor fi moștenite de către toți descendenții acestei notițe.",
@@ -177,7 +177,7 @@
"render_note": "relație ce definește notița (de tip notiță de cod HTML sau script) ce trebuie randată pentru notițele de tip „Randare notiță HTML”",
"run": "definește evenimentele la care să ruleze scriptul. Valori acceptate:\n<ul>\n<li>frontendStartup - când pornește interfața Trilium (sau este reîncărcată), dar nu pe mobil.</li>\n<li>mobileStartup - când pornește interfața Trilium (sau este reîncărcată), doar pe mobil.</li>\n<li>backendStartup - când pornește serverul Trilium</li>\n<li>hourly - o dată pe oră. Se poate utiliza adițional eticheta <code>runAtHour</code> pentru a specifica ora.</li>\n<li>daily - o dată pe zi</li>\n</ul>",
"run_at_hour": "La ce oră ar trebui să ruleze. Trebuie folosit împreună cu <code>#run=hourly</code>. Poate fi definit de mai multe ori pentru a rula de mai multe ori în cadrul aceleași zile.",
"run_on_attribute_change": " se execută atunci când atributele unei notițe care definește această relație se schimbă. Se apelează și atunci când un atribut este șters",
"run_on_attribute_change": "se execută atunci când atributele unei notițe care definește această relație se schimbă. Se apelează și atunci când un atribut este șters",
"run_on_attribute_creation": "se execută atunci când un nou atribut este creat pentru notița care definește această relație",
"run_on_branch_change": "se execută atunci când o ramură este actualizată.",
"run_on_branch_creation": "se execută când o ramură este creată. O ramură este o legătură dintre o notiță părinte și o notiță copil și este creată, spre exemplu, la clonarea sau mutarea unei notițe.",
@@ -198,7 +198,7 @@
"share_disallow_robot_indexing": "împiedică indexarea conținutului de către roboți utilizând antetul <code>X-Robots-Tag: noindex</code>",
"share_external_link": "notița va funcționa drept o legătură către un site web extern în ierarhia de partajare",
"share_favicon": "Notiță ce conține pictograma favicon pentru a fi setată în paginile partajate. De obicei se poate seta în rădăcina ierarhiei de partajare și se poate face moștenibilă. Notița ce conține favicon-ul trebuie să fie și ea în ierarhia de partajare. Considerați și utilizarea „share_hidden_from_tree”.",
"share_hidden_from_tree": "notița este ascunsă din arborele de navigație din stânga, dar încă este accesibilă prin intermediul unui URL",
"share_hidden_from_tree": "notița este ascunsă din arborele de navigație din stânga, dar încă este accesibilă prin intermediul unui URL.",
"share_index": "notițele cu această etichetă vor afișa lista tuturor rădăcilor notițelor partajate",
"share_js": "Notiță JavaScript ce va fi injectată în pagina de partajare. Notița respectivă trebuie să fie și ea în ierarhia de partajare. Considerați utilizarea 'share_hidden_from_tree'.",
"share_omit_default_css": "CSS-ul implicit pentru pagina de partajare va fi omis. Se poate folosi atunci când se fac schimbări majore de stil la pagină.",
@@ -214,7 +214,7 @@
"target_note_title": "Relația este o conexiune numită dintre o notiță sursă și o notiță țintă.",
"template": "Șablon",
"text": "Text",
"title_template": "titlul implicit al notițelor create în interiorul acestei notițe. Valoarea este evaluată ca un șir de caractere JavaScript\n și poate fi astfel îmbogățită cu un conținut dinamic prin intermediul variabilelor <code>now</code> și <code>parentNote</code>. Exemple:\n \n <ul>\n <li><code>Lucrările lui ${parentNote.getLabelValue('autor')}</code></li>\n <li><code>Jurnal pentru ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n A se vedea <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki-ul pentru detalii</a>, documentația API pentru <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> și <a href=\"https://day.js.org/docs/en/display/format\">now</a> pentru mai multe informații.",
"title_template": "titlul implicit al notițelor create în interiorul acestei notițe. Valoarea este evaluată ca un șir de caractere JavaScript\n și poate fi astfel îmbogățită cu un conținut dinamic prin intermediul variabilelow <code>now</code> și <code>parentNote</code>. Exemple:\n \n <ul>\n <li><code>Lucrările lui ${parentNote.getLabelValue('autor')}</code></li>\n <li><code>Jurnal pentru ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n A se vedea <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki-ul pentru detalii</a>, documentația API pentru <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> și <a href=\"https://day.js.org/docs/en/display/format\">now</a> pentru mai multe informații",
"toc": "<code>#toc</code> sau <code>#toc=show</code> forțează afișarea tabelei de conținut, <code>#toc=hide</code> forțează ascunderea ei. Dacă eticheta nu există, se utilizează setările globale",
"top": "păstrează notița la începutul listei (se aplică doar pentru notițe sortate automat)",
"url": "URL",
@@ -369,7 +369,7 @@
},
"confirm": {
"also_delete_note": "Șterge și notița",
"are_you_sure_remove_note": "Doriți ștergerea notiței „{{title}}” din harta de relații? ",
"are_you_sure_remove_note": "Doriți ștergerea notiței „{{title}}” din harta de relații?",
"cancel": "Anulează",
"confirmation": "Confirm",
"if_you_dont_check": "Dacă această opțiune nu este bifată, notița va fi ștearsă doar din harta de relații.",
@@ -519,8 +519,8 @@
"export_status": "Starea exportului",
"export_type_single": "Doar această notiță fără descendenții ei",
"export_type_subtree": "Această notiță și toți descendenții ei",
"format_html_zip": "HTML în arhivă ZIP - recomandat deoarece păstrează toată formatarea.",
"format_markdown": "Markdown - păstrează majoritatea formatării.",
"format_html_zip": "HTML în arhivă ZIP - recomandat deoarece păstrează toată formatarea",
"format_markdown": "Markdown - păstrează majoritatea formatării",
"format_opml": "OPML - format de interschimbare pentru editoare cu structură ierarhică (outline). Formatarea, imaginile și fișierele nu vor fi incluse.",
"opml_version_1": "OPML v1.0 - text simplu",
"opml_version_2": "OPML v2.0 - permite și HTML",
@@ -640,7 +640,7 @@
"newTabNoteLink": "pe o legătură către o notiță va deschide notița într-un tab nou",
"notSet": "nesetat",
"noteNavigation": "Navigarea printre notițe",
"numberedList": "<code>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată",
"numberedList": "<kbd>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată",
"onlyInDesktop": "Doar pentru desktop (aplicația Electron)",
"openEmptyTab": "deschide un tab nou",
"other": "Altele",
@@ -807,7 +807,7 @@
"dialog_title": "Mută notițele în...",
"error_no_path": "Nicio cale la care să poată fi mutate.",
"move_button": "Mută la notița selectată",
"move_success_message": "Notițele selectate au fost mutate în ",
"move_success_message": "Notițele selectate au fost mutate în",
"notes_to_move": "Notițe de mutat",
"search_placeholder": "căutați notița după denumirea ei",
"target_parent_note": "Notița părinte destinație"
@@ -1058,7 +1058,7 @@
"download_button": "Descarcă",
"file_size": "Dimensiune fișier:",
"help_title": "Informații despre reviziile notițelor",
"mime": "MIME: ",
"mime": "MIME:",
"no_revisions": "Nu există încă nicio revizie pentru această notiță...",
"note_revisions": "Revizii ale notiței",
"preview": "Previzualizare:",
@@ -1106,7 +1106,7 @@
"limit_description": "Limitează numărul de rezultate",
"order_by": "ordonează după",
"save_to_note": "Salvează în notiță",
"search_button": "Căutare",
"search_button": "Căutare <kbd>Enter</kbd>",
"search_execute": "Caută și execută acțiunile",
"search_note_saved": "Notița de căutare a fost salvată în {{- notePathTitle}}",
"search_parameters": "Parametrii de căutare",
@@ -1193,7 +1193,7 @@
"enable": "Activează corectorul ortografic",
"language_code_label": "Codurile de limbă",
"language_code_placeholder": "de exemplu „en-US”, „de-AT”",
"multiple_languages_info": "Mai multe limbi pot fi separate prin virgulă, e.g. \"en-US, de-DE, cs\". ",
"multiple_languages_info": "Mai multe limbi pot fi separate prin virgulă, e.g. \"en-US, de-DE, cs\".",
"title": "Corector ortografic",
"restart-required": "Schimbările asupra setărilor corectorului ortografic vor fi aplicate după restartarea aplicației."
},
@@ -1286,7 +1286,7 @@
"update_relation_target": {
"allowed_characters": "Sunt permise doar caractere alfanumerice, underline și două puncte.",
"change_target_note": "schimbă notița-țintă a unei relații existente",
"on_all_matched_notes": "Pentru toate notițele găsite",
"on_all_matched_notes": "Pentru toate notițele găsite:",
"relation_name": "denumirea relației",
"target_note": "notița destinație",
"to": "la",
@@ -1314,7 +1314,7 @@
"use_vim_keybindings_in_code_notes": "Combinații de taste Vim"
},
"web_view": {
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
"embed_websites": "Notițele de tip „Vizualizare web” permit încorporarea site-urilor web în Trilium.",
"web_view": "Vizualizare web"
},
@@ -1373,8 +1373,8 @@
},
"shared_info": {
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
"shared_locally": "Această notiță este partajată local la {{- link}}",
"shared_publicly": "Această notiță este partajată public la {{- link}}"
"shared_locally": "Această notiță este partajată local la",
"shared_publicly": "Această notiță este partajată public la"
},
"note_types": {
"book": "Colecție",
@@ -1863,16 +1863,11 @@
},
"create_new_ai_chat": "Crează o nouă discuție cu AI-ul",
"configuration_warnings": "Sunt câteva probleme la configurația AI-ului. Verificați setările.",
"experimental_warning": "Funcția LLM este experimentală.",
"experimental_warning": "Funcția LLM este experimentală!",
"selected_provider": "Furnizor selectat",
"selected_provider_description": "Selectați furnizorul de AI pentru funcțiile de discuție și completare",
"select_model": "Selectați modelul...",
"select_provider": "Selectați furnizorul...",
"ai_enabled": "Funcționalitățile AI au fost activate",
"ai_disabled": "Funcționalitățile AI au fost dezactivate",
"no_models_found_online": "Nu s-a găsit niciun model. Verificați cheia API și configurația.",
"no_models_found_ollama": "Nu s-a găsit niciun model Ollama. Verificați dacă Ollama rulează.",
"error_fetching": "Eroare la obținerea modelelor: {{error}}"
"select_provider": "Selectați furnizorul..."
},
"custom_date_time_format": {
"title": "Format dată/timp personalizat",
@@ -2003,28 +1998,6 @@
"call_to_action": {
"background_effects_title": "Efectele de fundal sunt acum stabile",
"background_effects_message": "Pe dispozitive cu Windows, efectele de fundal sunt complet stabile. Acestea adaugă un strop de culoare interfeței grafice prin estomparea fundalului din spatele ferestrei. Această tehnică este folosită și în alte aplicații precum Windows Explorer.",
"background_effects_button": "Activează efectele de fundal",
"next_theme_title": "Încercați noua temă Trilium",
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
"next_theme_button": "Testează noua temă",
"dismiss": "Treci peste"
},
"ui-performance": {
"title": "Setări de performanță",
"enable-motion": "Activează tranzițiile și animațiile",
"enable-shadows": "Activează umbrirea elementelor",
"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": {
"related_settings": "Setări similare"
},
"settings_appearance": {
"related_code_blocks": "Tema de culori pentru blocuri de cod în notițe de tip text",
"related_code_notes": "Tema de culori pentru notițele de tip cod"
},
"units": {
"percentage": "%"
"background_effects_button": "Activează efectele de fundal"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -729,8 +729,7 @@
"note_type": "筆記類型",
"editable": "可編輯",
"basic_properties": "基本屬性",
"language": "語言",
"configure_code_notes": "配寘代碼注釋..."
"language": "語言"
},
"book_properties": {
"view_type": "視圖類型",
@@ -846,7 +845,7 @@
"debug": "除錯",
"debug_description": "除錯將顯示額外的除錯資訊至控制台,以幫助除錯複雜查詢",
"action": "操作",
"search_button": "搜尋",
"search_button": "搜尋 <kbd>Enter</kbd>",
"search_execute": "搜尋並執行操作",
"save_to_note": "儲存至筆記",
"search_parameters": "搜尋參數",
@@ -1399,9 +1398,9 @@
"open-in-popup": "快速編輯"
},
"shared_info": {
"help_link": "如需幫助,請訪問 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>。",
"shared_publicly": "此筆記已公開分享於 {{- link}}。",
"shared_locally": "此筆記已於本地分享至 {{- link}}。"
"shared_publicly": "此筆記已公開分享在",
"shared_locally": "此筆記已在本地分享在",
"help_link": "如需幫助,請訪問 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>。"
},
"note_types": {
"text": "文字",
@@ -1479,8 +1478,7 @@
"hoist-this-note-workspace": "聚焦此筆記(工作區)",
"refresh-saved-search-results": "重新整理儲存的搜尋結果",
"create-child-note": "建立子筆記",
"unhoist": "取消聚焦",
"toggle-sidebar": "切換側邊欄"
"unhoist": "取消聚焦"
},
"title_bar_buttons": {
"window-on-top": "保持此視窗置頂"
@@ -1645,13 +1643,13 @@
"failed_notes": "失敗筆記",
"last_processed": "最後處理時間",
"refresh_stats": "更新統計資料",
"enable_ai_features": "啟用 AI/LLM 功能",
"enable_ai_features": "啟用 AI / LLM 功能",
"enable_ai_description": "啟用筆記摘要、內容生成等 AI 功能及其他 LLM 能力",
"openai_tab": "OpenAI",
"anthropic_tab": "Anthropic",
"voyage_tab": "Voyage AI",
"ollama_tab": "Ollama",
"enable_ai": "啟用 AI/LLM 功能",
"enable_ai": "啟用 AI / LLM 功能",
"enable_ai_desc": "啟用筆記摘要、內容生成等 AI 功能及其他 LLM 能力",
"provider_configuration": "AI 提供者設定",
"provider_precedence": "提供者優先級",
@@ -1773,12 +1771,7 @@
"selected_provider": "已選提供者",
"selected_provider_description": "選擇用於聊天和補全功能的 AI 提供者",
"select_model": "選擇模型…",
"select_provider": "選擇提供者…",
"ai_enabled": "已啟用 AI 功能",
"ai_disabled": "已禁用 AI 功能",
"no_models_found_online": "找不到模型。請檢查您的 API 金鑰及設定。",
"no_models_found_ollama": "找不到 Ollama 模型。請確認 Ollama 是否正在執行。",
"error_fetching": "獲取模型失敗:{{error}}"
"select_provider": "選擇提供者…"
},
"code-editor-options": {
"title": "編輯器"
@@ -2006,21 +1999,5 @@
"next_theme_message": "您正在使用舊版主題,要試用新主題嗎?",
"next_theme_button": "試用新主題",
"dismiss": "關閉"
},
"settings": {
"related_settings": "相關設定"
},
"settings_appearance": {
"related_code_blocks": "文字筆記中程式碼區塊的配色方案",
"related_code_notes": "程式碼筆記的配色方案"
},
"units": {
"percentage": "%"
},
"ui-performance": {
"title": "效能",
"enable-motion": "啟用轉場與動畫",
"enable-shadows": "啟用陰影",
"enable-backdrop-effects": "啟用選單、彈出視窗和面板的背景特效"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,7 @@
{
"about": {
"homepage": "Trang chủ:",
"title": "Về Trilium Notes",
"app_version": "Phiên bản:",
"db_version": "Phiên bản DB:",
"sync_version": "Phiên bản liên kết:"
"title": "Về Trilium Notes"
},
"add_link": {
"add_link": "Thêm liên kết",
@@ -29,8 +26,7 @@
"close": "Đóng"
},
"help": {
"other": "Khác",
"notSet": "chưa được đặt"
"other": "Khác"
},
"toast": {
"critical-error": {
@@ -73,16 +69,12 @@
"add_label": {
"add_label": "Thêm nhãn",
"label_name_placeholder": "tên nhãn",
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn",
"new_value_placeholder": "giá trị mới"
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn"
},
"rename_label": {
"rename_label": "Đặt lại tên nhãn"
},
"call_to_action": {
"dismiss": "Bỏ qua"
},
"abstract_search_option": {
"remove_this_search_option": "Xoá lựa chọn tìm kiếm này"
}
}

View File

@@ -113,7 +113,7 @@ declare namespace Fancytree {
generateFormElements(selected?: boolean, active?: boolean): void;
/** Return the currently active node or null. */
getActiveNode(): FancytreeNode | null;
getActiveNode(): FancytreeNode;
/** Return the first top level node if any (not the invisible root node). */
getFirstChild(): FancytreeNode;

View File

@@ -3,11 +3,7 @@ type DateTimeStyle = "full" | "long" | "medium" | "short" | "none" | undefined;
/**
* Formats the given date and time to a string based on the current locale.
*/
export function formatDateTime(date: string | Date | number | null | undefined, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") {
if (!date) {
return "";
}
export function formatDateTime(date: string | Date | number, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") {
const locale = navigator.language;
let parsedDate;

View File

@@ -1,163 +0,0 @@
/* #region Generic floating buttons styles */
.floating-buttons {
position: relative;
}
.floating-buttons-children,
.show-floating-buttons {
position: absolute;
top: 10px;
right: 10px;
display: flex;
flex-direction: row;
z-index: 100;
}
.note-split.rtl .floating-buttons-children,
.note-split.rtl .show-floating-buttons {
right: unset;
left: 10px;
}
.note-split.rtl .close-floating-buttons {
order: -1;
}
.note-split.rtl .close-floating-buttons,
.note-split.rtl .show-floating-buttons {
transform: rotate(180deg);
}
.type-canvas .floating-buttons-children {
top: 70px;
}
.type-canvas .floating-buttons-children > * {
--border-radius: 0; /* Overridden by themes */
}
.floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) {
margin: 2px;
}
.floating-buttons-children > *:not(.has-overflow) {
overflow: hidden;
}
.floating-buttons-children > button, .floating-buttons-children .floating-button {
font-size: 150%;
padding: 5px 10px 4px 10px;
width: 40px;
cursor: pointer;
color: var(--button-text-color);
background: var(--button-background-color);
border-radius: var(--button-border-radius);
border: 1px solid transparent;
display: flex;
justify-content: space-around;
}
.floating-buttons-children > button:hover, .floating-buttons-children .floating-button:hover {
text-decoration: none;
border-color: var(--button-border-color);
}
.floating-buttons .floating-buttons-children.temporarily-hidden {
display: none;
}
/* #endregion */
/* #region Show floating button */
.floating-buttons-children.temporarily-hidden+.show-floating-buttons {
display: block;
}
.show-floating-buttons {
/* display: none;*/
margin-left: 5px !important;
}
.show-floating-buttons-button {
border: 1px solid transparent;
color: var(--button-text-color);
padding: 6px;
border-radius: 100px !important;
}
.show-floating-buttons-button:hover {
border: 1px solid var(--button-border-color);
}
/* #endregion */
/* #region Geo map buttons */
.leaflet-pane {
z-index: 50;
}
/* #endregion */
/* #region Close floating buttons */
.close-floating-buttons {
margin-left: 5px !important;
}
.close-floating-buttons:first-child {
display: none !important;
}
.close-floating-buttons-button {
border: 1px solid transparent;
color: var(--button-text-color);
padding: 6px;
border-radius: 100px;
}
.close-floating-buttons-button:hover {
border: 1px solid var(--button-border-color);
}
/* #endregion */
/* #region Backlinks */
.backlinks-widget {
position: relative;
}
.backlinks-ticker {
border-radius: 10px;
border-color: var(--main-border-color);
background-color: var(--more-accented-background-color);
padding: 4px 10px 4px 10px;
opacity: 90%;
display: flex;
justify-content: space-between;
align-items: center;
}
.backlinks-count {
cursor: pointer;
}
.backlinks-items {
z-index: 10;
position: absolute;
top: 50px;
right: 10px;
width: 400px;
border-radius: 10px;
background-color: var(--accented-background-color);
color: var(--main-text-color);
padding: 20px;
overflow-y: auto;
}
.backlink-excerpt {
border-left: 2px solid var(--main-border-color);
padding-left: 10px;
opacity: 80%;
font-size: 90%;
}
.backlink-excerpt .backlink-link { /* the actual backlink */
font-weight: bold;
background-color: yellow;
}
/* #endregion */

View File

@@ -1,94 +0,0 @@
import { t } from "i18next";
import "./FloatingButtons.css";
import { useNoteContext, useNoteLabel, useNoteLabelBoolean } from "./react/hooks";
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
import { ParentComponent } from "./react/react_utils";
import { EventData, EventNames } from "../components/app_context";
import { type FloatingButtonsList, type FloatingButtonContext } from "./FloatingButtonsDefinitions";
import ActionButton from "./react/ActionButton";
import { ViewTypeOptions } from "../services/note_list_renderer";
interface FloatingButtonsProps {
items: FloatingButtonsList;
}
/*
* Note:
*
* For floating button widgets that require content to overflow, the has-overflow CSS class should
* be applied to the root element of the widget. Additionally, this root element may need to
* properly handle rounded corners, as defined by the --border-radius CSS variable.
*/
export default function FloatingButtons({ items }: FloatingButtonsProps) {
const { note, noteContext } = useNoteContext();
const parentComponent = useContext(ParentComponent);
const [ viewType ] = useNoteLabel(note, "viewType");
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const context = useMemo<FloatingButtonContext | null>(() => {
if (!note || !noteContext || !parentComponent) return null;
return {
note,
noteContext,
parentComponent,
isDefaultViewMode: noteContext.viewScope?.viewMode === "default",
viewType: viewType as ViewTypeOptions,
isReadOnly,
triggerEvent<T extends EventNames>(name: T, data?: Omit<EventData<T>, "ntxId">) {
parentComponent.triggerEvent(name, {
ntxId: noteContext.ntxId,
...data
} as EventData<T>);
}
};
}, [ note, noteContext, parentComponent, viewType, isReadOnly ]);
// Manage the user-adjustable visibility of the floating buttons.
const [ visible, setVisible ] = useState(true);
useEffect(() => setVisible(true), [ note ]);
return (
<div className="floating-buttons no-print">
<div className={`floating-buttons-children ${!visible ? "temporarily-hidden" : ""}`}>
{context && items.map((Component) => (
<Component {...context} />
))}
{visible && <CloseFloatingButton setVisible={setVisible} />}
</div>
{!visible && <ShowFloatingButton setVisible={setVisible} /> }
</div>
)
}
/**
* Show button that displays floating button after click on close button
*/
function ShowFloatingButton({ setVisible }: { setVisible(visible: boolean): void }) {
return (
<div className="show-floating-buttons">
<ActionButton
className="show-floating-buttons-button"
icon="bx bx-chevrons-left"
text={t("show_floating_buttons_button.button_title")}
onClick={() => setVisible(true)}
noIconActionClass
/>
</div>
);
}
function CloseFloatingButton({ setVisible }: { setVisible(visible: boolean): void }) {
return (
<div className="close-floating-buttons">
<ActionButton
className="close-floating-buttons-button"
icon="bx bx-chevrons-right"
text={t("hide_floating_buttons_button.button_title")}
onClick={() => setVisible(false)}
noIconActionClass
/>
</div>
);
}

View File

@@ -1,398 +0,0 @@
import { VNode } from "preact";
import appContext, { EventData, EventNames } from "../components/app_context";
import Component from "../components/component";
import NoteContext from "../components/note_context";
import FNote from "../entities/fnote";
import ActionButton, { ActionButtonProps } from "./react/ActionButton";
import { useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
import server from "../services/server";
import { BacklinkCountResponse, BacklinksResponse, SaveSqlConsoleResponse } from "@triliumnext/commons";
import toast from "../services/toast";
import { t } from "../services/i18n";
import { copyImageReferenceToClipboard } from "../services/image";
import tree from "../services/tree";
import protected_session_holder from "../services/protected_session_holder";
import options from "../services/options";
import { getHelpUrlForNote } from "../services/in_app_help";
import froca from "../services/froca";
import NoteLink from "./react/NoteLink";
import RawHtml from "./react/RawHtml";
import { ViewTypeOptions } from "../services/note_list_renderer";
export interface FloatingButtonContext {
parentComponent: Component;
note: FNote;
noteContext: NoteContext;
isDefaultViewMode: boolean;
isReadOnly: boolean;
/** Shorthand for triggering an event from the parent component. The `ntxId` is automatically handled for convenience. */
triggerEvent<T extends EventNames>(name: T, data?: Omit<EventData<T>, "ntxId">): void;
viewType?: ViewTypeOptions | null;
}
function FloatingButton({ className, ...props }: ActionButtonProps) {
return <ActionButton
className={`floating-button ${className ?? ""}`}
noIconActionClass
{...props}
/>
}
export type FloatingButtonsList = ((context: FloatingButtonContext) => false | VNode)[];
export const DESKTOP_FLOATING_BUTTONS: FloatingButtonsList = [
RefreshBackendLogButton,
SwitchSplitOrientationButton,
ToggleReadOnlyButton,
EditButton,
ShowTocWidgetButton,
ShowHighlightsListWidgetButton,
RunActiveNoteButton,
OpenTriliumApiDocsButton,
SaveToNoteButton,
RelationMapButtons,
GeoMapButtons,
CopyImageReferenceButton,
ExportImageButtons,
InAppHelpButton,
Backlinks
];
export const MOBILE_FLOATING_BUTTONS: FloatingButtonsList = [
RefreshBackendLogButton,
EditButton,
RelationMapButtons,
ExportImageButtons,
Backlinks
]
function RefreshBackendLogButton({ note, parentComponent, noteContext, isDefaultViewMode }: FloatingButtonContext) {
const isEnabled = note.noteId === "_backendLog" && isDefaultViewMode;
return isEnabled && <FloatingButton
text={t("backend_log.refresh")}
icon="bx bx-refresh"
onClick={() => parentComponent.triggerEvent("refreshData", { ntxId: noteContext.ntxId })}
/>
}
function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: FloatingButtonContext) {
const isEnabled = note.type === "mermaid" && note.isContentAvailable() && !isReadOnly && isDefaultViewMode;
const [ splitEditorOrientation, setSplitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
const upcomingOrientation = splitEditorOrientation === "horizontal" ? "vertical" : "horizontal";
return isEnabled && <FloatingButton
text={upcomingOrientation === "vertical" ? t("switch_layout_button.title_vertical") : t("switch_layout_button.title_horizontal")}
icon={upcomingOrientation === "vertical" ? "bx bxs-dock-bottom" : "bx bxs-dock-left"}
onClick={() => setSplitEditorOrientation(upcomingOrientation)}
/>
}
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const isEnabled = (note.type === "mermaid" || viewType === "geoMap")
&& note.isContentAvailable() && isDefaultViewMode;
return isEnabled && <FloatingButton
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
onClick={() => setReadOnly(!isReadOnly)}
/>
}
function EditButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
const [ animationClass, setAnimationClass ] = useState("");
const [ isEnabled, setIsEnabled ] = useState(false);
useEffect(() => {
noteContext.isReadOnly().then(isReadOnly => {
setIsEnabled(
isDefaultViewMode
&& (!note.isProtected || protected_session_holder.isProtectedSessionAvailable())
&& !options.is("databaseReadonly")
&& isReadOnly
);
});
}, [ note ]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsEnabled(false);
}
});
// make the edit button stand out on the first display, otherwise
// it's difficult to notice that the note is readonly
useEffect(() => {
if (isEnabled) {
setAnimationClass("bx-tada bx-lg");
setTimeout(() => {
setAnimationClass("");
}, 1700);
}
}, [ isEnabled ]);
return isEnabled && <FloatingButton
text={t("edit_button.edit_this_note")}
icon="bx bx-pencil"
className={animationClass}
onClick={() => {
if (noteContext.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext });
}
}}
/>
}
function ShowTocWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
const [ isEnabled, setIsEnabled ] = useState(false);
useTriliumEvent("reEvaluateTocWidgetVisibility", () => {
setIsEnabled(note.type === "text" && isDefaultViewMode && !!noteContext.viewScope?.tocTemporarilyHidden);
});
return isEnabled && <FloatingButton
text={t("show_toc_widget_button.show_toc")}
icon="bx bx-tn-toc"
onClick={() => {
if (noteContext?.viewScope && noteContext.noteId) {
noteContext.viewScope.tocTemporarilyHidden = false;
appContext.triggerEvent("showTocWidget", { noteId: noteContext.noteId });
}
}}
/>
}
function ShowHighlightsListWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
const [ isEnabled, setIsEnabled ] = useState(false);
useTriliumEvent("reEvaluateHighlightsListWidgetVisibility", () => {
setIsEnabled(note.type === "text" && isDefaultViewMode && !!noteContext.viewScope?.highlightsListTemporarilyHidden);
});
return isEnabled && <FloatingButton
text={t("show_highlights_list_widget_button.show_highlights_list")}
icon="bx bx-bookmarks"
onClick={() => {
if (noteContext?.viewScope && noteContext.noteId) {
noteContext.viewScope.highlightsListTemporarilyHidden = false;
appContext.triggerEvent("showHighlightsListWidget", { noteId: noteContext.noteId });
}
}}
/>
}
function RunActiveNoteButton({ note }: FloatingButtonContext) {
const isEnabled = note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium";
return isEnabled && <FloatingButton
icon="bx bx-play"
text={t("code_buttons.execute_button_title")}
triggerCommand="runActiveNote"
/>
}
function OpenTriliumApiDocsButton({ note }: FloatingButtonContext) {
const isEnabled = note.mime.startsWith("application/javascript;env=");
return isEnabled && <FloatingButton
icon="bx bx-help-circle"
text={t("code_buttons.trilium_api_docs_button_title")}
onClick={() => openInAppHelpFromUrl(note.mime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")}
/>
}
function SaveToNoteButton({ note }: FloatingButtonContext) {
const isEnabled = note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely();
return isEnabled && <FloatingButton
icon="bx bx-save"
text={t("code_buttons.save_to_note_button_title")}
onClick={async (e) => {
e.preventDefault();
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: note.noteId });
if (notePath) {
toast.showMessage(t("code_buttons.sql_console_saved_message", { "note_path": await tree.getNotePathTitle(notePath) }));
// TODO: This hangs the navigation, for some reason.
//await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext()?.setNote(notePath);
}
}}
/>
}
function RelationMapButtons({ note, triggerEvent }: FloatingButtonContext) {
const isEnabled = (note.type === "relationMap");
return isEnabled && (
<>
<FloatingButton
icon="bx bx-folder-plus"
text={t("relation_map_buttons.create_child_note_title")}
onClick={() => triggerEvent("relationMapCreateChildNote")}
/>
<FloatingButton
icon="bx bx-crop"
text={t("relation_map_buttons.reset_pan_zoom_title")}
onClick={() => triggerEvent("relationMapResetPanZoom")}
/>
<div className="btn-group">
<FloatingButton
icon="bx bx-zoom-in"
text={t("relation_map_buttons.zoom_in_title")}
onClick={() => triggerEvent("relationMapResetZoomIn")}
/>
<FloatingButton
icon="bx bx-zoom-out"
text={t("relation_map_buttons.zoom_out_title")}
onClick={() => triggerEvent("relationMapResetZoomOut")}
/>
</div>
</>
)
}
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
const isEnabled = viewType === "geoMap" && !isReadOnly;
return isEnabled && (
<FloatingButton
icon="bx bx-plus-circle"
text={t("geo-map.create-child-note-title")}
onClick={() => triggerEvent("geoMapCreateChildNote")}
/>
);
}
function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonContext) {
const hiddenImageCopyRef = useRef<HTMLDivElement>(null);
const isEnabled = ["mermaid", "canvas", "mindMap"].includes(note?.type ?? "")
&& note?.isContentAvailable() && isDefaultViewMode;
return isEnabled && (
<>
<FloatingButton
icon="bx bx-copy"
text={t("copy_image_reference_button.button_title")}
onClick={() => {
if (!hiddenImageCopyRef.current) return;
const imageEl = document.createElement("img");
imageEl.src = createImageSrcUrl(note);
hiddenImageCopyRef.current.replaceChildren(imageEl);
copyImageReferenceToClipboard($(hiddenImageCopyRef.current));
hiddenImageCopyRef.current.removeChild(imageEl);
}}
/>
<div ref={hiddenImageCopyRef} className="hidden-image-copy" style={{
position: "absolute" // Take out of the the hidden image from flexbox to prevent the layout being affected
}} />
</>
)
}
function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingButtonContext) {
const isEnabled = ["mermaid", "mindMap"].includes(note?.type ?? "")
&& note?.isContentAvailable() && isDefaultViewMode;
return isEnabled && (
<>
<FloatingButton
icon="bx bxs-file-image"
text={t("svg_export_button.button_title")}
onClick={() => triggerEvent("exportSvg")}
/>
<FloatingButton
icon="bx bxs-file-png"
text={t("png_export_button.button_title")}
onClick={() => triggerEvent("exportPng")}
/>
</>
)
}
function InAppHelpButton({ note }: FloatingButtonContext) {
const helpUrl = getHelpUrlForNote(note);
return !!helpUrl && (
<FloatingButton
icon="bx bx-help-circle"
text={t("help-button.title")}
onClick={() => helpUrl && openInAppHelpFromUrl(helpUrl)}
/>
)
}
function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
let [ backlinkCount, setBacklinkCount ] = useState(0);
let [ popupOpen, setPopupOpen ] = useState(false);
const backlinksContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isDefaultViewMode) return;
server.get<BacklinkCountResponse>(`note-map/${note.noteId}/backlink-count`).then(resp => {
setBacklinkCount(resp.count);
});
}, [ note ]);
// Determine the max height of the container.
const { windowHeight } = useWindowSize();
useLayoutEffect(() => {
const el = backlinksContainerRef.current;
if (popupOpen && el) {
const box = el.getBoundingClientRect();
const maxHeight = windowHeight - box.top - 10;
el.style.maxHeight = `${maxHeight}px`;
}
}, [ popupOpen, windowHeight ]);
const isEnabled = isDefaultViewMode && backlinkCount > 0;
return (isEnabled &&
<div className="backlinks-widget has-overflow">
<div
className="backlinks-ticker"
onClick={() => setPopupOpen(!popupOpen)}
>
<span className="backlinks-count">{t("zpetne_odkazy.backlink", { count: backlinkCount })}</span>
</div>
{popupOpen && (
<div ref={backlinksContainerRef} className="backlinks-items dropdown-menu" style={{ display: "block" }}>
<BacklinksList noteId={note.noteId} />
</div>
)}
</div>
);
}
function BacklinksList({ noteId }: { noteId: string }) {
const [ backlinks, setBacklinks ] = useState<BacklinksResponse>([]);
useEffect(() => {
server.get<BacklinksResponse>(`note-map/${noteId}/backlinks`).then(async (backlinks) => {
// prefetch all
const noteIds = backlinks
.filter(bl => "noteId" in bl)
.map((bl) => bl.noteId);
await froca.getNotes(noteIds);
setBacklinks(backlinks);
});
}, [ noteId ]);
return backlinks.map(backlink => (
<div>
<NoteLink
notePath={backlink.noteId}
showNotePath showNoteIcon
noPreview
/>
{"relationName" in backlink ? (
<p>{backlink.relationName}</p>
) : (
backlink.excerpts.map(excerpt => (
<RawHtml html={excerpt} />
))
)}
</div>
));
}

View File

@@ -1,28 +0,0 @@
.api-log-widget {
flex-grow: 1;
max-height: 40%;
position: relative;
border-top: 1px solid var(--main-border-color);
background-color: var(--accented-background-color);
}
.api-log-container {
overflow: auto;
height: 100%;
font-family: var(--monospace-font-family);
font-size: 0.8em;
white-space: pre;
padding: 15px;
}
.close-api-log-button {
padding: 5px;
border: 1px solid var(--button-border-color);
background-color: var(--button-background-color);
border-radius: var(--button-border-radius);
color: var(--button-text-color);
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
}

View File

@@ -0,0 +1,80 @@
import type { EventData } from "../components/app_context.js";
import type FNote from "../entities/fnote.js";
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
const TPL = /*html*/`
<div class="api-log-widget">
<style>
.api-log-widget {
padding: 15px;
flex-grow: 1;
max-height: 40%;
position: relative;
}
.hidden-api-log {
display: none;
}
.api-log-container {
overflow: auto;
height: 100%;
}
.close-api-log-button {
padding: 5px;
border: 1px solid var(--button-border-color);
background-color: var(--button-background-color);
border-radius: var(--button-border-radius);
color: var(--button-text-color);
position: absolute;
top: 10px;
right: 40px;
cursor: pointer;
}
</style>
<div class="bx bx-x close-api-log-button" title="${t("api_log.close")}"></div>
<div class="api-log-container"></div>
</div>`;
export default class ApiLogWidget extends NoteContextAwareWidget {
private $logContainer!: JQuery<HTMLElement>;
private $closeButton!: JQuery<HTMLElement>;
isEnabled() {
return !!this.note && this.note.mime.startsWith("application/javascript;env=") && super.isEnabled();
}
doRender() {
this.$widget = $(TPL);
this.toggle(false);
this.$logContainer = this.$widget.find(".api-log-container");
this.$closeButton = this.$widget.find(".close-api-log-button");
this.$closeButton.on("click", () => this.toggle(false));
}
async refreshWithNote(note: FNote) {
this.$logContainer.empty();
}
apiLogMessagesEvent({ messages, noteId }: EventData<"apiLogMessages">) {
if (!this.isNote(noteId)) {
return;
}
this.toggle(true);
for (const message of messages) {
this.$logContainer.append(message).append($("<br>"));
}
}
toggle(show: boolean) {
this.$widget.toggleClass("hidden-api-log", !show);
}
}

View File

@@ -1,41 +0,0 @@
import { useEffect, useState } from "preact/hooks";
import "./api_log.css";
import { useNoteContext, useTriliumEvent } from "./react/hooks";
import ActionButton from "./react/ActionButton";
import { t } from "../services/i18n";
/**
* Displays the messages that are logged by the current note via `api.log`, for frontend and backend scripts.
*/
export default function ApiLog() {
const { note, noteId } = useNoteContext();
const [ messages, setMessages ] = useState<string[]>();
useTriliumEvent("apiLogMessages", ({ messages, noteId: eventNoteId }) => {
if (eventNoteId !== noteId) return;
setMessages(messages);
});
// Clear when navigating away.
useEffect(() => setMessages(undefined), [ note ]);
const isEnabled = note?.mime.startsWith("application/javascript;env=") && messages?.length;
return (
<div className={`api-log-widget ${!isEnabled ? "hidden-ext" : ""}`}>
{isEnabled && (
<>
<ActionButton
icon="bx bx-x"
className="close-api-log-button"
text={t("api_log.close")}
onClick={() => setMessages(undefined)}
/>
<div className="api-log-container">
{messages.join("\n")}
</div>
</>
)}
</div>
)
}

View File

@@ -0,0 +1,504 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js";
import server from "../../services/server.js";
import contextMenuService from "../../menus/context_menu.js";
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5";
import froca from "../../services/froca.js";
import attributeRenderer from "../../services/attribute_renderer.js";
import noteCreateService from "../../services/note_create.js";
import attributeService from "../../services/attributes.js";
import linkService from "../../services/link.js";
import type AttributeDetailWidget from "./attribute_detail.js";
import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js";
import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js";
import type FNote from "../../entities/fnote.js";
import { escapeQuotes } from "../../services/utils.js";
const HELP_TEXT = `
<p>${t("attribute_editor.help_text_body1")}</p>
<p>${t("attribute_editor.help_text_body2")}</p>
<p>${t("attribute_editor.help_text_body3")}</p>`;
const TPL = /*html*/`
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
<style>
.attribute-list-editor {
border: 0 !important;
outline: 0 !important;
box-shadow: none !important;
padding: 0 0 0 5px !important;
margin: 0 !important;
max-height: 100px;
overflow: auto;
transition: opacity .1s linear;
}
.attribute-list-editor.ck-content .mention {
color: var(--muted-text-color) !important;
background: transparent !important;
}
.save-attributes-button {
color: var(--muted-text-color);
position: absolute;
bottom: 14px;
right: 25px;
cursor: pointer;
border: 1px solid transparent;
font-size: 130%;
}
.add-new-attribute-button {
color: var(--muted-text-color);
position: absolute;
bottom: 13px;
right: 0;
cursor: pointer;
border: 1px solid transparent;
font-size: 130%;
}
.add-new-attribute-button:hover, .save-attributes-button:hover {
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
background: var(--button-background-color);
color: var(--button-text-color);
}
.attribute-errors {
color: red;
padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */
}
</style>
<div class="attribute-list-editor" tabindex="200"></div>
<div class="bx bx-save save-attributes-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.save_attributes"))}"></div>
<div class="bx bx-plus add-new-attribute-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
<div class="attribute-errors" style="display: none;"></div>
</div>
`;
const mentionSetup: MentionFeed[] = [
{
marker: "@",
feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
itemRenderer: (_item) => {
const item = _item as Suggestion;
const itemElement = document.createElement("button");
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
return itemElement;
},
minimumCharacters: 0
},
{
marker: "#",
feed: async (queryText) => {
const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
return names.map((name) => {
return {
id: `#${name}`,
name: name
};
});
},
minimumCharacters: 0
},
{
marker: "~",
feed: async (queryText) => {
const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
return names.map((name) => {
return {
id: `~${name}`,
name: name
};
});
},
minimumCharacters: 0
}
];
const editorConfig: EditorConfig = {
toolbar: {
items: []
},
placeholder: t("attribute_editor.placeholder"),
mention: {
feeds: mentionSetup
},
licenseKey: "GPL"
};
type AttributeCommandNames = FilteredCommandNames<CommandData>;
export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> {
private attributeDetailWidget: AttributeDetailWidget;
private $editor!: JQuery<HTMLElement>;
private $addNewAttributeButton!: JQuery<HTMLElement>;
private $saveAttributesButton!: JQuery<HTMLElement>;
private $errors!: JQuery<HTMLElement>;
private textEditor!: AttributeEditor;
private lastUpdatedNoteId!: string | undefined;
private lastSavedContent!: string;
constructor(attributeDetailWidget: AttributeDetailWidget) {
super();
this.attributeDetailWidget = attributeDetailWidget;
}
doRender() {
this.$widget = $(TPL);
this.$editor = this.$widget.find(".attribute-list-editor");
this.initialized = this.initEditor();
this.$editor.on("keydown", async (e) => {
if (e.which === 13) {
// allow autocomplete to fill the result textarea
setTimeout(() => this.save(), 100);
}
this.attributeDetailWidget.hide();
});
this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160
this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button");
this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e));
this.$saveAttributesButton = this.$widget.find(".save-attributes-button");
this.$saveAttributesButton.on("click", () => this.save());
this.$errors = this.$widget.find(".attribute-errors");
}
addNewAttribute(e: JQuery.ClickEvent) {
contextMenuService.show<AttributeCommandNames>({
x: e.pageX,
y: e.pageY,
orientation: "left",
items: [
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ title: "----" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
],
selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command)
});
// Prevent automatic hiding of the context menu due to the button being clicked.
e.stopPropagation();
}
// triggered from keyboard shortcut
async addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) {
if (this.isNoteContext(ntxId)) {
await this.refresh();
this.handleAddNewAttributeCommand("addNewLabel");
}
}
// triggered from keyboard shortcut
async addNewRelationEvent({ ntxId }: EventData<"addNewRelation">) {
if (this.isNoteContext(ntxId)) {
await this.refresh();
this.handleAddNewAttributeCommand("addNewRelation");
}
}
async handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) {
// TODO: Not sure what the relation between FAttribute[] and Attribute[] is.
const attrs = this.parseAttributes() as FAttribute[];
if (!attrs) {
return;
}
let type: AttributeType;
let name;
let value;
if (command === "addNewLabel") {
type = "label";
name = "myLabel";
value = "";
} else if (command === "addNewRelation") {
type = "relation";
name = "myRelation";
value = "";
} else if (command === "addNewLabelDefinition") {
type = "label";
name = "label:myLabel";
value = "promoted,single,text";
} else if (command === "addNewRelationDefinition") {
type = "label";
name = "relation:myRelation";
value = "promoted,single";
} else {
return;
}
// TODO: Incomplete type
//@ts-ignore
attrs.push({
type,
name,
value,
isInheritable: false
});
await this.renderOwnedAttributes(attrs, false);
this.$editor.scrollTop(this.$editor[0].scrollHeight);
const rect = this.$editor[0].getBoundingClientRect();
setTimeout(() => {
// showing a little bit later because there's a conflict with outside click closing the attr detail
this.attributeDetailWidget.showAttributeDetail({
allAttributes: attrs,
attribute: attrs[attrs.length - 1],
isOwned: true,
x: (rect.left + rect.right) / 2,
y: rect.bottom,
focus: "name"
});
}, 100);
}
async save() {
if (this.lastUpdatedNoteId !== this.noteId) {
// https://github.com/zadam/trilium/issues/3090
console.warn("Ignoring blur event because a different note is loaded.");
return;
}
const attributes = this.parseAttributes();
if (attributes) {
await server.put(`notes/${this.noteId}/attributes`, attributes, this.componentId);
this.$saveAttributesButton.fadeOut();
// blink the attribute text to give a visual hint that save has been executed
this.$editor.css("opacity", 0);
// revert back
setTimeout(() => this.$editor.css("opacity", 1), 100);
}
}
parseAttributes() {
try {
return attributeParser.lexAndParse(this.getPreprocessedData());
} catch (e: any) {
this.$errors.text(e.message).slideDown();
}
}
getPreprocessedData() {
const str = this.textEditor
.getData()
.replace(/<a[^>]+href="(#[A-Za-z0-9_/]*)"[^>]*>[^<]*<\/a>/g, "$1")
.replace(/&nbsp;/g, " "); // otherwise .text() below outputs non-breaking space in unicode
return $("<div>").html(str).text();
}
async initEditor() {
this.$widget.show();
this.$editor.on("click", (e) => this.handleEditorClick(e));
this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig);
this.textEditor.model.document.on("change:data", () => this.dataChanged());
this.textEditor.editing.view.document.on(
"enter",
(event, data) => {
// disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422
data.preventDefault();
event.stop();
},
{ priority: "high" }
);
// disable spellcheck for attribute editor
const documentRoot = this.textEditor.editing.view.document.getRoot();
if (documentRoot) {
this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot));
}
}
dataChanged() {
this.lastUpdatedNoteId = this.noteId;
if (this.lastSavedContent === this.textEditor.getData()) {
this.$saveAttributesButton.fadeOut();
} else {
this.$saveAttributesButton.fadeIn();
}
if (this.$errors.is(":visible")) {
// using .hide() instead of .slideUp() since this will also hide the error after confirming
// mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird
this.$errors.hide();
}
}
async handleEditorClick(e: JQuery.ClickEvent) {
const pos = this.textEditor.model.document.selection.getFirstPosition();
if (pos && pos.textNode && pos.textNode.data) {
const clickIndex = this.getClickIndex(pos);
let parsedAttrs;
try {
parsedAttrs = attributeParser.lexAndParse(this.getPreprocessedData(), true);
} catch (e) {
// the input is incorrect because the user messed up with it and now needs to fix it manually
return null;
}
let matchedAttr: Attribute | null = null;
for (const attr of parsedAttrs) {
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr;
break;
}
}
setTimeout(() => {
if (matchedAttr) {
this.$editor.tooltip("hide");
this.attributeDetailWidget.showAttributeDetail({
allAttributes: parsedAttrs,
attribute: matchedAttr,
isOwned: true,
x: e.pageX,
y: e.pageY
});
} else {
this.showHelpTooltip();
}
}, 100);
} else {
this.showHelpTooltip();
}
}
showHelpTooltip() {
this.attributeDetailWidget.hide();
this.$editor.tooltip({
trigger: "focus",
html: true,
title: HELP_TEXT,
placement: "bottom",
offset: "0,30"
});
this.$editor.tooltip("show");
}
getClickIndex(pos: ModelPosition) {
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);
let curNode: ModelNode | Text | ModelElement | null = pos.textNode;
while (curNode?.previousSibling) {
curNode = curNode.previousSibling;
if ((curNode as ModelElement).name === "reference") {
clickIndex += (curNode.getAttribute("href") as string).length + 1;
} else if ("data" in curNode) {
clickIndex += (curNode.data as string).length;
}
}
return clickIndex;
}
async loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string) {
const { noteId } = linkService.parseNavigationStateFromUrl(href);
const note = noteId ? await froca.getNote(noteId, true) : null;
const title = note ? note.title : "[missing]";
$el.text(title);
}
async refreshWithNote(note: FNote) {
await this.renderOwnedAttributes(note.getOwnedAttributes(), true);
}
async renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) {
// attrs are not resorted if position changes after the initial load
ownedAttributes.sort((a, b) => a.position - b.position);
let htmlAttrs = (await attributeRenderer.renderAttributes(ownedAttributes, true)).html();
if (htmlAttrs.length > 0) {
htmlAttrs += "&nbsp;";
}
this.textEditor.setData(htmlAttrs);
if (saved) {
this.lastSavedContent = this.textEditor.getData();
this.$saveAttributesButton.fadeOut(0);
}
}
async createNoteForReferenceLink(title: string) {
let result;
if (this.notePath) {
result = await noteCreateService.createNoteWithTypePrompt(this.notePath, {
activate: false,
title: title
});
}
return result?.note?.getBestNotePathString();
}
async updateAttributeList(attributes: FAttribute[]) {
await this.renderOwnedAttributes(attributes, false);
}
focus() {
this.$editor.trigger("focus");
this.textEditor.model.change((writer) => {
const documentRoot = this.textEditor.editing.model.document.getRoot();
if (!documentRoot) {
return;
}
const positionAt = writer.createPositionAt(documentRoot, "end");
writer.setSelection(positionAt);
});
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.getAttributeRows(this.componentId).find((attr) => attributeService.isAffecting(attr, this.note))) {
this.refresh();
}
}
}

View File

@@ -1,11 +1,7 @@
import { isValidElement, VNode } from "preact";
import Component, { TypedComponent } from "../components/component.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import toastService from "../services/toast.js";
import { renderReactWidget } from "./react/react_utils.jsx";
import { EventNames, EventData } from "../components/app_context.js";
import { Handler } from "leaflet";
export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> {
protected attrs: Record<string, string>;
@@ -26,14 +22,11 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
this.childPositionCounter = 10;
}
child(..._components: (T | VNode)[]) {
if (!_components) {
child(...components: T[]) {
if (!components) {
return this;
}
// Convert any React components to legacy wrapped components.
const components = wrapReactWidgets(_components);
super.child(...components);
for (const component of components) {
@@ -55,7 +48,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
* @param components the components to be added as children to this component provided the condition is truthy.
* @returns self for chaining.
*/
optChild(condition: boolean, ...components: (T | VNode)[]) {
optChild(condition: boolean, ...components: T[]) {
if (condition) {
return this.child(...components);
} else {
@@ -265,30 +258,3 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
* For information on using widgets, see the tutorial {@tutorial widget_basics}.
*/
export default class BasicWidget extends TypedBasicWidget<Component> {}
export function wrapReactWidgets<T extends TypedComponent<any>>(components: (T | VNode)[]) {
const wrappedResult: T[] = [];
for (const component of components) {
if (isValidElement(component)) {
wrappedResult.push(new ReactWrappedWidget(component) as unknown as T);
} else {
wrappedResult.push(component);
}
}
return wrappedResult;
}
export class ReactWrappedWidget extends BasicWidget {
private el: VNode;
constructor(el: VNode) {
super();
this.el = el;
}
doRender() {
this.$widget = renderReactWidget(this, this.el);
}
}

View File

@@ -0,0 +1,54 @@
import SwitchWidget from "./switch.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
import { t } from "../services/i18n.js";
import type FNote from "../entities/fnote.js";
import type { EventData } from "../components/app_context.js";
// TODO: Deduplicate
type Response = {
success: true;
} | {
success: false;
message: string;
}
export default class BookmarkSwitchWidget extends SwitchWidget {
isEnabled() {
return (
super.isEnabled() &&
// it's not possible to bookmark root because that would clone it under bookmarks and thus create a cycle
!["root", "_hidden"].includes(this.noteId ?? "")
);
}
doRender() {
super.doRender();
this.switchOnName = t("bookmark_switch.bookmark");
this.switchOnTooltip = t("bookmark_switch.bookmark_this_note");
this.switchOffName = t("bookmark_switch.bookmark");
this.switchOffTooltip = t("bookmark_switch.remove_bookmark");
}
async toggle(state: boolean | null | undefined) {
const resp = await server.put<Response>(`notes/${this.noteId}/toggle-in-parent/_lbBookmarks/${!!state}`);
if (!resp.success && "message" in resp) {
toastService.showError(resp.message);
}
}
async refreshWithNote(note: FNote) {
const isBookmarked = !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks");
this.isToggled = isBookmarked;
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.getBranchRows().find((b) => b.noteId === this.noteId)) {
this.refresh();
}
}
}

View File

@@ -1,7 +1,6 @@
import { ComponentChildren } from "preact";
import { memo } from "preact/compat";
import AbstractBulkAction from "./abstract_bulk_action";
import HelpRemoveButtons from "../react/HelpRemoveButtons";
interface BulkActionProps {
label: string | ComponentChildren;
@@ -25,11 +24,19 @@ const BulkAction = memo(({ label, children, helpText, bulkAction }: BulkActionPr
{children}
</div>
</td>
<HelpRemoveButtons
help={helpText}
removeText="Delete"
onRemove={() => bulkAction?.deleteAction()}
/>
<td className="button-column">
{helpText && <div className="dropdown help-dropdown">
<span className="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div className="dropdown-menu dropdown-menu-right p-4">
{helpText}
</div>
</div>}
<span
className="bx bx-x icon-action action-conf-del"
onClick={() => bulkAction?.deleteAction()}
/>
</td>
</tr>
);
});

View File

@@ -1,11 +1,11 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
import { useEffect, useState } from "preact/hooks";
import { useSpacedUpdate } from "../../react/hooks.jsx";
function MoveNoteBulkActionComponent({ bulkAction }: { bulkAction: AbstractBulkAction }) {
function MoveNoteBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ targetParentNoteId, setTargetParentNoteId ] = useState<string>();
const spacedUpdate = useSpacedUpdate(() => {
return bulkAction.saveAction({ targetParentNoteId: targetParentNoteId })
@@ -45,6 +45,6 @@ export default class MoveNoteBulkAction extends AbstractBulkAction {
}
doRender() {
return <MoveNoteBulkActionComponent bulkAction={this} />
return <MoveNoteBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,3 +1,4 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
import BulkAction from "../BulkAction.jsx";

View File

@@ -1,4 +1,6 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
import { t } from "../../../services/i18n.js";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";

View File

@@ -1,102 +0,0 @@
.global-menu {
width: 53px;
height: 53px;
flex-shrink: 0;
}
.global-menu .dropdown-menu {
min-width: 20em;
}
.global-menu-button {
width: 100% !important;
height: 100% !important;
position: relative;
padding: 6px;
border: 0;
}
.global-menu-button svg path {
fill: var(--launcher-pane-text-color);
}
.global-menu-button:hover { border: 0; }
.global-menu-button:hover svg path {
transition: 200ms ease-in-out fill;
}
.global-menu-button:hover svg path.st0 { fill:#95C980; }
.global-menu-button:hover svg path.st1 { fill:#72B755; }
.global-menu-button:hover svg path.st2 { fill:#4FA52B; }
.global-menu-button:hover svg path.st3 { fill:#EE8C89; }
.global-menu-button:hover svg path.st4 { fill:#E96562; }
.global-menu-button:hover svg path.st5 { fill:#E33F3B; }
.global-menu-button:hover svg path.st6 { fill:#EFB075; }
.global-menu-button:hover svg path.st7 { fill:#E99547; }
.global-menu-button:hover svg path.st8 { fill:#E47B19; }
.global-menu-button-update-available {
position: absolute;
right: -30px;
bottom: -30px;
width: 100%;
height: 100%;
pointer-events: none;
}
.global-menu .zoom-container {
display: flex;
flex-direction: row;
align-items: baseline;
}
.global-menu .zoom-buttons {
margin-left: 2em;
}
.global-menu .zoom-buttons a {
display: inline-block;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
color: var(--button-text-color);
background-color: var(--button-background-color);
padding: 3px;
margin-left: 3px;
text-decoration: none;
}
.global-menu .zoom-buttons a:hover {
text-decoration: none;
}
.global-menu .zoom-state {
margin-left: 5px;
margin-right: 5px;
}
.global-menu .dropdown-item .bx {
position: relative;
top: 3px;
font-size: 120%;
margin-right: 6px;
}
/* #region Update available */
.global-menu-button-update-available-button {
width: 21px !important;
height: 21px !important;
padding: 0 !important;
border-radius: var(--button-border-radius);
transform: scale(0.9);
border: none;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
}
.global-menu-button-wrapper:hover .global-menu-button-update-available-button {
opacity: 1;
}
/* #endregion */

View File

@@ -0,0 +1,436 @@
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import utils from "../../services/utils.js";
import UpdateAvailableWidget from "./update_available.js";
import options from "../../services/options.js";
import { Tooltip, Dropdown } from "bootstrap";
const TPL = /*html*/`
<div class="dropdown global-menu">
<style>
.global-menu {
width: 53px;
height: 53px;
flex-shrink: 0;
}
.global-menu .dropdown-menu {
min-width: 20em;
}
.global-menu-button {
width: 100%;
height: 100%;
position: relative;
padding: 6px;
border: 0;
}
.global-menu-button > svg path {
fill: var(--launcher-pane-text-color);
}
.global-menu-button:hover { border: 0; }
.global-menu-button:hover > svg path {
transition: 200ms ease-in-out fill;
}
.global-menu-button:hover > svg path.st0 { fill:#95C980; }
.global-menu-button:hover > svg path.st1 { fill:#72B755; }
.global-menu-button:hover > svg path.st2 { fill:#4FA52B; }
.global-menu-button:hover > svg path.st3 { fill:#EE8C89; }
.global-menu-button:hover > svg path.st4 { fill:#E96562; }
.global-menu-button:hover > svg path.st5 { fill:#E33F3B; }
.global-menu-button:hover > svg path.st6 { fill:#EFB075; }
.global-menu-button:hover > svg path.st7 { fill:#E99547; }
.global-menu-button:hover > svg path.st8 { fill:#E47B19; }
.global-menu-button-update-available {
position: absolute;
right: -30px;
bottom: -30px;
width: 100%;
height: 100%;
pointer-events: none;
}
.global-menu .zoom-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
}
.global-menu .zoom-buttons a {
display: inline-block;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
color: var(--button-text-color);
background-color: var(--button-background-color);
padding: 3px;
margin-left: 3px;
text-decoration: none;
}
.global-menu .zoom-buttons a:hover {
text-decoration: none;
}
.global-menu .zoom-state {
margin-left: 5px;
margin-right: 5px;
}
.global-menu .dropdown-item .bx {
position: relative;
top: 3px;
font-size: 120%;
margin-right: 6px;
}
</style>
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action global-menu-button">
<div class="global-menu-button-update-available"></div>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li class="dropdown-item" data-trigger-command="openNewWindow">
<span class="bx bx-window-open"></span>
${t("global_menu.open_new_window")}
<kbd data-command="openNewWindow"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showShareSubtree">
<span class="bx bx-share-alt"></span>
${t("global_menu.show_shared_notes_subtree")}
</li>
<div class="dropdown-divider"></div>
<span class="zoom-container dropdown-item dropdown-item-container">
<div>
<span class="bx bx-empty"></span>
${t("global_menu.zoom")}
</div>
<div class="zoom-buttons">
<a data-trigger-command="toggleFullscreen" title="${t("global_menu.toggle_fullscreen")}" class="bx bx-expand-alt"></a>
&nbsp;
<a data-trigger-command="zoomOut" title="${t("global_menu.zoom_out")}" class="bx bx-minus"></a>
<span data-trigger-command="zoomReset" title="${t("global_menu.reset_zoom_level")}" class="zoom-state"></span>
<a data-trigger-command="zoomIn" title="${t("global_menu.zoom_in")}" class="bx bx-plus"></a>
</div>
</span>
<li class="dropdown-item toggle-pin">
<span class="bx bx-pin"></span>
${t("title_bar_buttons.window-on-top")}
</li>
<li class="dropdown-item" data-trigger-command="toggleZenMode">
<span class="bx bxs-yin-yang"></span>
${t("global_menu.toggle-zen-mode")}
<kbd data-command="toggleZenMode"></kbd>
</li>
<div class="dropdown-divider desktop-only"></div>
<li class="dropdown-item switch-to-mobile-version-button" data-trigger-command="switchToMobileVersion">
<span class="bx bx-mobile"></span>
${t("global_menu.switch_to_mobile_version")}
</li>
<li class="dropdown-item switch-to-desktop-version-button" data-trigger-command="switchToDesktopVersion">
<span class="bx bx-desktop"></span>
${t("global_menu.switch_to_desktop_version")}
</li>
<li class="dropdown-item" data-trigger-command="showLaunchBarSubtree">
<span class="bx ${utils.isMobile() ? "bx-mobile" : "bx-sidebar"}"></span>
${t("global_menu.configure_launchbar")}
</li>
<li class="dropdown-item dropdown-submenu">
<span class="dropdown-toggle">
<span class="bx bx-chip"></span>${t("global_menu.advanced")}
</span>
<ul class="dropdown-menu">
<li class="dropdown-item" data-trigger-command="showHiddenSubtree">
<span class="bx bx-hide"></span>
${t("global_menu.show_hidden_subtree")}
</li>
<li class="dropdown-item" data-trigger-command="showSearchHistory">
<span class="bx bx-search-alt"></span>
${t("global_menu.open_search_history")}
</li>
<div class="dropdown-divider"></div>
<li class="dropdown-item" data-trigger-command="showBackendLog">
<span class="bx bx-detail"></span>
${t("global_menu.show_backend_log")}
<kbd data-command="showBackendLog"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showSQLConsole">
<span class="bx bx-data"></span>
${t("global_menu.open_sql_console")}
<kbd data-command="showSQLConsole"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
<span class="bx bx-data"></span>
${t("global_menu.open_sql_console_history")}
</li>
<div class="dropdown-divider"></div>
<li class="dropdown-item open-dev-tools-button" data-trigger-command="openDevTools">
<span class="bx bx-bug-alt"></span>
${t("global_menu.open_dev_tools")}
<kbd data-command="openDevTools"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
title="${t("global_menu.reload_hint")}">
<span class="bx bx-refresh"></span>
${t("global_menu.reload_frontend")}
<kbd data-command="reloadFrontendApp"></kbd>
</li>
</ul>
</li>
<li class="dropdown-item" data-trigger-command="showOptions">
<span class="bx bx-cog"></span>
${t("global_menu.options")}
</li>
<div class="dropdown-divider desktop-only"></div>
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
<span class="bx bx-help-circle"></span>
${t("global_menu.show_help")}
<kbd data-command="showHelp"></kbd>
</li>
<li class="dropdown-item show-help-button" data-trigger-command="showCheatsheet">
<span class="bx bxs-keyboard"></span>
${t("global_menu.show-cheatsheet")}
<kbd data-command="showCheatsheet"></kbd>
</li>
<li class="dropdown-item show-about-dialog-button">
<span class="bx bx-info-circle"></span>
${t("global_menu.about")}
</li>
<li class="dropdown-item update-to-latest-version-button" style="display: none;" data-trigger-command="downloadLatestVersion">
<span class="bx bx-sync"></span>
<span class="version-text"></span>
</li>
<div class="dropdown-divider logout-button-separator"></div>
<li class="dropdown-item logout-button" data-trigger-command="logout">
<span class="bx bx-log-out"></span>
${t("global_menu.logout")}
</li>
</ul>
</div>
`;
export default class GlobalMenuWidget extends BasicWidget {
private updateAvailableWidget: UpdateAvailableWidget;
private isHorizontalLayout: boolean;
private tooltip!: Tooltip;
private dropdown!: Dropdown;
private $updateToLatestVersionButton!: JQuery<HTMLElement>;
private $zoomState!: JQuery<HTMLElement>;
private $toggleZenMode!: JQuery<HTMLElement>;
constructor(isHorizontalLayout: boolean) {
super();
this.updateAvailableWidget = new UpdateAvailableWidget();
this.isHorizontalLayout = isHorizontalLayout;
}
doRender() {
this.$widget = $(TPL);
if (!this.isHorizontalLayout) {
this.$widget.addClass("dropend");
}
const $globalMenuButton = this.$widget.find(".global-menu-button");
if (!this.isHorizontalLayout) {
$globalMenuButton.prepend(
$(`\
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t("global_menu.menu")}">
<g>
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
<path class="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/>
<path class="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/>
<path class="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/>
<path class="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/>
<path class="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/>
<path class="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/>
<path class="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
</g>
</svg>`)
);
this.tooltip = new Tooltip(this.$widget.find("[data-bs-toggle='tooltip']")[0], { trigger: "hover" });
} else {
$globalMenuButton.toggleClass("bx bx-menu");
}
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0], {
popperConfig: {
placement: "bottom"
}
});
this.$widget.find(".show-about-dialog-button").on("click", () => this.triggerCommand("openAboutDialog"));
const isElectron = utils.isElectron();
this.$widget.find(".toggle-pin").toggle(isElectron);
if (isElectron) {
this.$widget.on("click", ".toggle-pin", (e) => {
const $el = $(e.target);
const remote = utils.dynamicRequire("@electron/remote");
const focusedWindow = remote.BrowserWindow.getFocusedWindow();
const isAlwaysOnTop = focusedWindow.isAlwaysOnTop();
if (isAlwaysOnTop) {
focusedWindow.setAlwaysOnTop(false);
$el.removeClass("active");
} else {
focusedWindow.setAlwaysOnTop(true);
$el.addClass("active");
}
});
}
this.$widget.find(".logout-button").toggle(!isElectron);
this.$widget.find(".logout-button-separator").toggle(!isElectron);
this.$widget.find(".open-dev-tools-button").toggle(isElectron);
this.$widget.find(".switch-to-mobile-version-button").toggle(!isElectron && utils.isDesktop());
this.$widget.find(".switch-to-desktop-version-button").toggle(!isElectron && utils.isMobile());
this.$widget.on("click", ".dropdown-item", (e) => {
if ($(e.target).parent(".zoom-buttons")) {
return;
}
this.dropdown.toggle();
});
if (utils.isMobile()) {
this.$widget.on("click", ".dropdown-submenu .dropdown-toggle", (e) => {
const $submenu = $(e.target).closest(".dropdown-item");
$submenu.toggleClass("submenu-open");
$submenu.find("ul.dropdown-menu").toggleClass("show");
e.stopPropagation();
return;
});
}
this.$widget.on("click", ".dropdown-submenu", (e) => {
if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass("dropdown-toggle")) {
e.stopPropagation();
}
});
this.$widget.find(".global-menu-button-update-available").append(this.updateAvailableWidget.render());
this.$updateToLatestVersionButton = this.$widget.find(".update-to-latest-version-button");
if (!utils.isElectron()) {
this.$widget.find(".zoom-container").hide();
}
this.$zoomState = this.$widget.find(".zoom-state");
this.$toggleZenMode = this.$widget.find('[data-trigger-command="toggleZenMode"');
this.$widget.on("show.bs.dropdown", () => this.#onShown());
if (this.tooltip) {
this.$widget.on("hide.bs.dropdown", () => this.tooltip.enable());
}
this.$widget.find(".zoom-buttons").on(
"click",
// delay to wait for the actual zoom change
() => setTimeout(() => this.updateZoomState(), 300)
);
this.updateVersionStatus();
setInterval(() => this.updateVersionStatus(), 8 * 60 * 60 * 1000);
}
#onShown() {
this.$toggleZenMode.toggleClass("active", $("body").hasClass("zen"));
this.updateZoomState();
if (this.tooltip) {
this.tooltip.hide();
this.tooltip.disable();
}
}
updateZoomState() {
if (!utils.isElectron()) {
return;
}
const zoomFactor = utils.dynamicRequire("electron").webFrame.getZoomFactor();
const zoomPercent = Math.round(zoomFactor * 100);
this.$zoomState.text(`${zoomPercent}%`);
}
async updateVersionStatus() {
await options.initializedPromise;
if (options.get("checkForUpdates") !== "true") {
return;
}
const latestVersion = await this.fetchLatestVersion();
this.updateAvailableWidget.updateVersionStatus(latestVersion);
// Show "click to download" button in options menu if there's a new version available
this.$updateToLatestVersionButton.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
this.$updateToLatestVersionButton.find(".version-text").text(`Version ${latestVersion} is available, click to download.`);
}
async fetchLatestVersion() {
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
const resp = await fetch(RELEASES_API_URL);
const data = await resp.json();
return data?.tag_name?.substring(1);
}
downloadLatestVersionCommand() {
window.open("https://github.com/TriliumNext/Trilium/releases/latest");
}
activeContextChangedEvent() {
this.dropdown.hide();
}
noteSwitchedEvent() {
this.dropdown.hide();
}
}

View File

@@ -1,239 +0,0 @@
import Dropdown from "../react/Dropdown";
import "./global_menu.css";
import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool } from "../react/hooks";
import { useContext, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList";
import { CommandNames } from "../../components/app_context";
import KeyboardShortcut from "../react/KeyboardShortcut";
import { KeyboardActionNames } from "@triliumnext/commons";
import { ComponentChildren } from "preact";
import Component from "../../components/component";
import { ParentComponent } from "../react/react_utils";
import utils, { dynamicRequire, isElectron, isMobile } from "../../services/utils";
interface MenuItemProps<T> {
icon: string,
text: ComponentChildren,
title?: string,
command: T,
disabled?: boolean
active?: boolean;
outsideChildren?: ComponentChildren;
}
export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: boolean }) {
const isVerticalLayout = !isHorizontalLayout;
const parentComponent = useContext(ParentComponent);
const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus();
return (
<Dropdown
className="global-menu"
buttonClassName={`global-menu-button ${isHorizontalLayout ? "bx bx-menu" : ""}`} noSelectButtonStyle iconAction hideToggleArrow
text={<>
{isVerticalLayout && <VerticalLayoutIcon />}
{isUpdateAvailable && <div class="global-menu-button-update-available">
<span className="bx bx-sync global-menu-button-update-available-button" title={t("update_available.update_available")}></span>
</div>}
</>}
>
<MenuItem command="openNewWindow" icon="bx bx-window-open" text={t("global_menu.open_new_window")} />
<MenuItem command="showShareSubtree" icon="bx bx-share-alt" text={t("global_menu.show_shared_notes_subtree")} />
<FormDropdownDivider />
<ZoomControls parentComponent={parentComponent} />
<ToggleWindowOnTop />
<KeyboardActionMenuItem command="toggleZenMode" icon="bx bxs-yin-yang" text={t("global_menu.toggle-zen-mode")} />
<FormDropdownDivider />
<SwitchToOptions />
<MenuItem command="showLaunchBarSubtree" icon={`bx ${isMobile() ? "bx-mobile" : "bx-sidebar"}`} text={t("global_menu.configure_launchbar")} />
<AdvancedMenu />
<MenuItem command="showOptions" icon="bx bx-cog" text={t("global_menu.options")} />
<FormDropdownDivider />
<KeyboardActionMenuItem command="showHelp" icon="bx bx-help-circle" text={t("global_menu.show_help")} />
<KeyboardActionMenuItem command="showCheatsheet" icon="bx bxs-keyboard" text={t("global_menu.show-cheatsheet")} />
<MenuItem command="openAboutDialog" icon="bx bx-info-circle" text={t("global_menu.about")} />
{isUpdateAvailable && <MenuItem command={() => window.open("https://github.com/TriliumNext/Trilium/releases/latest")} icon="bx bx-sync" text={`Version ${latestVersion} is available, click to download.`} /> }
{!isElectron() && <BrowserOnlyOptions />}
</Dropdown>
)
}
function AdvancedMenu() {
return (
<FormDropdownSubmenu icon="bx bx-chip" title={t("global_menu.advanced")}>
<MenuItem command="showHiddenSubtree" icon="bx bx-hide" text={t("global_menu.show_hidden_subtree")} />
<MenuItem command="showSearchHistory" icon="bx bx-search-alt" text={t("global_menu.open_search_history")} />
<FormDropdownDivider />
<KeyboardActionMenuItem command="showBackendLog" icon="bx bx-detail" text={t("global_menu.show_backend_log")} />
<KeyboardActionMenuItem command="showSQLConsole" icon="bx bx-data" text={t("global_menu.open_sql_console")} />
<MenuItem command="showSQLConsoleHistory" icon="bx bx-data" text={t("global_menu.open_sql_console_history")} />
<FormDropdownDivider />
{isElectron() && <MenuItem command="openDevTools" icon="bx bx-bug-alt" text={t("global_menu.open_dev_tools")} />}
<KeyboardActionMenuItem command="reloadFrontendApp" icon="bx bx-refresh" text={t("global_menu.reload_frontend")} title={t("global_menu.reload_hint")} />
</FormDropdownSubmenu>
)
}
function BrowserOnlyOptions() {
return <>
<FormDropdownDivider />
<MenuItem command="logout" icon="bx bx-log-out" text={t("global_menu.logout")} />
</>;
}
function SwitchToOptions() {
if (isElectron()) {
return;
} else if (!isMobile()) {
return <MenuItem command="switchToMobileVersion" icon="bx bx-mobile" text={t("global_menu.switch_to_mobile_version")} />
} else {
return <MenuItem command="switchToDesktopVersion" icon="bx bx-desktop" text={t("global_menu.switch_to_desktop_version")} />
}
}
function MenuItem({ icon, text, title, command, disabled, active }: MenuItemProps<KeyboardActionNames | CommandNames | (() => void)>) {
return <FormListItem
icon={icon}
title={title}
triggerCommand={typeof command === "string" ? command : undefined}
onClick={typeof command === "function" ? command : undefined}
disabled={disabled}
active={active}
>{text}</FormListItem>
}
function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<KeyboardActionNames>) {
return <MenuItem
{...props}
command={command}
text={<>{text} <KeyboardShortcut actionName={command as KeyboardActionNames} /></>}
/>
}
function VerticalLayoutIcon() {
const logoRef = useRef<SVGSVGElement>(null);
useStaticTooltip(logoRef);
return (
<svg ref={logoRef} viewBox="0 0 256 256" title={t("global_menu.menu")}>
<g>
<path className="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
<path className="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
<path className="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/>
<path className="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/>
<path className="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/>
<path className="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/>
<path className="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/>
<path className="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/>
<path className="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/>
</g>
</svg>
)
}
function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) {
const [ zoomLevel, setZoomLevel ] = useState(100);
function updateZoomState() {
if (!isElectron()) {
return;
}
const zoomFactor = dynamicRequire("electron").webFrame.getZoomFactor();
setZoomLevel(Math.round(zoomFactor * 100));
}
useEffect(updateZoomState, []);
function ZoomControlButton({ command, title, icon, children }: { command: KeyboardActionNames, title: string, icon?: string, children?: ComponentChildren }) {
const linkRef = useRef<HTMLAnchorElement>(null);
useStaticTooltipWithKeyboardShortcut(linkRef, title, command);
return (
<a
ref={linkRef}
onClick={(e) => {
parentComponent?.triggerCommand(command);
setTimeout(() => updateZoomState(), 300)
e.stopPropagation();
}}
className={icon}
>{children}</a>
)
}
return isElectron() ? (
<FormListItem
icon="bx bx-empty"
className="zoom-container"
>
{t("global_menu.zoom")}
<>
<div className="zoom-buttons">
<ZoomControlButton command="toggleFullscreen" title={t("global_menu.toggle_fullscreen")} icon="bx bx-expand-alt" />
&nbsp;
<ZoomControlButton command="zoomOut" title={t("global_menu.zoom_out")} icon="bx bx-minus" />
<ZoomControlButton command="zoomReset" title={t("global_menu.reset_zoom_level")}>{zoomLevel}{t("units.percentage")}</ZoomControlButton>
<ZoomControlButton command="zoomIn" title={t("global_menu.zoom_in")} icon="bx bx-plus" />
</div>
</>
</FormListItem>
) : (
<MenuItem icon="bx bx-expand-alt" command="toggleFullscreen" text={t("global_menu.toggle_fullscreen")} />
);
}
function ToggleWindowOnTop() {
const focusedWindow = isElectron() ? dynamicRequire("@electron/remote").BrowserWindow.getFocusedWindow() : null;
const [ isAlwaysOnTop, setIsAlwaysOnTop ] = useState(focusedWindow?.isAlwaysOnTop());
return (isElectron() &&
<MenuItem
icon="bx bx-pin"
text={t("title_bar_buttons.window-on-top")}
active={isAlwaysOnTop}
command={() => {
const newState = !isAlwaysOnTop;
focusedWindow?.setAlwaysOnTop(newState);
setIsAlwaysOnTop(newState);
}}
/>
)
}
function useTriliumUpdateStatus() {
const [ latestVersion, setLatestVersion ] = useState<string>();
const [ checkForUpdates ] = useTriliumOptionBool("checkForUpdates");
const isUpdateAvailable = utils.isUpdateAvailable(latestVersion, glob.triliumVersion);
async function updateVersionStatus() {
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
const resp = await fetch(RELEASES_API_URL);
const data = await resp.json();
const latestVersion = data?.tag_name?.substring(1);
setLatestVersion(latestVersion);
}
useEffect(() => {
if (!checkForUpdates) {
setLatestVersion(undefined);
return;
}
updateVersionStatus();
const interval = setInterval(() => updateVersionStatus(), 8 * 60 * 60 * 1000);
return () => clearInterval(interval);
}, [ checkForUpdates ]);
return { isUpdateAvailable, latestVersion };
}

View File

@@ -1,11 +1,24 @@
import utils from "../../services/utils.js";
import contextMenu, { MenuCommandItem } from "../../menus/context_menu.js";
import contextMenu from "../../menus/context_menu.js";
import treeService from "../../services/tree.js";
import ButtonFromNoteWidget from "./button_from_note.js";
import type FNote from "../../entities/fnote.js";
import type { CommandNames } from "../../components/app_context.js";
import type { WebContents } from "electron";
import link from "../../services/link.js";
interface WebContents {
history: string[];
getActiveIndex(): number;
clearHistory(): void;
canGoBack(): boolean;
canGoForward(): boolean;
goToIndex(index: string): void;
}
interface ContextMenuItem {
title: string;
idx: string;
uiIcon: string;
}
export default class HistoryNavigationButton extends ButtonFromNoteWidget {
private webContents?: WebContents;
@@ -38,24 +51,28 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
async showContextMenu(e: JQuery.ContextMenuEvent) {
e.preventDefault();
if (!this.webContents || this.webContents.navigationHistory.length() < 2) {
if (!this.webContents || this.webContents.history.length < 2) {
return;
}
let items: MenuCommandItem<string>[] = [];
let items: ContextMenuItem[] = [];
const history = this.webContents.navigationHistory.getAllEntries();
const activeIndex = this.webContents.navigationHistory.getActiveIndex();
const activeIndex = this.webContents.getActiveIndex();
const history = this.webContents.history;
for (const idx in history) {
const { notePath } = link.parseNavigationStateFromUrl(history[idx].url);
if (!notePath) continue;
const url = history[idx];
const parts = url.split("#");
if (parts.length < 2) continue;
const notePathWithTab = parts[1];
const notePath = notePathWithTab.split("-")[0];
const title = await treeService.getNotePathTitle(notePath);
items.push({
title,
command: idx,
idx,
uiIcon:
parseInt(idx) === activeIndex
? "bx bx-radio-circle-marked" // compare with type coercion!
@@ -75,10 +92,9 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
x: e.pageX,
y: e.pageY,
items,
selectMenuItemHandler: (item: MenuCommandItem<string>) => {
if (item && item.command && this.webContents) {
const idx = parseInt(item.command, 10);
this.webContents.navigationHistory.goToIndex(idx);
selectMenuItemHandler: (item: any) => {
if (item && item.idx && this.webContents) {
this.webContents.goToIndex(item.idx);
}
}
});

View File

@@ -0,0 +1,43 @@
import options from "../../services/options.js";
import splitService from "../../services/resizer.js";
import CommandButtonWidget from "./command_button.js";
import { t } from "../../services/i18n.js";
import type { EventData } from "../../components/app_context.js";
export default class LeftPaneToggleWidget extends CommandButtonWidget {
private currentLeftPaneVisible: boolean;
constructor(isHorizontalLayout: boolean) {
super();
this.currentLeftPaneVisible = options.is("leftPaneVisible");
this.class(isHorizontalLayout ? "toggle-button" : "launcher-button");
this.settings.icon = () => {
if (options.get("layoutOrientation") === "horizontal") {
return "bx-sidebar";
}
return this.currentLeftPaneVisible ? "bx-chevrons-left" : "bx-chevrons-right";
};
this.settings.title = () => (this.currentLeftPaneVisible ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel"));
this.settings.command = () => (this.currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane");
if (isHorizontalLayout) {
this.settings.titlePlacement = "bottom";
}
}
refreshIcon() {
super.refreshIcon();
splitService.setupLeftPaneResizer(this.currentLeftPaneVisible);
}
setLeftPaneVisibilityEvent({ leftPaneVisible }: EventData<"setLeftPaneVisibility">) {
this.currentLeftPaneVisible = leftPaneVisible ?? !this.currentLeftPaneVisible;
this.refreshIcon();
}
}

View File

@@ -1,29 +0,0 @@
import { useEffect, useState } from "preact/hooks";
import ActionButton from "../react/ActionButton";
import options from "../../services/options";
import { t } from "../../services/i18n";
import { useTriliumEvent } from "../react/hooks";
import resizer from "../../services/resizer";
export default function LeftPaneToggle({ isHorizontalLayout }: { isHorizontalLayout: boolean }) {
const [ currentLeftPaneVisible, setCurrentLeftPaneVisible ] = useState(options.is("leftPaneVisible"));
useTriliumEvent("setLeftPaneVisibility", ({ leftPaneVisible }) => {
setCurrentLeftPaneVisible(leftPaneVisible ?? !currentLeftPaneVisible);
});
useEffect(() => {
resizer.setupLeftPaneResizer(currentLeftPaneVisible);
}, [ currentLeftPaneVisible ]);
return (
<ActionButton
className={`${isHorizontalLayout ? "toggle-button" : "launcher-button"}`}
text={currentLeftPaneVisible ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel")}
triggerCommand={currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane"}
icon={isHorizontalLayout
? "bx bx-sidebar"
: (currentLeftPaneVisible ? "bx bx-chevrons-left" : "bx bx-chevrons-right" )}
/>
)
}

View File

@@ -0,0 +1,252 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import utils from "../../services/utils.js";
import branchService from "../../services/branches.js";
import dialogService from "../../services/dialog.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
import ws from "../../services/ws.js";
import appContext, { type EventData } from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import type FNote from "../../entities/fnote.js";
import type { FAttachmentRow } from "../../entities/fattachment.js";
// TODO: Deduplicate with server
interface ConvertToAttachmentResponse {
attachment: FAttachmentRow;
}
const TPL = /*html*/`
<div class="dropdown note-actions">
<style>
.note-actions {
width: 35px;
height: 35px;
}
.note-actions .dropdown-menu {
min-width: 15em;
}
.note-actions .dropdown-item .bx {
position: relative;
top: 3px;
font-size: 120%;
margin-right: 5px;
}
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
color: var(--muted-text-color) !important;
background-color: transparent !important;
pointer-events: none; /* makes it unclickable */
}
</style>
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
class="icon-action bx bx-dots-vertical-rounded"></button>
<div class="dropdown-menu dropdown-menu-right">
<li data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">
<span class="bx bx-paperclip"></span> ${t("note_actions.convert_into_attachment")}
</li>
<li data-trigger-command="renderActiveNote" class="dropdown-item render-note-button">
<span class="bx bx-extension"></span> ${t("note_actions.re_render_note")}<kbd data-command="renderActiveNote"></kbd>
</li>
<li data-trigger-command="findInText" class="dropdown-item find-in-text-button">
<span class='bx bx-search'></span> ${t("note_actions.search_in_note")}<kbd data-command="findInText"></kbd>
</li>
<li data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button">
<span class="bx bx-printer"></span> ${t("note_actions.print_note")}<kbd data-command="printActiveNote"></kbd>
</li>
<li data-trigger-command="exportAsPdf" class="dropdown-item export-as-pdf-button">
<span class="bx bxs-file-pdf"></span> ${t("note_actions.print_pdf")}<kbd data-command="exportAsPdf"></kbd>
</li>
<div class="dropdown-divider"></div>
<li class="dropdown-item import-files-button"><span class="bx bx-import"></span> ${t("note_actions.import_files")}</li>
<li class="dropdown-item export-note-button"><span class="bx bx-export"></span> ${t("note_actions.export_note")}</li>
<div class="dropdown-divider"></div>
<li data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button" title="${t("note_actions.open_note_externally_title")}">
<span class="bx bx-file-find"></span> ${t("note_actions.open_note_externally")}<kbd data-command="openNoteExternally"></kbd>
</li>
<li data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button">
<span class="bx bx-customize"></span> ${t("note_actions.open_note_custom")}<kbd data-command="openNoteCustom"></kbd>
</li>
<li data-trigger-command="showNoteSource" class="dropdown-item show-source-button">
<span class="bx bx-code"></span> ${t("note_actions.note_source")}<kbd data-command="showNoteSource"></kbd>
</li>
<div class="dropdown-divider"></div>
<li data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button">
<span class="bx bx-save"></span> ${t("note_actions.save_revision")}<kbd data-command="forceSaveRevision"></kbd>
</li>
<li class="dropdown-item delete-note-button"><span class="bx bx-trash destructive-action-icon"></span> ${t("note_actions.delete_note")}</li>
<div class="dropdown-divider"></div>
<li data-trigger-command="showAttachments" class="dropdown-item show-attachments-button">
<span class="bx bx-paperclip"></span> ${t("note_actions.note_attachments")}<kbd data-command="showAttachments"></kbd>
</li>
</div>
</div>`;
export default class NoteActionsWidget extends NoteContextAwareWidget {
private $convertNoteIntoAttachmentButton!: JQuery<HTMLElement>;
private $findInTextButton!: JQuery<HTMLElement>;
private $printActiveNoteButton!: JQuery<HTMLElement>;
private $exportAsPdfButton!: JQuery<HTMLElement>;
private $showSourceButton!: JQuery<HTMLElement>;
private $showAttachmentsButton!: JQuery<HTMLElement>;
private $renderNoteButton!: JQuery<HTMLElement>;
private $saveRevisionButton!: JQuery<HTMLElement>;
private $exportNoteButton!: JQuery<HTMLElement>;
private $importNoteButton!: JQuery<HTMLElement>;
private $openNoteExternallyButton!: JQuery<HTMLElement>;
private $openNoteCustomButton!: JQuery<HTMLElement>;
private $deleteNoteButton!: JQuery<HTMLElement>;
isEnabled() {
return this.note?.type !== "launcher";
}
doRender() {
this.$widget = $(TPL);
this.$widget.on("show.bs.dropdown", () => {
if (this.note) {
this.refreshVisibility(this.note);
}
});
this.$convertNoteIntoAttachmentButton = this.$widget.find("[data-trigger-command='convertNoteIntoAttachment']");
this.$findInTextButton = this.$widget.find(".find-in-text-button");
this.$printActiveNoteButton = this.$widget.find(".print-active-note-button");
this.$exportAsPdfButton = this.$widget.find(".export-as-pdf-button");
this.$showSourceButton = this.$widget.find(".show-source-button");
this.$showAttachmentsButton = this.$widget.find(".show-attachments-button");
this.$renderNoteButton = this.$widget.find(".render-note-button");
this.$saveRevisionButton = this.$widget.find(".save-revision-button");
this.$exportNoteButton = this.$widget.find(".export-note-button");
this.$exportNoteButton.on("click", () => {
if (this.$exportNoteButton.hasClass("disabled") || !this.noteContext?.notePath) {
return;
}
this.triggerCommand("showExportDialog", {
notePath: this.noteContext.notePath,
defaultType: "single"
});
});
this.$importNoteButton = this.$widget.find(".import-files-button");
this.$importNoteButton.on("click", () => {
if (this.noteId) {
this.triggerCommand("showImportDialog", { noteId: this.noteId });
}
});
this.$widget.on("click", ".dropdown-item", () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown("toggle"));
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");
this.$deleteNoteButton = this.$widget.find(".delete-note-button");
this.$deleteNoteButton.on("click", () => {
if (!this.note || this.note.noteId === "root") {
return;
}
branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
});
}
async refreshVisibility(note: FNote) {
const isInOptions = note.noteId.startsWith("_options");
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap", "doc"].includes(note.type));
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type));
const canPrint = ["text", "code"].includes(note.type);
this.toggleDisabled(this.$printActiveNoteButton, canPrint);
this.toggleDisabled(this.$exportAsPdfButton, canPrint);
this.$exportAsPdfButton.toggleClass("hidden-ext", !utils.isElectron());
this.$renderNoteButton.toggle(note.type === "render");
this.toggleDisabled(this.$openNoteExternallyButton, utils.isElectron() && !["search", "book"].includes(note.type));
this.toggleDisabled(
this.$openNoteCustomButton,
utils.isElectron() &&
!utils.isMac() && // no implementation for Mac yet
!["search", "book"].includes(note.type)
);
// I don't want to handle all special notes like this, but intuitively user might want to export content of backend log
this.toggleDisabled(this.$exportNoteButton, !["_backendLog"].includes(note.noteId) && !isInOptions);
this.toggleDisabled(this.$importNoteButton, !["search"].includes(note.type) && !isInOptions);
this.toggleDisabled(this.$deleteNoteButton, !isInOptions);
this.toggleDisabled(this.$saveRevisionButton, !isInOptions);
}
async convertNoteIntoAttachmentCommand() {
if (!this.note || !(await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title })))) {
return;
}
const { attachment: newAttachment } = await server.post<ConvertToAttachmentResponse>(`notes/${this.noteId}/convert-to-attachment`);
if (!newAttachment) {
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
return;
}
toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext()?.setNote(newAttachment.ownerId, {
viewScope: {
viewMode: "attachments",
attachmentId: newAttachment.attachmentId
}
});
}
toggleDisabled($el: JQuery<HTMLElement>, enable: boolean) {
if (enable) {
$el.removeAttr("disabled");
} else {
$el.attr("disabled", "disabled");
}
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.isNoteReloaded(this.noteId)) {
this.refresh();
}
}
}

View File

@@ -0,0 +1,14 @@
import { t } from "../../services/i18n.js";
import CommandButtonWidget from "./command_button.js";
export default class RevisionsButton extends CommandButtonWidget {
constructor() {
super();
this.icon("bx-history").title(t("revisions_button.note_revisions")).command("showRevisions").titlePlacement("bottom").class("icon-action");
}
isEnabled() {
return super.isEnabled() && !["launcher", "doc"].includes(this.note?.type ?? "");
}
}

View File

@@ -0,0 +1,62 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
import LoadResults from "../../services/load_results.js";
import type { AttributeRow } from "../../services/load_results.js";
export default class ShowHighlightsListWidgetButton extends OnClickButtonWidget {
isEnabled(): boolean {
return Boolean(super.isEnabled() && this.note && this.note.type === "text" && this.noteContext?.viewScope?.viewMode === "default");
}
constructor() {
super();
this.icon("bx-bookmarks")
.title(t("show_highlights_list_widget_button.show_highlights_list"))
.titlePlacement("bottom")
.onClick(() => {
if (this.noteContext?.viewScope && this.noteId) {
this.noteContext.viewScope.highlightsListTemporarilyHidden = false;
appContext.triggerEvent("showHighlightsListWidget", { noteId: this.noteId });
}
this.toggleInt(false);
});
}
async refreshWithNote(): Promise<void> {
if (this.noteContext?.viewScope) {
this.toggleInt(this.noteContext.viewScope.highlightsListTemporarilyHidden);
}
}
async reEvaluateHighlightsListWidgetVisibilityEvent({ noteId }: { noteId: string }): Promise<void> {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }: { loadResults: LoadResults }): Promise<void> {
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (
loadResults
.getAttributeRows()
.find((attr: AttributeRow) =>
attr.type === "label" &&
(attr.name?.toLowerCase().includes("readonly") || attr.name === "hideHighlightWidget") &&
this.note &&
attributeService.isAffecting(attr, this.note)
)
) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }: { noteId: string }): Promise<void> {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@@ -0,0 +1,62 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
import LoadResults from "../../services/load_results.js";
import type { AttributeRow } from "../../services/load_results.js";
export default class ShowTocWidgetButton extends OnClickButtonWidget {
isEnabled(): boolean {
return Boolean(super.isEnabled() && this.note && this.note.type === "text" && this.noteContext?.viewScope?.viewMode === "default");
}
constructor() {
super();
this.icon("bx-tn-toc")
.title(t("show_toc_widget_button.show_toc"))
.titlePlacement("bottom")
.onClick(() => {
if (this.noteContext?.viewScope && this.noteId) {
this.noteContext.viewScope.tocTemporarilyHidden = false;
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
}
this.toggleInt(false);
});
}
async refreshWithNote(): Promise<void> {
if (this.noteContext?.viewScope) {
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
}
}
async reEvaluateTocWidgetVisibilityEvent({ noteId }: { noteId: string }): Promise<void> {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }: { loadResults: LoadResults }): Promise<void> {
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (
loadResults
.getAttributeRows()
.find((attr: AttributeRow) =>
attr.type === "label" &&
(attr.name?.toLowerCase().includes("readonly") || attr.name === "toc") &&
this.note &&
attributeService.isAffecting(attr, this.note)
)
) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }: { noteId: string }): Promise<void> {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@@ -0,0 +1,40 @@
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import utils from "../../services/utils.js";
const TPL = /*html*/`
<div style="display: none;">
<style>
.global-menu-button-update-available-button {
width: 21px !important;
height: 21px !important;
padding: 0 !important;
border-radius: var(--button-border-radius);
transform: scale(0.9);
border: none;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
}
.global-menu-button-wrapper:hover .global-menu-button-update-available-button {
opacity: 1;
}
</style>
<span class="bx bx-sync global-menu-button-update-available-button" title="${t("update_available.update_available")}"></span>
</div>
`;
export default class UpdateAvailableWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
}
updateVersionStatus(latestVersion: string) {
this.$widget.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
}
}

View File

@@ -1,26 +0,0 @@
:root {
--zen-button-size: 32px;
}
.close-zen-container {
width: var(--zen-button-size);
height: var(--zen-button-size);
}
body.zen .close-zen-container {
display: block;
position: fixed;
top: 2px;
right: 2px;
z-index: 9999;
-webkit-app-region: no-drag;
}
body.zen.mobile .close-zen-container {
top: -2px;
}
body.zen.electron:not(.platform-darwin):not(.native-titlebar) .close-zen-container {
left: calc(env(titlebar-area-width) - var(--zen-button-size) - 2px);
right: unset;
}

Some files were not shown because too many files have changed in this diff Show More