Compare commits

..

1 Commits

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

View File

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

1
.env Normal file
View File

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

View File

@@ -74,7 +74,7 @@ runs:
- name: Update build info - name: Update build info
shell: ${{ inputs.shell }} shell: ${{ inputs.shell }}
run: pnpm run chore:update-build-info run: npm run chore:update-build-info
# Critical debugging configuration # Critical debugging configuration
- name: Run electron-forge build with enhanced logging - name: Run electron-forge build with enhanced logging
@@ -86,8 +86,7 @@ runs:
APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }} APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }}
WINDOWS_SIGN_EXECUTABLE: ${{ env.WINDOWS_SIGN_EXECUTABLE }} WINDOWS_SIGN_EXECUTABLE: ${{ env.WINDOWS_SIGN_EXECUTABLE }}
TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }} TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}
TARGET_ARCH: ${{ inputs.arch }} run: pnpm nx --project=desktop electron-forge:make -- --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
# Add DMG signing step # Add DMG signing step
- name: Sign DMG - name: Sign DMG

View File

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

View File

@@ -1,103 +0,0 @@
name: "Deploy to Cloudflare Pages"
description: "Deploys to Cloudflare Pages on either a temporary branch with preview comment, or on the production version if on the main branch."
inputs:
project_name:
description: "CloudFlare Pages project name"
comment_body:
description: "The message to display when deployment is ready"
default: "Deployment is ready."
required: false
production_url:
description: "The URL to mention as the production URL."
required: true
deploy_dir:
description: "The directory from which to deploy."
required: true
cloudflare_api_token:
description: "The Cloudflare API token to use for deployment."
required: true
cloudflare_account_id:
description: "The Cloudflare account ID to use for deployment."
required: true
github_token:
description: "The GitHub token to use for posting PR comments."
required: true
runs:
using: composite
steps:
# Install wrangler globally to avoid workspace issues
- name: Install Wrangler
shell: bash
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: ${{ inputs.cloudflare_api_token }}
accountId: ${{ inputs.cloudflare_account_id }}
command: pages deploy ${{ inputs.deploy_dir }} --project-name=${{ inputs.project_name}} --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: ${{ inputs.cloudflare_api_token }}
accountId: ${{ inputs.cloudflare_account_id }}
command: pages deploy ${{ inputs.deploy_dir }} --project-name=${{ inputs.project_name}} --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
env:
COMMENT_BODY: ${{ inputs.comment_body }}
PRODUCTION_URL: ${{ inputs.production_url }}
PROJECT_NAME: ${{ inputs.project_name }}
with:
github-token: ${{ inputs.github_token }}
script: |
const prNumber = context.issue.number;
// Construct preview URL based on Cloudflare Pages pattern
const projectName = process.env.PROJECT_NAME;
const previewUrl = `https://pr-${prNumber}.${projectName}.pages.dev`;
// Check if we already commented
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const customMessage = process.env.COMMENT_BODY;
const botComment = comments.data.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes(customMessage)
);
const mainUrl = process.env.PRODUCTION_URL;
const commentBody = `${customMessage}!\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
});
}

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

@@ -67,7 +67,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@@ -95,6 +95,6 @@ jobs:
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -1,129 +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'
# Allow manual triggering from Actions tab
workflow_dispatch:
# Run on pull requests for preview deployments
pull_request:
branches:
- main
- master
paths:
- 'docs/**'
- 'README.md' # README is synced to docs/index.md
- 'mkdocs.yml'
- 'requirements-docs.txt'
- '.github/workflows/deploy-docs.yml'
- 'scripts/fix-mkdocs-structure.ts'
jobs:
build-and-deploy:
name: Build and Deploy MkDocs
runs-on: ubuntu-latest
timeout-minutes: 10
# Required permissions for deployment
permissions:
contents: read
deployments: write
pull-requests: write # For PR preview comments
id-token: write # For OIDC authentication (if needed)
steps:
- name: Checkout Repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # Fetch all history for git info and mkdocs-git-revision-date plugin
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.14'
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@v6
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: Fix HTML Links
run: |
# Remove .md extensions from links in generated HTML
pnpm tsx ./scripts/fix-html-links.ts site
- 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: Deploy
uses: ./.github/actions/deploy-to-cloudflare-pages
if: github.repository == ${{ vars.REPO_MAIN }}
with:
project_name: "trilium-docs"
comment_body: "📚 Documentation preview is ready"
production_url: "https://docs.triliumnotes.org"
deploy_dir: "site"
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@@ -44,7 +44,7 @@ jobs:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 22
cache: "pnpm" cache: "pnpm"
@@ -82,16 +82,16 @@ jobs:
require-healthy: true require-healthy: true
- name: Run Playwright tests - 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 - name: Upload Playwright trace
if: failure() if: failure()
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
with: with:
name: Playwright trace (${{ matrix.dockerfile }}) name: Playwright trace (${{ matrix.dockerfile }})
path: test-output/playwright/output path: test-output/playwright/output
- uses: actions/upload-artifact@v5 - uses: actions/upload-artifact@v4
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
with: with:
name: Playwright report (${{ matrix.dockerfile }}) name: Playwright report (${{ matrix.dockerfile }})
@@ -144,7 +144,7 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'
@@ -152,12 +152,12 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- name: Update build info
run: pnpm run chore:update-build-info
- name: Run the TypeScript build - name: Run the TypeScript build
run: pnpm run server:build run: pnpm run server:build
- name: Update build info
run: pnpm run chore:update-build-info
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -209,9 +209,9 @@ jobs:
touch "/tmp/digests/${digest#sha256:}" touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
with: with:
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }} name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/* path: /tmp/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
@@ -223,7 +223,7 @@ jobs:
- build - build
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@v6 uses: actions/download-artifact@v5
with: with:
path: /tmp/digests path: /tmp/digests
pattern: digests-* pattern: digests-*

View File

@@ -19,6 +19,7 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
env: env:
GITHUB_UPLOAD_URL: https://uploads.github.com/repos/TriliumNext/Notes/releases/179589950/assets{?name,label}
GITHUB_RELEASE_ID: 179589950 GITHUB_RELEASE_ID: 179589950
permissions: permissions:
@@ -50,12 +51,13 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4
- name: Update nightly version - name: Update nightly version
run: npm run chore:ci-update-nightly-version run: npm run chore:ci-update-nightly-version
- name: Run the build - name: Run the build
@@ -77,7 +79,7 @@ jobs:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Publish release - name: Publish release
uses: softprops/action-gh-release@v2.4.1 uses: softprops/action-gh-release@v2.3.2
if: ${{ github.event_name != 'pull_request' }} if: ${{ github.event_name != 'pull_request' }}
with: with:
make_latest: false make_latest: false
@@ -89,7 +91,7 @@ jobs:
name: Nightly Build name: Nightly Build
- name: Publish artifacts - name: Publish artifacts
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
with: with:
name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }} name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }}
@@ -118,7 +120,7 @@ jobs:
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
- name: Publish release - name: Publish release
uses: softprops/action-gh-release@v2.4.1 uses: softprops/action-gh-release@v2.3.2
if: ${{ github.event_name != 'pull_request' }} if: ${{ github.event_name != 'pull_request' }}
with: with:
make_latest: false make_latest: false

View File

@@ -4,8 +4,6 @@ on:
push: push:
branches: branches:
- main - main
paths-ignore:
- "apps/website/**"
pull_request: pull_request:
permissions: permissions:
@@ -21,8 +19,14 @@ jobs:
filter: tree:0 filter: tree:0
fetch-depth: 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: pnpm/action-setup@v4
- uses: actions/setup-node@v6 - uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'
@@ -30,12 +34,10 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps - run: pnpm exec playwright install --with-deps
- uses: nrwl/nx-set-shas@v4
- run: pnpm --filter server-e2e e2e # Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
# - run: npx nx-cloud record -- echo Hello World
- name: Upload test report # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
if: failure() # When you enable task distribution, run the e2e-ci task instead of e2e
uses: actions/upload-artifact@v5 - run: pnpm exec nx affected -t e2e --exclude desktop-e2e
with:
name: e2e report
path: apps/server-e2e/test-output

View File

@@ -30,30 +30,18 @@ jobs:
image: win-signing image: win-signing
shell: cmd shell: cmd
forge_platform: win32 forge_platform: win32
# Exclude ARM64 Linux from default matrix to use native runner
exclude:
- arch: arm64
os:
name: linux
# Add ARM64 Linux with native ubuntu-24.04-arm runner for better-sqlite3 compatibility
include:
- arch: arm64
os:
name: linux
image: ubuntu-24.04-arm
shell: bash
forge_platform: linux
runs-on: ${{ matrix.os.image }} runs-on: ${{ matrix.os.image }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 22
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4
- name: Run the build - name: Run the build
uses: ./.github/actions/build-electron uses: ./.github/actions/build-electron
with: with:
@@ -73,7 +61,7 @@ jobs:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }} GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Upload the artifact - name: Upload the artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
with: with:
name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }} name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }}
path: apps/desktop/upload/*.* path: apps/desktop/upload/*.*
@@ -100,7 +88,7 @@ jobs:
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
- name: Upload the artifact - name: Upload the artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
with: with:
name: release-server-linux-${{ matrix.arch }} name: release-server-linux-${{ matrix.arch }}
path: upload/*.* path: upload/*.*
@@ -120,14 +108,14 @@ jobs:
docs/Release Notes docs/Release Notes
- name: Download all artifacts - name: Download all artifacts
uses: actions/download-artifact@v6 uses: actions/download-artifact@v5
with: with:
merge-multiple: true merge-multiple: true
pattern: release-* pattern: release-*
path: upload path: upload
- name: Publish stable release - name: Publish stable release
uses: softprops/action-gh-release@v2.4.1 uses: softprops/action-gh-release@v2.3.2
with: with:
draft: false draft: false
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md

View File

@@ -1,51 +0,0 @@
name: Deploy website
on:
push:
branches:
- main
paths:
- "apps/website/**"
pull_request:
paths:
- "apps/website/**"
release:
types: [ released ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
name: Build & deploy website
permissions:
contents: read
deployments: write
pull-requests: write # For PR preview comments
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
node-version: 22
cache: "pnpm"
- name: Install dependencies
run: pnpm install --filter website --frozen-lockfile
- name: Build the website
run: pnpm website:build
- name: Deploy
uses: ./.github/actions/deploy-to-cloudflare-pages
with:
project_name: "trilium-homepage"
comment_body: "📚 Website preview is ready"
production_url: "https://triliumnotes.org"
deploy_dir: "apps/website/dist"
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
github_token: ${{ secrets.GITHUB_TOKEN }}

9
.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. # See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
/.cache
# compiled output # compiled output
dist dist
@@ -33,11 +32,14 @@ testem.log
.DS_Store .DS_Store
Thumbs.db Thumbs.db
.nx/cache
.nx/workspace-data
vite.config.*.timestamp* vite.config.*.timestamp*
vitest.config.*.timestamp* vitest.config.*.timestamp*
test-output test-output
apps/*/data* apps/*/data
apps/*/out apps/*/out
upload upload
@@ -46,6 +48,3 @@ upload
/result /result
.svelte-kit .svelte-kit
# docs
site/

2
.nvmrc
View File

@@ -1 +1 @@
22.21.0 22.18.0

View File

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

View File

@@ -14,7 +14,6 @@ usageMatchRegex:
# the `{key}` will be placed by a proper keypath matching regex, # the `{key}` will be placed by a proper keypath matching regex,
# you can ignore it and use your own matching rules as well # you can ignore it and use your own matching rules as well
- "[^\\w\\d]t\\(['\"`]({key})['\"`]" - "[^\\w\\d]t\\(['\"`]({key})['\"`]"
- <Trans\s*i18nKey="({key})"[^>]*>
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys # A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
# and works like how the i18next framework identifies the namespace scope from the # and works like how the i18next framework identifies the namespace scope from the

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

@@ -5,8 +5,7 @@
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"apps/server/src/assets/translations", "apps/server/src/assets/translations",
"apps/client/src/translations", "apps/client/src/translations"
"apps/website/public/translations"
], ],
"npm.exclude": [ "npm.exclude": [
"**/dist", "**/dist",
@@ -36,5 +35,6 @@
"docs/**/*.png": true, "docs/**/*.png": true,
"apps/server/src/assets/doc_notes/**": true, "apps/server/src/assets/doc_notes/**": true,
"apps/edit-docs/demo/**": true "apps/edit-docs/demo/**": true
} },
"nxConsole.generateAiAgentRules": true
} }

View File

@@ -20,10 +20,5 @@
"scope": "typescript", "scope": "typescript",
"prefix": "jqf", "prefix": "jqf",
"body": ["private $${1:name}!: JQuery<HTMLElement>;"] "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 ## 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 ## Development Commands
@@ -14,9 +14,12 @@ Trilium Notes is a hierarchical note-taking application with advanced features l
### Running Applications ### Running Applications
- `pnpm run server:start` - Start development server (http://localhost:8080) - `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 - `pnpm run server:start-prod` - Run server in production mode
### Building ### Building
- `pnpm nx build <project>` - Build specific project (server, client, desktop, etc.)
- `pnpm run client:build` - Build client application - `pnpm run client:build` - Build client application
- `pnpm run server:build` - Build server application - `pnpm run server:build` - Build server application
- `pnpm run electron:build` - Build desktop 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:all` - Run all tests (parallel + sequential)
- `pnpm test:parallel` - Run tests that can run in parallel - `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 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 - `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 ## Architecture Overview
### Monorepo Structure ### 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 - `apps/server/src/assets/db/schema.sql` - Core database structure
4. **Configuration**: 4. **Configuration**:
- `nx.json` - NX workspace configuration
- `package.json` - Project dependencies and scripts - `package.json` - Project dependencies and scripts
## Note Types and Features ## Note Types and Features
@@ -145,7 +154,7 @@ Trilium provides powerful user scripting capabilities:
- Update schema in `apps/server/src/assets/db/schema.sql` - Update schema in `apps/server/src/assets/db/schema.sql`
## Build System Notes ## Build System Notes
- Uses pnpm for monorepo management - Uses NX for monorepo management with build caching
- Vite for fast development builds - Vite for fast development builds
- ESBuild for production optimization - ESBuild for production optimization
- pnpm workspaces for dependency management - pnpm workspaces for dependency management

View File

@@ -1,14 +1,3 @@
<div align="center">
<sup>Special thanks to:</sup><br />
<a href="https://go.warp.dev/Trilium" target="_blank">
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-03.png"><br />
Warp, built for coding with multiple AI agents<br />
</a>
<sup>Available for macOS, Linux and Windows</sup>
</div>
<hr />
# Trilium Notes # Trilium Notes
![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran) ![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran) ![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran) ![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran)
@@ -16,7 +5,7 @@
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total) ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)
[![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) [![Translation status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/) [![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) [![Translation status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
[English](./README.md) | [Chinese (Simplified)](./docs/README-ZH_CN.md) | [Chinese (Traditional)](./docs/README-ZH_TW.md) | [Russian](./docs/README-ru.md) | [Japanese](./docs/README-ja.md) | [Italian](./docs/README-it.md) | [Spanish](./docs/README-es.md) [English](./README.md) | [Chinese (Simplified)](./docs/README-ZH_CN.md) | [Chinese (Traditional)](./docs/README-ZH_TW.md) | [Russian](./docs/README.ru.md) | [Japanese](./docs/README.ja.md) | [Italian](./docs/README.it.md) | [Spanish](./docs/README.es.md)
Trilium Notes is a free and open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases. Trilium Notes is a free and open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
@@ -24,27 +13,6 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a> <a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
## ⏬ Download
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) stable version, recommended for most users.
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) unstable development version, updated daily with the latest features and fixes.
## 📚 Documentation
**Visit our comprehensive documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)**
Our documentation is available in multiple formats:
- **Online Documentation**: Browse the full documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)
- **In-App Help**: Press `F1` within Trilium to access the same documentation directly in the application
- **GitHub**: Navigate through the [User Guide](./docs/User%20Guide/User%20Guide/) in this repository
### Quick Links
- [Getting Started Guide](https://docs.triliumnotes.org/)
- [Installation Instructions](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Docker Setup](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
- [Basic Concepts and Features](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
- [Patterns of Personal Knowledge Base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
## 🎁 Features ## 🎁 Features
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes)) * Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes))
@@ -88,6 +56,19 @@ There are no special migration steps to migrate from a zadam/Trilium instance to
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext/Trilium have their sync versions incremented which prevents direct migration. Versions up to and including [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext/Trilium have their sync versions incremented which prevents direct migration.
## 📖 Documentation
We're currently in the progress of moving the documentation to in-app (hit the `F1` key within Trilium). As a result, there may be some missing parts until we've completed the migration. If you'd prefer to navigate through the documentation within GitHub, you can navigate the [User Guide](./docs/User%20Guide/User%20Guide/) documentation.
Below are some quick links for your convenience to navigate the documentation:
- [Server installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Docker installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
- [Concepts and Features - Note](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
- [Patterns of personal knowledge base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
Until we finish reorganizing the documentation, you may also want to [browse the old documentation](https://triliumnext.github.io/Docs).
## 💬 Discuss with us ## 💬 Discuss with us
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have! Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!
@@ -161,7 +142,7 @@ Download the repository, install dependencies using `pnpm` and then run the envi
git clone https://github.com/TriliumNext/Trilium.git git clone https://github.com/TriliumNext/Trilium.git
cd Trilium cd Trilium
pnpm install pnpm install
pnpm edit-docs:edit-docs pnpm nx run edit-docs:edit-docs
``` ```
### Building the Executable ### Building the Executable
@@ -170,7 +151,7 @@ Download the repository, install dependencies using `pnpm` and then build the de
git clone https://github.com/TriliumNext/Trilium.git git clone https://github.com/TriliumNext/Trilium.git
cd Trilium cd Trilium
pnpm install 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). For more details, see the [development docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
@@ -181,34 +162,16 @@ Please view the [documentation guide](https://github.com/TriliumNext/Trilium/blo
## 👏 Shoutouts ## 👏 Shoutouts
* [zadam](https://github.com/zadam) for the original concept and implementation of the application. * [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team
* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the application icon. * [FancyTree](https://github.com/mar10/fancytree) - very feature rich tree library without real competition. Trilium Notes would not be the same without it.
* [nriver](https://github.com/nriver) for his work on internationalization. * [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with support for huge amount of languages
* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas. * [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library without competition. Used in [relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map.html) and [link maps](https://triliumnext.github.io/Docs/Wiki/note-map.html#link-map)
* [antoniotejada](https://github.com/nriver) for the original syntax highlight widget.
* [Dosu](https://dosu.dev/) for providing us with the automated responses to GitHub issues and discussions.
* [Tabler Icons](https://tabler.io/icons) for the system tray icons.
Trilium would not be possible without the technologies behind it:
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - the visual editor behind text notes. We are grateful for being offered a set of the premium features.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with support for huge amount of languages.
* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite whiteboard used in Canvas notes.
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the mind map functionality.
* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical maps.
* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive table used in collections.
* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library without real competition.
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library. Used in [relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map.html) and [link maps](https://triliumnext.github.io/Docs/Wiki/note-map.html#link-map)
## 🤝 Support ## 🤝 Support
Trilium is built and maintained with [hundreds of hours of work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your support keeps it open-source, improves features, and covers costs such as hosting. Support for the TriliumNext organization will be possible in the near future. For now, you can:
- Support continued development on TriliumNext by supporting our developers: [eliandoran](https://github.com/sponsors/eliandoran) (See the [repository insights]([developers]([url](https://github.com/TriliumNext/trilium/graphs/contributors))) for a full list)
Consider supporting the main developer ([eliandoran](https://github.com/eliandoran)) of the application via: - Show a token of gratitude to the original Trilium developer ([zadam](https://github.com/sponsors/zadam)) via [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
- [GitHub Sponsors](https://github.com/sponsors/eliandoran)
- [PayPal](https://paypal.me/eliandoran)
- [Buy Me a Coffee](https://buymeacoffee.com/eliandoran)
## 🔑 License ## 🔑 License

View File

@@ -35,22 +35,22 @@
"chore:generate-openapi": "tsx bin/generate-openapi.js" "chore:generate-openapi": "tsx bin/generate-openapi.js"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.56.1", "@playwright/test": "1.54.2",
"@stylistic/eslint-plugin": "5.5.0", "@stylistic/eslint-plugin": "5.2.3",
"@types/express": "5.0.3", "@types/express": "5.0.3",
"@types/node": "22.18.12", "@types/node": "22.17.2",
"@types/yargs": "17.0.34", "@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"eslint": "9.38.0", "eslint": "9.33.0",
"eslint-plugin-simple-import-sort": "12.1.1", "eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25", "esm": "3.2.25",
"jsdoc": "4.0.5", "jsdoc": "4.0.4",
"lorem-ipsum": "2.0.8", "lorem-ipsum": "2.0.8",
"rcedit": "4.0.1", "rcedit": "4.0.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"tslib": "2.8.1", "tslib": "2.8.1",
"typedoc": "0.28.14", "typedoc": "0.28.10",
"typedoc-plugin-missing-exports": "4.1.2" "typedoc-plugin-missing-exports": "4.1.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"appdmg": "0.6.6" "appdmg": "0.6.6"

View File

@@ -1,3 +1,4 @@
import type child_process from "child_process";
import { describe, beforeAll, afterAll } from "vitest"; import { describe, beforeAll, afterAll } from "vitest";
let etapiAuthToken: string | undefined; let etapiAuthToken: string | undefined;
@@ -11,6 +12,8 @@ type SpecDefinitionsFunc = () => void;
function describeEtapi(description: string, specDefinitions: SpecDefinitionsFunc): void { function describeEtapi(description: string, specDefinitions: SpecDefinitionsFunc): void {
describe(description, () => { describe(description, () => {
let appProcess: ReturnType<typeof child_process.spawn>;
beforeAll(async () => {}); beforeAll(async () => {});
afterAll(() => {}); afterAll(() => {});

View File

@@ -1,4 +1,5 @@
# The development license key for premium CKEditor features. # The development license key for premium CKEditor features.
# Note: This key must only be used for the Trilium Notes project. # Note: This key must only be used for the Trilium Notes project.
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3ODcyNzA0MDAsImp0aSI6IjkyMWE1MWNlLTliNDMtNGRlMC1iOTQwLTc5ZjM2MDBkYjg1NyIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOiJ0cmlsaXVtIiwiZmVhdHVyZXMiOlsiVFJJTElVTSJdLCJ2YyI6ImU4YzRhMjBkIn0.hny77p-U4-jTkoqbwPytrEar5ylGCWBN7Ez3SlB8i6_mJCBIeCSTOlVQk_JMiOEq3AGykUMHzWXzjdMFwgniOw # Expires on: 2025-09-13
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3NTc3MjE1OTksImp0aSI6ImFiN2E0NjZmLWJlZGMtNDNiYy1iMzU4LTk0NGQ0YWJhY2I3ZiIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOlsic2giLCJkcnVwYWwiXSwid2hpdGVMYWJlbCI6dHJ1ZSwiZmVhdHVyZXMiOlsiRFJVUCIsIkNNVCIsIkRPIiwiRlAiLCJTQyIsIlRPQyIsIlRQTCIsIlBPRSIsIkNDIiwiTUYiLCJTRUUiLCJFQ0giLCJFSVMiXSwidmMiOiI1MzlkOWY5YyJ9.2rvKPql4hmukyXhEtWPZ8MLxKvzPIwzCdykO653g7IxRRZy2QJpeRszElZx9DakKYZKXekVRAwQKgHxwkgbE_w
VITE_CKEDITOR_ENABLE_INSPECTOR=false VITE_CKEDITOR_ENABLE_INSPECTOR=false

View File

@@ -1,21 +1,16 @@
{ {
"name": "@triliumnext/client", "name": "@triliumnext/client",
"version": "0.99.3", "version": "0.98.0",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)", "description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true, "private": true,
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"author": { "author": {
"name": "Trilium Notes Team", "name": "Trilium Notes Team",
"email": "contact@eliandoran.me", "email": "contact@eliandoran.me",
"url": "https://github.com/TriliumNext/Trilium" "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": { "dependencies": {
"@eslint/js": "9.38.0", "@eslint/js": "9.33.0",
"@excalidraw/excalidraw": "0.18.0", "@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19", "@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19", "@fullcalendar/daygrid": "6.1.19",
@@ -24,7 +19,7 @@
"@fullcalendar/multimonth": "6.1.19", "@fullcalendar/multimonth": "6.1.19",
"@fullcalendar/timegrid": "6.1.19", "@fullcalendar/timegrid": "6.1.19",
"@maplibre/maplibre-gl-leaflet": "0.1.3", "@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.0", "@mermaid-js/layout-elk": "0.1.9",
"@mind-elixir/node-menu": "5.0.0", "@mind-elixir/node-menu": "5.0.0",
"@popperjs/core": "2.11.8", "@popperjs/core": "2.11.8",
"@triliumnext/ckeditor5": "workspace:*", "@triliumnext/ckeditor5": "workspace:*",
@@ -32,35 +27,33 @@
"@triliumnext/commons": "workspace:*", "@triliumnext/commons": "workspace:*",
"@triliumnext/highlightjs": "workspace:*", "@triliumnext/highlightjs": "workspace:*",
"@triliumnext/share-theme": "workspace:*", "@triliumnext/share-theme": "workspace:*",
"@triliumnext/split.js": "workspace:*",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
"bootstrap": "5.3.8", "bootstrap": "5.3.7",
"boxicons": "2.1.4", "boxicons": "2.1.4",
"color": "5.0.2", "dayjs": "1.11.13",
"dayjs": "1.11.18",
"dayjs-plugin-utc": "0.1.2", "dayjs-plugin-utc": "0.1.2",
"debounce": "2.2.0", "debounce": "2.2.0",
"draggabilly": "3.0.0", "draggabilly": "3.0.0",
"force-graph": "1.51.0", "force-graph": "1.50.1",
"globals": "16.4.0", "globals": "16.3.0",
"i18next": "25.6.0", "i18next": "25.3.6",
"i18next-http-backend": "3.0.2", "i18next-http-backend": "3.0.2",
"jquery": "3.7.1", "jquery": "3.7.1",
"jquery.fancytree": "2.38.5", "jquery.fancytree": "2.38.5",
"jsplumb": "2.15.6", "jsplumb": "2.15.6",
"katex": "0.16.25", "katex": "0.16.22",
"knockout": "3.5.1", "knockout": "3.5.1",
"leaflet": "1.9.4", "leaflet": "1.9.4",
"leaflet-gpx": "2.2.0", "leaflet-gpx": "2.2.0",
"mark.js": "8.11.1", "mark.js": "8.11.1",
"marked": "16.4.1", "marked": "16.2.0",
"mermaid": "11.12.0", "mermaid": "11.10.0",
"mind-elixir": "5.3.4", "mind-elixir": "5.0.6",
"normalize.css": "8.0.1", "normalize.css": "8.0.1",
"panzoom": "9.4.3", "panzoom": "9.4.3",
"preact": "10.27.2", "preact": "10.27.1",
"react-i18next": "16.2.0", "react-i18next": "15.6.1",
"reveal.js": "5.2.1", "split.js": "1.6.5",
"svg-pan-zoom": "3.6.2", "svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1", "tabulator-tables": "6.3.1",
"vanilla-js-wheel-zoom": "9.0.4" "vanilla-js-wheel-zoom": "9.0.4"
@@ -69,15 +62,27 @@
"@ckeditor/ckeditor5-inspector": "5.0.0", "@ckeditor/ckeditor5-inspector": "5.0.0",
"@preact/preset-vite": "2.10.2", "@preact/preset-vite": "2.10.2",
"@types/bootstrap": "5.2.10", "@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.33", "@types/jquery": "3.5.32",
"@types/leaflet": "1.9.21", "@types/leaflet": "1.9.20",
"@types/leaflet-gpx": "1.3.8", "@types/leaflet-gpx": "1.3.7",
"@types/mark.js": "8.11.12", "@types/mark.js": "8.11.12",
"@types/reveal.js": "5.2.1", "@types/tabulator-tables": "6.2.10",
"@types/tabulator-tables": "6.3.0",
"copy-webpack-plugin": "13.0.1", "copy-webpack-plugin": "13.0.1",
"happy-dom": "20.0.8", "happy-dom": "18.0.1",
"script-loader": "0.7.2", "script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.4" "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

@@ -7,9 +7,6 @@
"display": "standalone", "display": "standalone",
"scope": "/", "scope": "/",
"start_url": "/", "start_url": "/",
"display_override": [
"window-controls-overlay"
],
"icons": [ "icons": [
{ {
"src": "icon.png", "src": "icon.png",

View File

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

View File

@@ -1,8 +1,6 @@
import utils from "../services/utils.js"; import utils from "../services/utils.js";
import type { CommandMappings, CommandNames, EventData, EventNames } from "./app_context.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. * 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; initialized: Promise<void> | null;
parent?: TypedComponent<any>; parent?: TypedComponent<any>;
_position!: number; _position!: number;
private listeners: Record<string, EventHandler[]> | null = {};
constructor() { constructor() {
this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`; 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 { handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
const promises: Promise<unknown>[] = []; 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) { for (const child of this.children) {
const ret = child.handleEvent(name, data) as Promise<void>; const ret = child.handleEvent(name, data) as Promise<void>;
@@ -131,35 +120,6 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
return promise; 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> {} 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 froca from "../services/froca.js";
import linkService from "../services/link.js"; import linkService from "../services/link.js";
import { t } from "../services/i18n.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 { export default class Entrypoints extends Component {
constructor() { constructor() {
@@ -19,7 +34,7 @@ export default class Entrypoints extends Component {
openDevToolsCommand() { openDevToolsCommand() {
if (utils.isElectron()) { 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()) { if (utils.isElectron()) {
// standard JS version does not work completely correctly in electron // standard JS version does not work completely correctly in electron
const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents(); const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents();
const activeIndex = webContents.navigationHistory.getActiveIndex(); const activeIndex = parseInt(webContents.navigationHistory.getActiveIndex());
webContents.goToIndex(activeIndex - 1); webContents.goToIndex(activeIndex - 1);
} else { } else {
@@ -121,7 +136,7 @@ export default class Entrypoints extends Component {
if (utils.isElectron()) { if (utils.isElectron()) {
// standard JS version does not work completely correctly in electron // standard JS version does not work completely correctly in electron
const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents(); const webContents = utils.dynamicRequire("@electron/remote").getCurrentWebContents();
const activeIndex = webContents.navigationHistory.getActiveIndex(); const activeIndex = parseInt(webContents.navigationHistory.getActiveIndex());
webContents.goToIndex(activeIndex + 1); webContents.goToIndex(activeIndex + 1);
} else { } else {

View File

@@ -326,12 +326,10 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
} }
// Collections must always display a note list, even if no children. // Collections must always display a note list, even if no children.
if (note.type === "book") {
const viewType = note.getLabelValue("viewType") ?? "grid"; const viewType = note.getLabelValue("viewType") ?? "grid";
if (!["list", "grid"].includes(viewType)) { if (!["list", "grid"].includes(viewType)) {
return true; return true;
} }
}
if (!note.hasChildren()) { if (!note.hasChildren()) {
return false; return false;
@@ -440,22 +438,4 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
} }
} }
export function openInCurrentNoteContext(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement> | null, notePath: string, viewScope?: ViewScope) {
const ntxId = $(evt?.target as Element)
.closest("[data-ntx-id]")
.attr("data-ntx-id");
const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext();
if (noteContext) {
noteContext.setNote(notePath, { viewScope }).then(() => {
if (noteContext !== appContext.tabManager.getActiveContext()) {
appContext.tabManager.activateNoteContext(noteContext.ntxId);
}
});
} else {
appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true });
}
}
export default NoteContext; export default NoteContext;

View File

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

View File

@@ -433,9 +433,6 @@ export default class TabManager extends Component {
$autocompleteEl.autocomplete("close"); $autocompleteEl.autocomplete("close");
} }
// close dangling tooltips
$("body > div.tooltip").remove();
const noteContextsToRemove = noteContextToRemove.getSubContexts(); const noteContextsToRemove = noteContextToRemove.getSubContexts();
const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId); const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId);
@@ -603,18 +600,18 @@ export default class TabManager extends Component {
} }
async moveTabToNewWindowCommand({ ntxId }: { ntxId: string }) { async moveTabToNewWindowCommand({ ntxId }: { ntxId: string }) {
const { notePath, hoistedNoteId, viewScope } = this.getNoteContextById(ntxId); const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId);
const removed = await this.removeNoteContext(ntxId); const removed = await this.removeNoteContext(ntxId);
if (removed) { if (removed) {
this.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope }); this.triggerCommand("openInWindow", { notePath, hoistedNoteId });
} }
} }
async copyTabToNewWindowCommand({ ntxId }: { ntxId: string }) { async copyTabToNewWindowCommand({ ntxId }: { ntxId: string }) {
const { notePath, hoistedNoteId, viewScope } = this.getNoteContextById(ntxId); const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId);
this.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope }); this.triggerCommand("openInWindow", { notePath, hoistedNoteId });
} }
async reopenLastTabCommand() { async reopenLastTabCommand() {

View File

@@ -23,11 +23,11 @@ export default class TouchBarComponent extends Component {
this.$widget = $("<div>"); this.$widget = $("<div>");
$(window).on("focusin", async (e) => { $(window).on("focusin", async (e) => {
const focusedEl = e.target as unknown as HTMLElement; const $target = $(e.target);
const $target = $(focusedEl);
this.$activeModal = $target.closest(".modal-dialog"); this.$activeModal = $target.closest(".modal-dialog");
this.lastFocusedComponent = appContext.getComponentByEl(focusedEl); const parentComponentEl = $target.closest(".component");
this.lastFocusedComponent = appContext.getComponentByEl(parentComponentEl[0]);
this.#refreshTouchBar(); this.#refreshTouchBar();
}); });
} }

View File

@@ -8,8 +8,10 @@ import electronContextMenu from "./menus/electron_context_menu.js";
import glob from "./services/glob.js"; import glob from "./services/glob.js";
import { t } from "./services/i18n.js"; import { t } from "./services/i18n.js";
import options from "./services/options.js"; import options from "./services/options.js";
import server from "./services/server.js";
import type ElectronRemote from "@electron/remote"; import type ElectronRemote from "@electron/remote";
import type Electron from "electron"; import type Electron from "electron";
import "./stylesheets/bootstrap.scss";
import "boxicons/css/boxicons.min.css"; import "boxicons/css/boxicons.min.css";
import "autocomplete.js/index_jquery.js"; import "autocomplete.js/index_jquery.js";
@@ -44,10 +46,6 @@ if (utils.isElectron()) {
electronContextMenu.setupContextMenu(); electronContextMenu.setupContextMenu();
} }
if (utils.isPWA()) {
initPWATopbarColor();
}
function initOnElectron() { function initOnElectron() {
const electron: typeof Electron = utils.dynamicRequire("electron"); const electron: typeof Electron = utils.dynamicRequire("electron");
electron.ipcRenderer.on("globalShortcut", async (event, actionName) => appContext.triggerCommand(actionName)); electron.ipcRenderer.on("globalShortcut", async (event, actionName) => appContext.triggerCommand(actionName));
@@ -116,20 +114,3 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) {
const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote; const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote;
nativeTheme.themeSource = themeSource; nativeTheme.themeSource = themeSource;
} }
function initPWATopbarColor() {
const tracker = $("#background-color-tracker");
if (tracker.length) {
const applyThemeColor = () => {
let meta = $("meta[name='theme-color']");
if (!meta.length) {
meta = $(`<meta name="theme-color">`).appendTo($("head"));
}
meta.attr("content", tracker.css("color"));
};
tracker.on("transitionend", applyThemeColor);
applyThemeColor();
}
}

View File

@@ -1,5 +1,6 @@
import server from "../services/server.js"; import server from "../services/server.js";
import noteAttributeCache from "../services/note_attribute_cache.js"; import noteAttributeCache from "../services/note_attribute_cache.js";
import ws from "../services/ws.js";
import protectedSessionHolder from "../services/protected_session_holder.js"; import protectedSessionHolder from "../services/protected_session_holder.js";
import cssClassManager from "../services/css_class_manager.js"; import cssClassManager from "../services/css_class_manager.js";
import type { Froca } from "../services/froca-interface.js"; import type { Froca } from "../services/froca-interface.js";
@@ -63,7 +64,7 @@ export interface NoteMetaData {
/** /**
* Note is the main node and concept in Trilium. * Note is the main node and concept in Trilium.
*/ */
export default class FNote { class FNote {
private froca: Froca; private froca: Froca;
noteId!: string; noteId!: string;
@@ -255,20 +256,18 @@ export default class FNote {
return this.children; return this.children;
} }
async getSubtreeNoteIds(includeArchived = false) { async getSubtreeNoteIds() {
let noteIds: (string | string[])[] = []; let noteIds: (string | string[])[] = [];
for (const child of await this.getChildNotes()) { for (const child of await this.getChildNotes()) {
if (child.isArchived && !includeArchived) continue;
noteIds.push(child.noteId); noteIds.push(child.noteId);
noteIds.push(await child.getSubtreeNoteIds(includeArchived)); noteIds.push(await child.getSubtreeNoteIds());
} }
return noteIds.flat(); return noteIds.flat();
} }
async getSubtreeNotes() { async getSubtreeNotes() {
const noteIds = await this.getSubtreeNoteIds(); const noteIds = await this.getSubtreeNoteIds();
return (await this.froca.getNotes(noteIds)); return this.froca.getNotes(noteIds);
} }
async getChildNotes() { async getChildNotes() {
@@ -585,7 +584,7 @@ export default class FNote {
let childBranches = this.getChildBranches(); let childBranches = this.getChildBranches();
if (!childBranches) { if (!childBranches) {
console.error(`No children for '${this.noteId}'. This shouldn't happen.`); ws.logError(`No children for '${this.noteId}'. This shouldn't happen.`);
return []; return [];
} }
@@ -906,8 +905,8 @@ export default class FNote {
return this.getBlob(); return this.getBlob();
} }
getBlob() { async getBlob() {
return this.froca.getBlob("notes", this.noteId); return await this.froca.getBlob("notes", this.noteId);
} }
toString() { toString() {
@@ -1021,14 +1020,6 @@ export default class FNote {
return this.noteId.startsWith("_options"); 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. * Provides note's date metadata.
*/ */
@@ -1036,3 +1027,5 @@ export default class FNote {
return await server.get<NoteMetaData>(`notes/${this.noteId}/metadata`); 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 FlexContainer from "../widgets/containers/flex_container.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import TabRowWidget from "../widgets/tab_row.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 LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import NoteTreeWidget from "../widgets/note_tree.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 NoteDetailWidget from "../widgets/note_detail.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js"; import RibbonContainer from "../widgets/containers/ribbon_container.js";
import NoteIconWidget from "../widgets/note_icon.jsx"; 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 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 ScrollingContainer from "../widgets/containers/scrolling_container.js";
import RootContainer from "../widgets/containers/root_container.js"; import RootContainer from "../widgets/containers/root_container.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js"; import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
import SpacerWidget from "../widgets/spacer.js"; import SpacerWidget from "../widgets/spacer.js";
import QuickSearchWidget from "../widgets/quick_search.js"; import QuickSearchWidget from "../widgets/quick_search.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.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 CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import ClosePaneButton from "../widgets/buttons/close_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 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 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 FindWidget from "../widgets/find.js";
import TocWidget from "../widgets/toc.js"; import TocWidget from "../widgets/toc.js";
import HighlightsListWidget from "../widgets/highlights_list.js"; import HighlightsListWidget from "../widgets/highlights_list.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.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 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 MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.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 options from "../services/options.js";
import utils from "../services/utils.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 { AppContext } from "../components/app_context.js";
import type { WidgetsByParent } from "../services/bundle.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 { 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";
import NoteList from "../widgets/collections/NoteList.jsx";
export default class DesktopLayout { export default class DesktopLayout {
@@ -76,9 +107,9 @@ export default class DesktopLayout {
new FlexContainer("row") new FlexContainer("row")
.class("tab-row-container") .class("tab-row-container")
.child(new FlexContainer("row").id("tab-row-left-spacer")) .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")) .child(new TabRowWidget().class("full-width"))
.optChild(customTitleBarButtons, <TitleBarButtons />) .optChild(customTitleBarButtons, new TitleBarButtonsWidget())
.css("height", "40px") .css("height", "40px")
.css("background-color", "var(--launcher-pane-background-color)") .css("background-color", "var(--launcher-pane-background-color)")
.setParent(appContext) .setParent(appContext)
@@ -99,7 +130,7 @@ export default class DesktopLayout {
new FlexContainer("column") new FlexContainer("column")
.id("rest-pane") .id("rest-pane")
.css("flex-grow", "1") .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( .child(
new FlexContainer("row") new FlexContainer("row")
.filling() .filling()
@@ -120,30 +151,69 @@ export default class DesktopLayout {
.css("min-height", "50px") .css("min-height", "50px")
.css("align-items", "center") .css("align-items", "center")
.cssBlock(".title-row > * { margin: 5px; }") .cssBlock(".title-row > * { margin: 5px; }")
.child(<NoteIconWidget />) .child(new NoteIconWidget())
.child(<NoteTitleWidget />) .child(new NoteTitleWidget())
.child(new SpacerWidget(0, 1)) .child(new SpacerWidget(0, 1))
.child(<MovePaneButton direction="left" />) .child(new MovePaneButton(true))
.child(<MovePaneButton direction="right" />) .child(new MovePaneButton(false))
.child(<ClosePaneButton />) .child(new ClosePaneButton())
.child(<CreatePaneButton />) .child(new CreatePaneButton())
) )
.child(<Ribbon />) .child(
.child(<SharedInfo />) 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(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( .child(
new ScrollingContainer() new ScrollingContainer()
.filling() .filling()
.child(new PromotedAttributesWidget()) .child(new PromotedAttributesWidget())
.child(<SqlTableSchemas />) .child(new SqlTableSchemasWidget())
.child(new NoteDetailWidget()) .child(new NoteDetailWidget())
.child(<NoteList media="screen" />) .child(new NoteListWidget(false))
.child(<SearchResult />) .child(new SearchResultWidget())
.child(<SqlResults />) .child(new SqlResultWidget())
.child(<ScrollPadding />) .child(new ScrollPaddingWidget())
) )
.child(<ApiLog />) .child(new ApiLogWidget())
.child(new FindWidget()) .child(new FindWidget())
.child( .child(
...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC ...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. // Desktop-specific dialogs.
.child(<PasswordNoteSetDialog />) .child(new PasswordNoteSetDialog())
.child(<UploadAttachmentsDialog />); .child(new UploadAttachmentsDialog());
applyModals(rootContainer); applyModals(rootContainer);
return rootContainer; return rootContainer;
@@ -176,18 +246,14 @@ export default class DesktopLayout {
let launcherPane; let launcherPane;
if (isHorizontal) { if (isHorizontal) {
launcherPane = new FlexContainer("row") launcherPane = new FlexContainer("row").css("height", "53px").class("horizontal").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true));
.css("height", "53px")
.class("horizontal")
.child(new LauncherContainer(true))
.child(<GlobalMenu isHorizontalLayout={true} />);
} else { } else {
launcherPane = new FlexContainer("column") launcherPane = new FlexContainer("column")
.css("width", "53px") .css("width", "53px")
.class("vertical") .class("vertical")
.child(<GlobalMenu isHorizontalLayout={false} />) .child(new GlobalMenuWidget(false))
.child(new LauncherContainer(false)) .child(new LauncherContainer(false))
.child(<LeftPaneToggle isHorizontalLayout={false} />); .child(new LeftPaneToggleWidget(false));
} }
launcherPane.id("launcher-pane"); launcherPane.id("launcher-pane");

View File

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

View File

@@ -3,30 +3,30 @@ import NoteTitleWidget from "../widgets/note_title.js";
import NoteDetailWidget from "../widgets/note_detail.js"; import NoteDetailWidget from "../widgets/note_detail.js";
import QuickSearchWidget from "../widgets/quick_search.js"; import QuickSearchWidget from "../widgets/quick_search.js";
import NoteTreeWidget from "../widgets/note_tree.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 ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_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 GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js"; import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js"; import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.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 SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import type AppContext from "../components/app_context.js"; import type AppContext from "../components/app_context.js";
import TabRowWidget from "../widgets/tab_row.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 { applyModals } from "./layout_commons.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx"; import CloseZenButton from "../widgets/close_zen_button.js";
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 NoteList from "../widgets/collections/NoteList.jsx";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
import SearchResult from "../widgets/search_result.jsx";
const MOBILE_CSS = ` const MOBILE_CSS = `
<style> <style>
@@ -43,8 +43,8 @@ kbd {
border: none; border: none;
cursor: pointer; cursor: pointer;
font-size: 1.25em; font-size: 1.25em;
padding-inline-start: 0.5em; padding-left: 0.5em;
padding-inline-end: 0.5em; padding-right: 0.5em;
color: var(--main-text-color); color: var(--main-text-color);
} }
.quick-search { .quick-search {
@@ -62,7 +62,7 @@ const FANCYTREE_CSS = `
margin-top: 0px; margin-top: 0px;
overflow-y: auto; overflow-y: auto;
contain: content; contain: content;
padding-inline-start: 10px; padding-left: 10px;
} }
.fancytree-custom-icon { .fancytree-custom-icon {
@@ -71,7 +71,7 @@ const FANCYTREE_CSS = `
.fancytree-title { .fancytree-title {
font-size: 1.5em; font-size: 1.5em;
margin-inline-start: 0.6em !important; margin-left: 0.6em !important;
} }
.fancytree-node { .fancytree-node {
@@ -84,7 +84,7 @@ const FANCYTREE_CSS = `
span.fancytree-expander { span.fancytree-expander {
width: 24px !important; width: 24px !important;
margin-inline-end: 5px; margin-right: 5px;
} }
.fancytree-loading span.fancytree-expander { .fancytree-loading span.fancytree-expander {
@@ -104,7 +104,7 @@ span.fancytree-expander {
.tree-wrapper .scroll-to-active-note-button, .tree-wrapper .scroll-to-active-note-button,
.tree-wrapper .tree-settings-button { .tree-wrapper .tree-settings-button {
position: fixed; position: fixed;
margin-inline-end: 16px; margin-right: 16px;
display: none; display: none;
} }
@@ -129,41 +129,44 @@ export default class MobileLayout {
.class("d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-3 col-xl-3") .class("d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-3 col-xl-3")
.id("mobile-sidebar-wrapper") .id("mobile-sidebar-wrapper")
.css("max-height", "100%") .css("max-height", "100%")
.css("padding-inline-start", "0") .css("padding-left", "0")
.css("padding-inline-end", "0") .css("padding-right", "0")
.css("contain", "content") .css("contain", "content")
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS))) .child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
) )
.child( .child(
new ScreenContainer("detail", "row") new ScreenContainer("detail", "column")
.id("detail-container") .id("detail-container")
.class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-9") .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-9")
.child(
new NoteWrapperWidget()
.child( .child(
new FlexContainer("row") new FlexContainer("row")
.contentSized() .contentSized()
.css("font-size", "larger") .css("font-size", "larger")
.css("align-items", "center") .css("align-items", "center")
.child(<ToggleSidebarButton />) .child(new ToggleSidebarButtonWidget().contentSized())
.child(<NoteTitleWidget />) .child(new NoteTitleWidget().contentSized().css("position", "relative").css("padding-left", "0.5em"))
.child(<MobileDetailMenu />) .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(<SharedInfoWidget />)
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
.child(new PromotedAttributesWidget()) .child(new PromotedAttributesWidget())
.child( .child(
new ScrollingContainer() new ScrollingContainer()
.filling() .filling()
.contentSized() .contentSized()
.child(new NoteDetailWidget()) .child(new NoteDetailWidget())
.child(<NoteList media="screen" />) .child(new NoteListWidget(false))
.child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />) .child(new FilePropertiesWidget().css("font-size", "smaller"))
.child(<SearchResult />)
.child(<FilePropertiesWrapper />)
)
.child(<MobileEditorToolbar />)
) )
.child(new MobileEditorToolbar())
) )
) )
.child( .child(
@@ -171,25 +174,10 @@ export default class MobileLayout {
.contentSized() .contentSized()
.id("mobile-bottom-bar") .id("mobile-bottom-bar")
.child(new TabRowWidget().css("height", "40px")) .child(new TabRowWidget().css("height", "40px"))
.child(new FlexContainer("row") .child(new FlexContainer("row").class("horizontal").css("height", "53px").child(new LauncherContainer(true)).child(new GlobalMenuWidget(true)).id("launcher-pane"))
.class("horizontal")
.css("height", "53px")
.child(new LauncherContainer(true))
.child(<GlobalMenuWidget isHorizontalLayout />)
.id("launcher-pane"))
) )
.child(<CloseZenModeButton />); .child(new CloseZenButton());
applyModals(rootContainer); applyModals(rootContainer);
return rootContainer; return rootContainer;
} }
} }
function FilePropertiesWrapper() {
const { note } = useNoteContext();
return (
<div>
{note?.type === "file" && <FilePropertiesTab note={note} />}
</div>
);
}

View File

@@ -1,3 +1,5 @@
import "./stylesheets/bootstrap.scss";
// @ts-ignore - module = undefined // @ts-ignore - module = undefined
// Required for correct loading of scripts in Electron // Required for correct loading of scripts in Electron
if (typeof module === 'object') {window.module = module; module = undefined;} if (typeof module === 'object') {window.module = module; module = undefined;}

View File

@@ -1,8 +1,6 @@
import { KeyboardActionNames } from "@triliumnext/commons"; import keyboardActionService from "../services/keyboard_actions.js";
import keyboardActionService, { getActionSync } from "../services/keyboard_actions.js";
import note_tooltip from "../services/note_tooltip.js"; import note_tooltip from "../services/note_tooltip.js";
import utils from "../services/utils.js"; import utils from "../services/utils.js";
import { should } from "vitest";
export interface ContextMenuOptions<T> { export interface ContextMenuOptions<T> {
x: number; x: number;
@@ -15,13 +13,8 @@ export interface ContextMenuOptions<T> {
onHide?: () => void; onHide?: () => void;
} }
export interface MenuSeparatorItem { interface MenuSeparatorItem {
kind: "separator"; title: "----";
}
export interface MenuHeader {
title: string;
kind: "header";
} }
export interface MenuItemBadge { export interface MenuItemBadge {
@@ -45,13 +38,12 @@ export interface MenuCommandItem<T> {
handler?: MenuHandler<T>; handler?: MenuHandler<T>;
items?: MenuItem<T>[] | null; items?: MenuItem<T>[] | null;
shortcut?: string; shortcut?: string;
keyboardShortcut?: KeyboardActionNames;
spellingSuggestion?: string; spellingSuggestion?: string;
checked?: boolean; checked?: boolean;
columns?: number; columns?: number;
} }
export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem | MenuHeader; export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem;
export type MenuHandler<T> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void; export type MenuHandler<T> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void;
export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent; export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent;
@@ -150,57 +142,20 @@ class ContextMenu {
this.$widget this.$widget
.css({ .css({
display: "block", display: "block",
top, top: top,
left left: left
}) })
.addClass("show"); .addClass("show");
} }
addItems($parent: JQuery<HTMLElement>, items: MenuItem<any>[], multicolumn = false) { addItems($parent: JQuery<HTMLElement>, items: MenuItem<any>[]) {
let $group = $parent; // The current group or parent element to which items are being appended for (const item of items) {
let shouldStartNewGroup = false; // If true, the next item will start a new group
let shouldResetGroup = false; // If true, the next item will be the last one from the group
for (let index = 0; index < items.length; index++) {
const item = items[index];
if (!item) { if (!item) {
continue; continue;
} }
// If the current item is a header, start a new group. This group will contain the if (item.title === "----") {
// header and the next item that follows the header. $parent.append($("<div>").addClass("dropdown-divider"));
if ("kind" in item && item.kind === "header") {
if (multicolumn && !shouldResetGroup) {
shouldStartNewGroup = true;
}
}
// If the next item is a separator, start a new group. This group will contain the
// current item, the separator, and the next item after the separator.
const nextItem = (index < items.length - 1) ? items[index + 1] : null;
if (multicolumn && nextItem && "kind" in nextItem && nextItem.kind === "separator") {
if (!shouldResetGroup) {
shouldStartNewGroup = true;
} else {
shouldResetGroup = true; // Continue the current group
}
}
// Create a new group to avoid column breaks before and after the seaparator / header.
// This is a workaround for Firefox not supporting break-before / break-after: avoid
// for columns.
if (shouldStartNewGroup) {
$group = $("<div class='dropdown-no-break'>");
$parent.append($group);
shouldStartNewGroup = false;
}
if ("kind" in item && item.kind === "separator") {
$group.append($("<div>").addClass("dropdown-divider"));
shouldResetGroup = true; // End the group after the next item
} else if ("kind" in item && item.kind === "header") {
$group.append($("<h6>").addClass("dropdown-header").text(item.title));
shouldResetGroup = true;
} else { } else {
const $icon = $("<span>"); const $icon = $("<span>");
@@ -230,23 +185,7 @@ class ContextMenu {
} }
} }
if ("keyboardShortcut" in item && item.keyboardShortcut) { if ("shortcut" in item && item.shortcut) {
const shortcuts = getActionSync(item.keyboardShortcut).effectiveShortcuts;
if (shortcuts) {
const allShortcuts: string[] = [];
for (const effectiveShortcut of shortcuts) {
allShortcuts.push(effectiveShortcut.split("+")
.map(key => `<kbd>${key}</kbd>`)
.join("+"));
}
if (allShortcuts.length) {
const container = $("<span>").addClass("keyboard-shortcut");
container.append($(allShortcuts.join(",")));
$link.append(container);
}
}
} else if ("shortcut" in item && item.shortcut) {
$link.append($("<kbd>").text(item.shortcut)); $link.append($("<kbd>").text(item.shortcut));
} }
@@ -302,24 +241,16 @@ class ContextMenu {
$link.addClass("dropdown-toggle"); $link.addClass("dropdown-toggle");
const $subMenu = $("<ul>").addClass("dropdown-menu"); const $subMenu = $("<ul>").addClass("dropdown-menu");
const hasColumns = !!item.columns && item.columns > 1; if (!this.isMobile && item.columns) {
if (!this.isMobile && hasColumns) { $subMenu.css("column-count", item.columns);
$subMenu.css("column-count", item.columns!);
} }
this.addItems($subMenu, item.items, hasColumns); this.addItems($subMenu, item.items);
$item.append($subMenu); $item.append($subMenu);
} }
$group.append($item); $parent.append($item);
// After adding a menu item, if the previous item was a separator or header,
// reset the group so that the next item will be appended directly to the parent.
if (shouldResetGroup) {
$group = $parent;
shouldResetGroup = false;
};
} }
} }
} }

View File

@@ -37,7 +37,7 @@ function setupContextMenu() {
handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord) handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
}); });
items.push({ kind: "separator" }); items.push({ title: `----` });
} }
if (params.isEditable) { if (params.isEditable) {
@@ -112,7 +112,7 @@ function setupContextMenu() {
// Replace the placeholder with the real search keyword. // Replace the placeholder with the real search keyword.
let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText)); let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText));
items.push({ kind: "separator" }); items.push({ title: "----" });
items.push({ items.push({
title: t("electron_context_menu.search_online", { term: shortenedSelection, searchEngine: searchEngineName }), title: t("electron_context_menu.search_online", { term: shortenedSelection, searchEngine: searchEngineName }),

View File

@@ -45,16 +45,16 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener<
isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-script-launcher"), command: "addScriptLauncher", uiIcon: "bx bx-code-curly" } : null, isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-script-launcher"), command: "addScriptLauncher", uiIcon: "bx bx-code-curly" } : null,
isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-custom-widget"), command: "addWidgetLauncher", uiIcon: "bx bx-customize" } : null, isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-custom-widget"), command: "addWidgetLauncher", uiIcon: "bx bx-customize" } : null,
isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-spacer"), command: "addSpacerLauncher", uiIcon: "bx bx-dots-horizontal" } : null, isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-spacer"), command: "addSpacerLauncher", uiIcon: "bx bx-dots-horizontal" } : null,
isVisibleRoot || isAvailableRoot ? { kind: "separator" } : null, isVisibleRoot || isAvailableRoot ? { title: "----" } : null,
isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null, isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null, isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
isVisibleItem || isAvailableItem ? { kind: "separator" } : null, isVisibleItem || isAvailableItem ? { title: "----" } : null,
{ title: `${t("launcher_context_menu.duplicate-launcher")}`, command: "duplicateSubtree", uiIcon: "bx bx-outline", enabled: isItem }, { title: `${t("launcher_context_menu.duplicate-launcher")}`, command: "duplicateSubtree", uiIcon: "bx bx-outline", enabled: isItem },
{ title: `${t("launcher_context_menu.delete")}`, command: "deleteNotes", uiIcon: "bx bx-trash destructive-action-icon", enabled: canBeDeleted }, { title: `${t("launcher_context_menu.delete")}`, command: "deleteNotes", uiIcon: "bx bx-trash destructive-action-icon", enabled: canBeDeleted },
{ kind: "separator" }, { title: "----" },
{ title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-reset destructive-action-icon", enabled: canBeReset } { title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-reset destructive-action-icon", enabled: canBeReset }
]; ];

View File

@@ -13,8 +13,6 @@ import type NoteTreeWidget from "../widgets/note_tree.js";
import type FAttachment from "../entities/fattachment.js"; import type FAttachment from "../entities/fattachment.js";
import type { SelectMenuItemEventListener } from "../components/events.js"; import type { SelectMenuItemEventListener } from "../components/events.js";
import utils from "../services/utils.js"; import utils from "../services/utils.js";
import attributes from "../services/attributes.js";
import { executeBulkActions } from "../services/bulk_action.js";
// TODO: Deduplicate once client/server is well split. // TODO: Deduplicate once client/server is well split.
interface ConvertToAttachmentResponse { interface ConvertToAttachmentResponse {
@@ -63,11 +61,6 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
// the only exception is when the only selected note is the one that was right-clicked, then // the only exception is when the only selected note is the one that was right-clicked, then
// it's clear what the user meant to do. // it's clear what the user meant to do.
const selNodes = this.treeWidget.getSelectedNodes(); const selNodes = this.treeWidget.getSelectedNodes();
const selectedNotes = await froca.getNotes(selNodes.map(node => node.data.noteId));
if (note && !selectedNotes.includes(note)) selectedNotes.push(note);
const isArchived = selectedNotes.every(note => note.isArchived);
const canToggleArchived = !selectedNotes.some(note => note.isArchived !== isArchived);
const noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node); const noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node);
const notSearch = note?.type !== "search"; const notSearch = note?.type !== "search";
@@ -76,29 +69,27 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
const items: (MenuItem<TreeCommandNames> | null)[] = [ const items: (MenuItem<TreeCommandNames> | null)[] = [
{ title: t("tree-context-menu.open-in-a-new-tab"), command: "openInTab", shortcut: "Ctrl+Click", uiIcon: "bx bx-link-external", enabled: noSelectedNotes }, { title: `${t("tree-context-menu.open-in-a-new-tab")}`, command: "openInTab", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes }, { title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes }, { title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
isHoisted isHoisted
? null ? null
: { : {
title: `${t("tree-context-menu.hoist-note")}`, title: `${t("tree-context-menu.hoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`,
command: "toggleNoteHoisting", command: "toggleNoteHoisting",
keyboardShortcut: "toggleNoteHoisting",
uiIcon: "bx bxs-chevrons-up", uiIcon: "bx bxs-chevrons-up",
enabled: noSelectedNotes && notSearch enabled: noSelectedNotes && notSearch
}, },
!isHoisted || !isNotRoot !isHoisted || !isNotRoot
? null ? null
: { title: t("tree-context-menu.unhoist-note"), command: "toggleNoteHoisting", keyboardShortcut: "toggleNoteHoisting", uiIcon: "bx bx-door-open" }, : { title: `${t("tree-context-menu.unhoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
{ kind: "separator" }, { title: "----" },
{ {
title: t("tree-context-menu.insert-note-after"), title: `${t("tree-context-menu.insert-note-after")}<kbd data-command="createNoteAfter"></kbd>`,
command: "insertNoteAfter", command: "insertNoteAfter",
keyboardShortcut: "createNoteAfter",
uiIcon: "bx bx-plus", uiIcon: "bx bx-plus",
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null, items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp, enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp,
@@ -106,22 +97,21 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
}, },
{ {
title: t("tree-context-menu.insert-child-note"), title: `${t("tree-context-menu.insert-child-note")}<kbd data-command="createNoteInto"></kbd>`,
command: "insertChildNote", command: "insertChildNote",
keyboardShortcut: "createNoteInto",
uiIcon: "bx bx-plus", uiIcon: "bx bx-plus",
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null, items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
enabled: notSearch && noSelectedNotes && notOptionsOrHelp, enabled: notSearch && noSelectedNotes && notOptionsOrHelp,
columns: 2 columns: 2
}, },
{ kind: "separator" }, { title: "----" },
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes }, { title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes }, { title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
{ kind: "separator" }, { title: "----" },
{ {
title: t("tree-context-menu.advanced"), title: t("tree-context-menu.advanced"),
@@ -130,52 +120,48 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
items: [ items: [
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", enabled: true }, { title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", enabled: true },
{ kind: "separator" }, { title: "----" },
{ {
title: t("tree-context-menu.edit-branch-prefix"), title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`,
command: "editBranchPrefix", command: "editBranchPrefix",
keyboardShortcut: "editBranchPrefix",
uiIcon: "bx bx-rename", uiIcon: "bx bx-rename",
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
}, },
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp }, { title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },
{ kind: "separator" }, { title: "----" },
{ title: t("tree-context-menu.expand-subtree"), command: "expandSubtree", keyboardShortcut: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes }, { title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: t("tree-context-menu.collapse-subtree"), command: "collapseSubtree", keyboardShortcut: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes }, { title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ {
title: t("tree-context-menu.sort-by"), title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`,
command: "sortChildNotes", command: "sortChildNotes",
keyboardShortcut: "sortChildNotes",
uiIcon: "bx bx-sort-down", uiIcon: "bx bx-sort-down",
enabled: noSelectedNotes && notSearch enabled: noSelectedNotes && notSearch
}, },
{ kind: "separator" }, { title: "----" },
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true }, { title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true },
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptionsOrHelp } { title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptionsOrHelp }
] ]
}, },
{ kind: "separator" }, { title: "----" },
{ {
title: t("tree-context-menu.cut"), title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`,
command: "cutNotesToClipboard", command: "cutNotesToClipboard",
keyboardShortcut: "cutNotesToClipboard",
uiIcon: "bx bx-cut", uiIcon: "bx bx-cut",
enabled: isNotRoot && !isHoisted && parentNotSearch enabled: isNotRoot && !isHoisted && parentNotSearch
}, },
{ title: t("tree-context-menu.copy-clone"), command: "copyNotesToClipboard", keyboardShortcut: "copyNotesToClipboard", uiIcon: "bx bx-copy", enabled: isNotRoot && !isHoisted }, { title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy", enabled: isNotRoot && !isHoisted },
{ {
title: t("tree-context-menu.paste-into"), title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`,
command: "pasteNotesFromClipboard", command: "pasteNotesFromClipboard",
keyboardShortcut: "pasteNotesFromClipboard",
uiIcon: "bx bx-paste", uiIcon: "bx bx-paste",
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes
}, },
@@ -188,71 +174,39 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
}, },
{ {
title: t("tree-context-menu.move-to"), title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`,
command: "moveNotesTo", command: "moveNotesTo",
keyboardShortcut: "moveNotesTo",
uiIcon: "bx bx-transfer", uiIcon: "bx bx-transfer",
enabled: isNotRoot && !isHoisted && parentNotSearch enabled: isNotRoot && !isHoisted && parentNotSearch
}, },
{ title: t("tree-context-menu.clone-to"), command: "cloneNotesTo", keyboardShortcut: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted }, { title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
{ {
title: t("tree-context-menu.duplicate"), title: `${t("tree-context-menu.duplicate")} <kbd data-command="duplicateSubtree">`,
command: "duplicateSubtree", command: "duplicateSubtree",
keyboardShortcut: "duplicateSubtree",
uiIcon: "bx bx-outline", uiIcon: "bx bx-outline",
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
}, },
{ {
title: !isArchived ? t("tree-context-menu.archive") : t("tree-context-menu.unarchive"), title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
uiIcon: !isArchived ? "bx bx-archive" : "bx bx-archive-out",
enabled: canToggleArchived,
handler: () => {
if (!selectedNotes.length) return;
if (selectedNotes.length == 1) {
const note = selectedNotes[0];
if (!isArchived) {
attributes.addLabel(note.noteId, "archived");
} else {
attributes.removeOwnedLabelByName(note, "archived");
}
} else {
const noteIds = selectedNotes.map(note => note.noteId);
if (!isArchived) {
executeBulkActions(noteIds, [{
name: "addLabel", labelName: "archived"
}]);
} else {
executeBulkActions(noteIds, [{
name: "deleteLabel", labelName: "archived"
}]);
}
}
}
},
{
title: t("tree-context-menu.delete"),
command: "deleteNotes", command: "deleteNotes",
keyboardShortcut: "deleteNotes",
uiIcon: "bx bx-trash destructive-action-icon", uiIcon: "bx bx-trash destructive-action-icon",
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp
}, },
{ kind: "separator" }, { title: "----" },
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptionsOrHelp }, { title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", enabled: notSearch && noSelectedNotes && notOptionsOrHelp }, { title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
{ kind: "separator" }, { title: "----" },
{ {
title: t("tree-context-menu.search-in-subtree"), title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`,
command: "searchInSubtree", command: "searchInSubtree",
keyboardShortcut: "searchInSubtree",
uiIcon: "bx bx-search", uiIcon: "bx bx-search",
enabled: notSearch && noSelectedNotes enabled: notSearch && noSelectedNotes
} }

View File

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

View File

@@ -1,155 +0,0 @@
:root {
--print-font-size: 11pt;
--ck-content-color-image-caption-background: transparent !important;
}
html,
body {
width: 100%;
height: 100%;
color: black;
}
@page {
margin: 2cm;
}
.note-list-widget.full-height,
.note-list-widget.full-height .note-list-widget-content {
height: unset !important;
}
.component {
contain: none !important;
}
body[data-note-type="text"] .ck-content {
font-size: var(--print-font-size);
text-align: justify;
}
.ck-content figcaption {
font-style: italic;
}
.ck-content a {
text-decoration: none;
}
.ck-content a:not([href^="#root/"]) {
text-decoration: underline;
color: #374a75;
}
.ck-content .todo-list__label * {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
@supports selector(.todo-list__label__description:has(*)) and (height: 1lh) {
.ck-content .todo-list__label__description {
/* The percentage of the line height that the check box occupies */
--box-ratio: 0.75;
/* The size of the gap between the check box and the caption */
--box-text-gap: 0.25em;
--box-size: calc(1lh * var(--box-ratio));
--box-vert-offset: calc((1lh - var(--box-size)) / 2);
display: inline-block;
padding-inline-start: calc(var(--box-size) + var(--box-text-gap));
/* Source: https://pictogrammers.com/library/mdi/icon/checkbox-blank-outline/ */
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5C3.89%2c3 3%2c3.89 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5C21%2c3.89 20.1%2c3 19%2c3M19%2c5V19H5V5H19Z' /%3e%3c/svg%3e");
background-position: 0 var(--box-vert-offset);
background-size: var(--box-size);
background-repeat: no-repeat;
}
.ck-content .todo-list__label:has(input[type="checkbox"]:checked) .todo-list__label__description {
/* Source: https://pictogrammers.com/library/mdi/icon/checkbox-outline/ */
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5A2%2c2 0 0%2c0 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5A2%2c2 0 0%2c0 19%2c3M19%2c5V19H5V5H19M10%2c17L6%2c13L7.41%2c11.58L10%2c14.17L16.59%2c7.58L18%2c9' /%3e%3c/svg%3e");
}
.ck-content .todo-list__label input[type="checkbox"] {
display: none !important;
}
}
/* #region Footnotes */
.footnote-reference a,
.footnote-back-link a {
text-decoration: none !important;
}
li.footnote-item {
position: relative;
width: fit-content;
}
.ck-content .footnote-back-link {
margin-right: 0.25em;
}
.ck-content .footnote-content {
display: inline-block;
width: unset;
}
/* #endregion */
/* #region Widows and orphans */
p,
blockquote {
widows: 4;
orphans: 4;
}
pre > code {
widows: 6;
orphans: 6;
overflow: auto;
white-space: pre-wrap !important;
}
h1,
h2,
h3,
h4,
h5,
h6 {
page-break-after: avoid;
break-after: avoid;
}
/* #endregion */
/* #region Tables */
.table thead th,
.table td,
.table th {
/* Fix center vertical alignment of table cells */
vertical-align: middle;
}
pre {
box-shadow: unset !important;
border: 0.75pt solid gray !important;
border-radius: 2pt !important;
}
th,
span[style] {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
/* #endregion */
/* #region Page breaks */
.page-break {
page-break-after: always;
break-after: always;
}
.page-break > *,
.page-break::after {
display: none !important;
}
/* #endregion */

View File

@@ -1,92 +0,0 @@
import FNote from "./entities/fnote";
import { render } from "preact";
import { CustomNoteList } from "./widgets/collections/NoteList";
import { useCallback, useLayoutEffect, useRef } from "preact/hooks";
import content_renderer from "./services/content_renderer";
interface RendererProps {
note: FNote;
onReady: () => void;
}
async function main() {
const notePath = window.location.hash.substring(1);
const noteId = notePath.split("/").at(-1);
if (!noteId) return;
await import("./print.css");
const froca = (await import("./services/froca")).default;
const note = await froca.getNote(noteId);
render(<App note={note} noteId={noteId} />, document.body);
}
function App({ note, noteId }: { note: FNote | null | undefined, noteId: string }) {
const sentReadyEvent = useRef(false);
const onReady = useCallback(() => {
if (sentReadyEvent.current) return;
window.dispatchEvent(new Event("note-ready"));
window._noteReady = true;
sentReadyEvent.current = true;
}, []);
const props: RendererProps | undefined | null = note && { note, onReady };
if (!note || !props) return <Error404 noteId={noteId} />
useLayoutEffect(() => {
document.body.dataset.noteType = note.type;
}, [ note ]);
return (
<>
{note.type === "book"
? <CollectionRenderer {...props} />
: <SingleNoteRenderer {...props} />
}
</>
);
}
function SingleNoteRenderer({ note, onReady }: RendererProps) {
const containerRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
async function load() {
if (note.type === "text") {
await import("@triliumnext/ckeditor5/src/theme/ck-content.css");
}
const { $renderedContent } = await content_renderer.getRenderedContent(note, { noChildrenList: true });
containerRef.current?.replaceChildren(...$renderedContent);
}
load().then(() => requestAnimationFrame(onReady))
}, [ note ]);
return <>
<h1>{note.title}</h1>
<main ref={containerRef} />
</>;
}
function CollectionRenderer({ note, onReady }: RendererProps) {
return <CustomNoteList
isEnabled
note={note}
notePath={note.getBestNotePath().join("/")}
ntxId="print"
highlightedTokens={null}
media="print"
onReady={onReady}
/>;
}
function Error404({ noteId }: { noteId: string }) {
return (
<main>
<p>The note you are trying to print could not be found.</p>
<small>{noteId}</small>
</main>
)
}
main();

View File

@@ -1,15 +1,5 @@
import $ from "jquery"; import $ from "jquery";
async function loadBootstrap() {
if (document.body.dir === "rtl") {
await import("bootstrap/dist/css/bootstrap.rtl.min.css");
} else {
await import("bootstrap/dist/css/bootstrap.min.css");
}
}
(window as any).$ = $; (window as any).$ = $;
(window as any).jQuery = $; (window as any).jQuery = $;
await loadBootstrap();
$("body").show(); $("body").show();

View File

@@ -2,7 +2,6 @@ import server from "./server.js";
import froca from "./froca.js"; import froca from "./froca.js";
import type FNote from "../entities/fnote.js"; import type FNote from "../entities/fnote.js";
import type { AttributeRow } from "./load_results.js"; import type { AttributeRow } from "./load_results.js";
import { AttributeType } from "@triliumnext/commons";
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) { async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
await server.put(`notes/${noteId}/attribute`, { await server.put(`notes/${noteId}/attribute`, {
@@ -26,14 +25,6 @@ async function removeAttributeById(noteId: string, attributeId: string) {
await server.remove(`notes/${noteId}/attributes/${attributeId}`); 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. * 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. * 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. * @param value the value of the attribute to set.
*/ */
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) { 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. // Create or update the attribute.
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value }); await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
} else { } else {

View File

@@ -210,7 +210,7 @@ function makeToast(id: string, message: string): ToastOptions {
} }
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "deleteNotes") { if (message.taskType !== "deleteNotes") {
return; return;
} }
@@ -228,7 +228,7 @@ ws.subscribeToMessages(async (message) => {
}); });
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "undeleteNotes") { if (message.taskType !== "undeleteNotes") {
return; return;
} }

View File

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

View File

@@ -23,13 +23,11 @@ interface Options {
tooltip?: boolean; tooltip?: boolean;
trim?: boolean; trim?: boolean;
imageHasZoom?: boolean; imageHasZoom?: boolean;
/** If enabled, it will prevent the default behavior in which an empty note would display a list of children. */
noChildrenList?: boolean;
} }
const CODE_MIME_TYPES = new Set(["application/json"]); const CODE_MIME_TYPES = new Set(["application/json"]);
export async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FAttachment, options: Options = {}) { async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FAttachment, options: Options = {}) {
options = Object.assign( options = Object.assign(
{ {
@@ -44,7 +42,7 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo
const $renderedContent = $('<div class="rendered-content">'); const $renderedContent = $('<div class="rendered-content">');
if (type === "text" || type === "book") { if (type === "text" || type === "book") {
await renderText(entity, $renderedContent, options); await renderText(entity, $renderedContent);
} else if (type === "code") { } else if (type === "code") {
await renderCode(entity, $renderedContent); await renderCode(entity, $renderedContent);
} else if (["image", "canvas", "mindMap"].includes(type)) { } else if (["image", "canvas", "mindMap"].includes(type)) {
@@ -116,7 +114,7 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo
}; };
} }
async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>, options: Options = {}) { async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
// entity must be FNote // entity must be FNote
const blob = await note.getBlob(); const blob = await note.getBlob();
@@ -137,7 +135,7 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HT
} }
await formatCodeBlocks($renderedContent); await formatCodeBlocks($renderedContent);
} else if (note instanceof FNote && !options.noChildrenList) { } else if (note instanceof FNote) {
await renderChildrenList($renderedContent, note); await renderChildrenList($renderedContent, note);
} }
} }
@@ -258,19 +256,8 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
</button> </button>
`); `);
$downloadButton.on("click", (e) => { $downloadButton.on("click", () => openService.downloadFileNote(entity.noteId));
e.stopPropagation(); $openButton.on("click", () => openService.openNoteExternally(entity.noteId, entity.mime));
openService.downloadFileNote(entity.noteId)
});
$openButton.on("click", async (e) => {
const iconEl = $openButton.find("> .bx");
iconEl.removeClass("bx bx-link-external");
iconEl.addClass("bx bx-loader spin");
e.stopPropagation();
await openService.openNoteExternally(entity.noteId, entity.mime)
iconEl.removeClass("bx bx-loader spin");
iconEl.addClass("bx bx-link-external");
});
// open doesn't work for protected notes since it works through a browser which isn't in protected session // open doesn't work for protected notes since it works through a browser which isn't in protected session
$openButton.toggle(!entity.isProtected); $openButton.toggle(!entity.isProtected);

View File

@@ -1,39 +1,21 @@
import {readCssVar} from "../utils/css-var";
import Color, { ColorInstance } from "color";
const registeredClasses = new Set<string>(); const registeredClasses = new Set<string>();
// Read the color lightness limits defined in the theme as CSS variables function createClassForColor(color: string | null) {
if (!color?.trim()) {
return "";
}
const lightThemeColorMaxLightness = readCssVar( const normalizedColorName = color.replace(/[^a-z0-9]/gi, "");
document.documentElement,
"tree-item-light-theme-max-color-lightness"
).asNumber(70);
const darkThemeColorMinLightness = readCssVar( if (!normalizedColorName.trim()) {
document.documentElement, return "";
"tree-item-dark-theme-min-color-lightness" }
).asNumber(50);
function createClassForColor(colorString: string | null) { const className = `color-${normalizedColorName}`;
if (!colorString?.trim()) return "";
const color = parseColor(colorString);
if (!color) return "";
const className = `color-${color.hex().substring(1)}`;
if (!registeredClasses.has(className)) { if (!registeredClasses.has(className)) {
const adjustedColor = adjustColorLightness(color, lightThemeColorMaxLightness!, // make the active fancytree selector more specific than the normal color setting
darkThemeColorMinLightness!); $("head").append(`<style>.${className}, span.fancytree-active.${className} { color: ${color} !important; }</style>`);
$("head").append(`<style>
.${className}, span.fancytree-active.${className} {
--light-theme-custom-color: ${adjustedColor.lightThemeColor};
--dark-theme-custom-color: ${adjustedColor.darkThemeColor};
--custom-color-hue: ${getHue(color) ?? 'unset'};
}
</style>`);
registeredClasses.add(className); registeredClasses.add(className);
} }
@@ -41,41 +23,6 @@ function createClassForColor(colorString: string | null) {
return className; return className;
} }
function parseColor(color: string) {
try {
return Color(color);
} catch (ex) {
console.error(ex);
}
}
/**
* Returns a pair of colors — one optimized for light themes and the other for dark themes, derived
* from the specified color to maintain sufficient contrast with each theme.
* The adjustment is performed by limiting the colors lightness in the CIELAB color space,
* according to the lightThemeMaxLightness and darkThemeMinLightness parameters.
*/
function adjustColorLightness(color: ColorInstance, lightThemeMaxLightness: number, darkThemeMinLightness: number) {
const labColor = color.lab();
const lightness = labColor.l();
// For the light theme, limit the maximum lightness
const lightThemeColor = labColor.l(Math.min(lightness, lightThemeMaxLightness)).hex();
// For the dark theme, limit the minimum lightness
const darkThemeColor = labColor.l(Math.max(lightness, darkThemeMinLightness)).hex();
return {lightThemeColor, darkThemeColor};
}
/** Returns the hue of the specified color, or undefined if the color is grayscale. */
function getHue(color: ColorInstance) {
const hslColor = color.hsl();
if (hslColor.saturationl() > 0) {
return hslColor.hue();
}
}
export default { export default {
createClassForColor createClassForColor
}; };

View File

@@ -60,7 +60,7 @@ async function confirmDeleteNoteBoxWithNote(title: string) {
return new Promise<ConfirmDialogResult | undefined>((res) => appContext.triggerCommand("showConfirmDeleteNoteBoxWithNoteDialog", { title, callback: res })); return new Promise<ConfirmDialogResult | undefined>((res) => appContext.triggerCommand("showConfirmDeleteNoteBoxWithNoteDialog", { title, callback: res }));
} }
export async function prompt(props: PromptDialogOptions) { async function prompt(props: PromptDialogOptions) {
return new Promise<string | null>((res) => appContext.triggerCommand("showPromptDialog", { ...props, callback: res })); return new Promise<string | null>((res) => appContext.triggerCommand("showPromptDialog", { ...props, callback: res }));
} }

View File

@@ -48,6 +48,6 @@ function getUrl(docNameValue: string, language: string) {
// Cannot have spaces in the URL due to how JQuery.load works. // Cannot have spaces in the URL due to how JQuery.load works.
docNameValue = docNameValue.replaceAll(" ", "%20"); 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`; return `${basePath}/doc_notes/${language}/${docNameValue}.html`;
} }

View File

@@ -1,8 +1,16 @@
import ws from "./ws.js"; import ws from "./ws.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import { OpenedFileUpdateStatus } from "@triliumnext/commons";
const fileModificationStatus: Record<string, Record<string, OpenedFileUpdateStatus>> = { // TODO: Deduplicate
interface Message {
type: string;
entityType: string;
entityId: string;
lastModifiedMs: number;
filePath: string;
}
const fileModificationStatus: Record<string, Record<string, Message>> = {
notes: {}, notes: {},
attachments: {} attachments: {}
}; };
@@ -31,7 +39,7 @@ function ignoreModification(entityType: string, entityId: string) {
delete fileModificationStatus[entityType][entityId]; delete fileModificationStatus[entityType][entityId];
} }
ws.subscribeToMessages(async message => { ws.subscribeToMessages(async (message: Message) => {
if (message.type !== "openedFileUpdated") { if (message.type !== "openedFileUpdated") {
return; return;
} }

View File

@@ -40,23 +40,20 @@ class FrocaImpl implements Froca {
constructor() { constructor() {
this.initializedPromise = this.loadInitialTree(); this.initializedPromise = this.loadInitialTree();
this.#clear();
} }
async loadInitialTree() { async loadInitialTree() {
const resp = await server.get<SubtreeResponse>("tree"); const resp = await server.get<SubtreeResponse>("tree");
// clear the cache only directly before adding new content which is important for e.g., switching to protected session // clear the cache only directly before adding new content which is important for e.g., switching to protected session
this.#clear();
this.addResp(resp);
}
#clear() {
this.notes = {}; this.notes = {};
this.branches = {}; this.branches = {};
this.attributes = {}; this.attributes = {};
this.attachments = {}; this.attachments = {};
this.blobPromises = {}; this.blobPromises = {};
this.addResp(resp);
} }
async loadSubTree(subTreeNoteId: string) { async loadSubTree(subTreeNoteId: string) {

View File

@@ -8,7 +8,6 @@ import FAttribute, { type FAttributeRow } from "../entities/fattribute.js";
import FAttachment, { type FAttachmentRow } from "../entities/fattachment.js"; import FAttachment, { type FAttachmentRow } from "../entities/fattachment.js";
import type { default as FNote, FNoteRow } from "../entities/fnote.js"; import type { default as FNote, FNoteRow } from "../entities/fnote.js";
import type { EntityChange } from "../server_types.js"; import type { EntityChange } from "../server_types.js";
import type { OptionNames } from "@triliumnext/commons";
async function processEntityChanges(entityChanges: EntityChange[]) { async function processEntityChanges(entityChanges: EntityChange[]) {
const loadResults = new LoadResults(entityChanges); const loadResults = new LoadResults(entityChanges);
@@ -31,8 +30,9 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
continue; // only noise continue; // only noise
} }
options.set(attributeEntity.name as OptionNames, attributeEntity.value); options.set(attributeEntity.name, attributeEntity.value);
loadResults.addOption(attributeEntity.name as OptionNames);
loadResults.addOption(attributeEntity.name);
} else if (ec.entityName === "attachments") { } else if (ec.entityName === "attachments") {
processAttachment(loadResults, ec); processAttachment(loadResults, ec);
} else if (ec.entityName === "blobs") { } else if (ec.entityName === "blobs") {

View File

@@ -21,7 +21,6 @@ import dayjs from "dayjs";
import type NoteContext from "../components/note_context.js"; import type NoteContext from "../components/note_context.js";
import type NoteDetailWidget from "../widgets/note_detail.js"; import type NoteDetailWidget from "../widgets/note_detail.js";
import type Component from "../components/component.js"; import type Component from "../components/component.js";
import { formatLogMessage } from "@triliumnext/commons";
/** /**
* A whole number * A whole number
@@ -456,7 +455,7 @@ export interface Api {
/** /**
* Log given message to the log pane in UI * Log given message to the log pane in UI
*/ */
log(message: string | object): void; log(message: string): void;
} }
/** /**
@@ -697,7 +696,7 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
this.log = (message) => { this.log = (message) => {
const { noteId } = this.startNote; const { noteId } = this.startNote;
message = `${utils.now()}: ${formatLogMessage(message)}`; message = `${utils.now()}: ${message}`;
console.log(`Script ${noteId}: ${message}`); console.log(`Script ${noteId}: ${message}`);

View File

@@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest";
describe("i18n", () => { describe("i18n", () => {
it("translations are valid JSON", () => { it("translations are valid JSON", () => {
for (const locale of LOCALES) { for (const locale of LOCALES) {
if (locale.contentOnly || locale.id === "en_rtl") { if (locale.contentOnly) {
continue; continue;
} }

View File

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

View File

@@ -4,7 +4,6 @@ import ws from "./ws.js";
import utils from "./utils.js"; import utils from "./utils.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import { t } from "./i18n.js"; import { t } from "./i18n.js";
import { WebSocketMessage } from "@triliumnext/commons";
type BooleanLike = boolean | "true" | "false"; type BooleanLike = boolean | "true" | "false";
@@ -67,7 +66,7 @@ function makeToast(id: string, message: string): ToastOptions {
} }
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "importNotes") { if (message.taskType !== "importNotes") {
return; return;
} }
@@ -88,8 +87,8 @@ ws.subscribeToMessages(async (message) => {
} }
}); });
ws.subscribeToMessages(async (message: WebSocketMessage) => { ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "importAttachments") { if (message.taskType !== "importAttachments") {
return; return;
} }

View File

@@ -1,44 +0,0 @@
import { NoteType } from "@triliumnext/commons";
import FNote from "../entities/fnote";
import { ViewTypeOptions } from "../widgets/collections/interface";
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",
presentation: null
};
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; return action;
} }
export function getActionSync(actionName: string) {
return keyboardActionRepo[actionName];
}
function updateDisplayedShortcuts($container: JQuery<HTMLElement>) { function updateDisplayedShortcuts($container: JQuery<HTMLElement>) {
//@ts-ignore //@ts-ignore
//TODO: each() does not support async callbacks. //TODO: each() does not support async callbacks.

View File

@@ -3,8 +3,16 @@ import linkContextMenuService from "../menus/link_context_menu.js";
import appContext, { type NoteCommandData } from "../components/app_context.js"; import appContext, { type NoteCommandData } from "../components/app_context.js";
import froca from "./froca.js"; import froca from "./froca.js";
import utils from "./utils.js"; import utils from "./utils.js";
import { ALLOWED_PROTOCOLS } from "@triliumnext/commons";
import { openInCurrentNoteContext } from "../components/note_context.js"; // Be consistent with `allowedSchemes` in `src\services\html_sanitizer.ts`
// TODO: Deduplicate with server once we can.
export const ALLOWED_PROTOCOLS = [
'http', 'https', 'ftp', 'ftps', 'mailto', 'data', 'evernote', 'file', 'facetime', 'gemini', 'git',
'gopher', 'imap', 'irc', 'irc6', 'jabber', 'jar', 'lastfm', 'ldap', 'ldaps', 'magnet', 'message',
'mumble', 'nfs', 'onenote', 'pop', 'rmi', 's3', 'sftp', 'skype', 'sms', 'spotify', 'steam', 'svn', 'udp',
'view-source', 'vlc', 'vnc', 'ws', 'wss', 'xmpp', 'jdbc', 'slack', 'tel', 'smb', 'zotero', 'geo',
'mid'
];
function getNotePathFromUrl(url: string) { function getNotePathFromUrl(url: string) {
const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url); const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url);
@@ -27,7 +35,8 @@ async function getLinkIcon(noteId: string, viewMode: ViewMode | undefined) {
return icon; return icon;
} }
export type ViewMode = "default" | "source" | "attachments" | "contextual-help"; // TODO: Remove `string` once all the view modes have been mapped.
type ViewMode = "default" | "source" | "attachments" | "contextual-help" | string;
export interface ViewScope { export interface ViewScope {
/** /**
@@ -317,7 +326,21 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
viewScope viewScope
}); });
} else if (isLeftClick) { } else if (isLeftClick) {
openInCurrentNoteContext(evt, notePath, viewScope); const ntxId = $(evt?.target as any)
.closest("[data-ntx-id]")
.attr("data-ntx-id");
const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext();
if (noteContext) {
noteContext.setNote(notePath, { viewScope }).then(() => {
if (noteContext !== appContext.tabManager.getActiveContext()) {
appContext.tabManager.activateNoteContext(noteContext.ntxId);
}
});
} else {
appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true });
}
} }
} else if (hrefLink) { } else if (hrefLink) {
const withinEditLink = $link?.hasClass("ck-link-actions__preview"); const withinEditLink = $link?.hasClass("ck-link-actions__preview");

View File

@@ -1,4 +1,4 @@
import type { AttachmentRow, EtapiTokenRow, OptionNames } from "@triliumnext/commons"; import type { AttachmentRow, EtapiTokenRow } from "@triliumnext/commons";
import type { AttributeType } from "../entities/fattribute.js"; import type { AttributeType } from "../entities/fattribute.js";
import type { EntityChange } from "../server_types.js"; import type { EntityChange } from "../server_types.js";
@@ -67,7 +67,7 @@ export default class LoadResults {
private revisionRows: RevisionRow[]; private revisionRows: RevisionRow[];
private noteReorderings: string[]; private noteReorderings: string[];
private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[]; private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[];
private optionNames: OptionNames[]; private optionNames: string[];
private attachmentRows: AttachmentRow[]; private attachmentRows: AttachmentRow[];
public hasEtapiTokenChanges: boolean = false; public hasEtapiTokenChanges: boolean = false;
@@ -180,11 +180,11 @@ export default class LoadResults {
return this.contentNoteIdToComponentId.find((l) => l.noteId === noteId && l.componentId !== componentId); return this.contentNoteIdToComponentId.find((l) => l.noteId === noteId && l.componentId !== componentId);
} }
addOption(name: OptionNames) { addOption(name: string) {
this.optionNames.push(name); this.optionNames.push(name);
} }
isOptionReloaded(name: OptionNames) { isOptionReloaded(name: string) {
return this.optionNames.includes(name); return this.optionNames.includes(name);
} }

View File

@@ -36,8 +36,6 @@ export interface Suggestion {
commandId?: string; commandId?: string;
commandDescription?: string; commandDescription?: string;
commandShortcut?: string; commandShortcut?: string;
attributeSnippet?: string;
highlightedAttributeSnippet?: string;
} }
export interface Options { export interface Options {
@@ -325,33 +323,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
html += '</div>'; html += '</div>';
return html; return html;
} }
// Add special class for search-notes action return `<span class="${suggestion.icon ?? "bx bx-note"}"></span> ${suggestion.highlightedNotePathTitle}`;
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;
} }
}, },
// we can't cache identical searches because notes can be created / renamed, new recent notes can be added // we can't cache identical searches because notes can be created / renamed, new recent notes can be added

View File

@@ -0,0 +1,71 @@
import type FNote from "../entities/fnote.js";
import BoardView from "../widgets/view_widgets/board_view/index.js";
import CalendarView from "../widgets/view_widgets/calendar_view.js";
import GeoView from "../widgets/view_widgets/geo_view/index.js";
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
import TableView from "../widgets/view_widgets/table_view/index.js";
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
import type ViewMode from "../widgets/view_widgets/view_mode.js";
const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board"] as const;
export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">;
export type ViewTypeOptions = typeof allViewTypes[number];
export default class NoteListRenderer {
private viewType: ViewTypeOptions;
private args: ArgsWithoutNoteId;
public viewMode?: ViewMode<any>;
constructor(args: ArgsWithoutNoteId) {
this.args = args;
this.viewType = this.#getViewType(args.parentNote);
}
#getViewType(parentNote: FNote): ViewTypeOptions {
const viewType = parentNote.getLabelValue("viewType");
if (!(allViewTypes as readonly string[]).includes(viewType || "")) {
// when not explicitly set, decide based on the note type
return parentNote.type === "search" ? "list" : "grid";
} else {
return viewType as ViewTypeOptions;
}
}
get isFullHeight() {
switch (this.viewType) {
case "list":
case "grid":
return false;
default:
return true;
}
}
async renderList() {
const args = this.args;
const viewMode = this.#buildViewMode(args);
this.viewMode = viewMode;
await viewMode.beforeRender();
return await viewMode.renderList();
}
#buildViewMode(args: ViewModeArgs) {
switch (this.viewType) {
case "calendar":
return new CalendarView(args);
case "table":
return new TableView(args);
case "geoMap":
return new GeoView(args);
case "board":
return new BoardView(args);
case "list":
case "grid":
default:
return new ListOrGridView(this.viewType, args);
}
}
}

View File

@@ -1,7 +1,7 @@
import { t } from "./i18n.js"; import { t } from "./i18n.js";
import froca from "./froca.js"; import froca from "./froca.js";
import server from "./server.js"; import server from "./server.js";
import type { MenuCommandItem, MenuItem, MenuItemBadge, MenuSeparatorItem } from "../menus/context_menu.js"; import type { MenuCommandItem, MenuItem, MenuItemBadge } from "../menus/context_menu.js";
import type { NoteType } from "../entities/fnote.js"; import type { NoteType } from "../entities/fnote.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js"; import type { TreeCommandNames } from "../menus/tree_context_menu.js";
@@ -73,7 +73,7 @@ const BETA_BADGE = {
title: t("note_types.beta-feature") title: t("note_types.beta-feature")
}; };
const SEPARATOR: MenuSeparatorItem = { kind: "separator" }; const SEPARATOR = { title: "----" };
const creationDateCache = new Map<string, Date>(); const creationDateCache = new Map<string, Date>();
let rootCreationDate: Date | undefined; let rootCreationDate: Date | undefined;
@@ -81,8 +81,8 @@ let rootCreationDate: Date | undefined;
async function getNoteTypeItems(command?: TreeCommandNames) { async function getNoteTypeItems(command?: TreeCommandNames) {
const items: MenuItem<TreeCommandNames>[] = [ const items: MenuItem<TreeCommandNames>[] = [
...getBlankNoteTypes(command), ...getBlankNoteTypes(command),
...await getBuiltInTemplates(null, command, false),
...await getBuiltInTemplates(t("note_types.collections"), command, true), ...await getBuiltInTemplates(t("note_types.collections"), command, true),
...await getBuiltInTemplates(null, command, false),
...await getUserTemplates(command) ...await getUserTemplates(command)
]; ];
@@ -121,10 +121,7 @@ async function getUserTemplates(command?: TreeCommandNames) {
} }
const items: MenuItem<TreeCommandNames>[] = [ const items: MenuItem<TreeCommandNames>[] = [
{ SEPARATOR
title: t("note_type_chooser.templates"),
kind: "header"
}
]; ];
for (const templateNote of templateNotes) { for (const templateNote of templateNotes) {
@@ -161,15 +158,15 @@ async function getBuiltInTemplates(title: string | null, command: TreeCommandNam
if (title) { if (title) {
items.push({ items.push({
title: title, title: title,
kind: "header" enabled: false,
uiIcon: "bx bx-empty"
}); });
} else { } else {
items.push(SEPARATOR); items.push(SEPARATOR);
} }
for (const templateNote of childNotes) { for (const templateNote of childNotes) {
if (templateNote.hasLabel("collection") !== filterCollections || if (templateNote.hasLabel("collection") !== filterCollections) {
!templateNote.hasLabel("template")) {
continue; continue;
} }

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 const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache
download(url); 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); const openAttachmentExternally = async (attachmentId: string, mime: string) => await openExternally("attachments", attachmentId, mime);
function getHost() { function getHost() {

View File

@@ -20,7 +20,7 @@ class Options {
this.arr = arr; this.arr = arr;
} }
get(key: OptionNames) { get(key: string) {
return this.arr?.[key] as string; return this.arr?.[key] as string;
} }
@@ -40,7 +40,7 @@ class Options {
} }
} }
getInt(key: OptionNames) { getInt(key: string) {
const value = this.arr?.[key]; const value = this.arr?.[key];
if (typeof value === "number") { if (typeof value === "number") {
return value; return value;
@@ -52,7 +52,7 @@ class Options {
return null; return null;
} }
getFloat(key: OptionNames) { getFloat(key: string) {
const value = this.arr?.[key]; const value = this.arr?.[key];
if (typeof value !== "string") { if (typeof value !== "string") {
return null; return null;
@@ -60,15 +60,15 @@ class Options {
return parseFloat(value); return parseFloat(value);
} }
is(key: OptionNames) { is(key: string) {
return this.arr[key] === "true"; return this.arr[key] === "true";
} }
set(key: OptionNames, value: OptionValue) { set(key: string, value: OptionValue) {
this.arr[key] = value; this.arr[key] = value;
} }
async save(key: OptionNames, value: OptionValue) { async save(key: string, value: OptionValue) {
this.set(key, value); this.set(key, value);
const payload: Record<string, OptionValue> = {}; const payload: Record<string, OptionValue> = {};
@@ -85,7 +85,7 @@ class Options {
await server.put<void>("options", newValues); await server.put<void>("options", newValues);
} }
async toggle(key: OptionNames) { async toggle(key: string) {
await this.save(key, (!this.is(key)).toString()); await this.save(key, (!this.is(key)).toString());
} }
} }

View File

@@ -107,11 +107,11 @@ function makeToast(message: Message, title: string, text: string): ToastOptions
} }
ws.subscribeToMessages(async (message) => { ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "protectNotes") { if (message.taskType !== "protectNotes") {
return; return;
} }
const isProtecting = message.data?.protect; const isProtecting = message.data.protect;
const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title"); const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title");
if (message.type === "taskError") { if (message.type === "taskError") {

View File

@@ -1,5 +1,5 @@
import options from "./options.js"; import options from "./options.js";
import Split from "@triliumnext/split.js"; import Split from "split.js"
export const DEFAULT_GUTTER_SIZE = 5; export const DEFAULT_GUTTER_SIZE = 5;
@@ -10,10 +10,6 @@ let leftInstance: ReturnType<typeof Split> | null;
let rightPaneWidth: number; let rightPaneWidth: number;
let rightInstance: ReturnType<typeof Split> | null; let rightInstance: ReturnType<typeof Split> | null;
const noteSplitMap = new Map<string[], ReturnType<typeof Split> | undefined>(); // key: a group of ntxIds, value: the corresponding Split instance
const noteSplitRafMap = new Map<string[], number>();
let splitNoteContainer: HTMLElement | undefined;
function setupLeftPaneResizer(leftPaneVisible: boolean) { function setupLeftPaneResizer(leftPaneVisible: boolean) {
if (leftInstance) { if (leftInstance) {
leftInstance.destroy(); leftInstance.destroy();
@@ -46,7 +42,6 @@ function setupLeftPaneResizer(leftPaneVisible: boolean) {
sizes: [leftPaneWidth, restPaneWidth], sizes: [leftPaneWidth, restPaneWidth],
gutterSize: DEFAULT_GUTTER_SIZE, gutterSize: DEFAULT_GUTTER_SIZE,
minSize: [150, 300], minSize: [150, 300],
rtl: glob.isRtl,
onDragEnd: (sizes) => { onDragEnd: (sizes) => {
leftPaneWidth = Math.round(sizes[0]); leftPaneWidth = Math.round(sizes[0]);
options.save("leftPaneWidth", Math.round(sizes[0])); options.save("leftPaneWidth", Math.round(sizes[0]));
@@ -80,7 +75,6 @@ function setupRightPaneResizer() {
sizes: [100 - rightPaneWidth, rightPaneWidth], sizes: [100 - rightPaneWidth, rightPaneWidth],
gutterSize: DEFAULT_GUTTER_SIZE, gutterSize: DEFAULT_GUTTER_SIZE,
minSize: [300, 180], minSize: [300, 180],
rtl: glob.isRtl,
onDragEnd: (sizes) => { onDragEnd: (sizes) => {
rightPaneWidth = Math.round(sizes[1]); rightPaneWidth = Math.round(sizes[1]);
options.save("rightPaneWidth", Math.round(sizes[1])); options.save("rightPaneWidth", Math.round(sizes[1]));
@@ -89,87 +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, {
rtl: glob.isRtl,
gutterSize: DEFAULT_GUTTER_SIZE,
minSize: 150,
});
noteSplitMap.set(targetNtxIds, splitInstance);
noteSplitRafMap.delete(targetNtxIds);
});
noteSplitRafMap.set(targetNtxIds, rafId);
}
export default { export default {
setupLeftPaneResizer, setupLeftPaneResizer,
setupRightPaneResizer, setupRightPaneResizer
setupNoteSplitResizer,
delNoteSplitResizer,
moveNoteSplitResizer
}; };

View File

@@ -218,7 +218,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
if (utils.isElectron()) { if (utils.isElectron()) {
const ipc = utils.dynamicRequire("electron").ipcRenderer; 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) { if (arg.statusCode >= 200 && arg.statusCode < 300) {
handleSuccessfulResponse(arg); handleSuccessfulResponse(arg);
} else { } else {

View File

@@ -1,5 +1,5 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js"; import shortcuts, { keyMatches, matchesShortcut } from "./shortcuts.js";
// Mock utils module // Mock utils module
vi.mock("./utils.js", () => ({ vi.mock("./utils.js", () => ({
@@ -119,6 +119,11 @@ describe("shortcuts", () => {
metaKey: options.metaKey || false metaKey: options.metaKey || false
} as KeyboardEvent); } as KeyboardEvent);
it("should match simple key shortcuts", () => {
const event = createKeyboardEvent({ key: "a", code: "KeyA" });
expect(matchesShortcut(event, "a")).toBe(true);
});
it("should match shortcuts with modifiers", () => { it("should match shortcuts with modifiers", () => {
const event = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true }); const event = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
expect(matchesShortcut(event, "ctrl+a")).toBe(true); expect(matchesShortcut(event, "ctrl+a")).toBe(true);
@@ -143,28 +148,6 @@ describe("shortcuts", () => {
expect(matchesShortcut(event, "a")).toBe(false); expect(matchesShortcut(event, "a")).toBe(false);
}); });
it("should not match when no modifiers are used", () => {
const event = createKeyboardEvent({ key: "a", code: "KeyA" });
expect(matchesShortcut(event, "a")).toBe(false);
});
it("should match some keys even with no modifiers", () => {
// Bare function keys
let event = createKeyboardEvent({ key: "F1", code: "F1" });
expect(matchesShortcut(event, "F1")).toBeTruthy();
expect(matchesShortcut(event, "f1")).toBeTruthy();
// Function keys with shift
event = createKeyboardEvent({ key: "F1", code: "F1", shiftKey: true });
expect(matchesShortcut(event, "Shift+F1")).toBeTruthy();
// Special keys
for (const keyCode of [ "Delete", "Enter" ]) {
event = createKeyboardEvent({ key: keyCode, code: keyCode });
expect(matchesShortcut(event, keyCode), `Key ${keyCode}`).toBeTruthy();
}
});
it("should handle alternative modifier names", () => { it("should handle alternative modifier names", () => {
const ctrlEvent = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true }); const ctrlEvent = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
expect(matchesShortcut(ctrlEvent, "control+a")).toBe(true); expect(matchesShortcut(ctrlEvent, "control+a")).toBe(true);
@@ -337,36 +320,4 @@ describe("shortcuts", () => {
expect(event.preventDefault).not.toHaveBeenCalled(); expect(event.preventDefault).not.toHaveBeenCalled();
}); });
}); });
describe('isIMEComposing', () => {
it('should return true when event.isComposing is true', () => {
const event = { isComposing: true, keyCode: 65 } as KeyboardEvent;
expect(isIMEComposing(event)).toBe(true);
});
it('should return true when keyCode is 229', () => {
const event = { isComposing: false, keyCode: 229 } as KeyboardEvent;
expect(isIMEComposing(event)).toBe(true);
});
it('should return true when both isComposing is true and keyCode is 229', () => {
const event = { isComposing: true, keyCode: 229 } as KeyboardEvent;
expect(isIMEComposing(event)).toBe(true);
});
it('should return false for normal keys', () => {
const event = { isComposing: false, keyCode: 65 } as KeyboardEvent;
expect(isIMEComposing(event)).toBe(false);
});
it('should return false when isComposing is undefined and keyCode is not 229', () => {
const event = { keyCode: 13 } as KeyboardEvent;
expect(isIMEComposing(event)).toBe(false);
});
it('should handle null/undefined events gracefully', () => {
expect(isIMEComposing(null as any)).toBe(false);
expect(isIMEComposing(undefined as any)).toBe(false);
});
});
}); });

View File

@@ -36,35 +36,8 @@ const keyMap: { [key: string]: string[] } = {
}; };
// Function keys // Function keys
const functionKeyCodes: string[] = [];
for (let i = 1; i <= 19; i++) { for (let i = 1; i <= 19; i++) {
const keyCode = `F${i}`; keyMap[`f${i}`] = [`F${i}`];
functionKeyCodes.push(keyCode);
keyMap[`f${i}`] = [ keyCode ];
}
const KEYCODES_WITH_NO_MODIFIER = new Set([
"Delete",
"Enter",
...functionKeyCodes
]);
/**
* Check if IME (Input Method Editor) is composing
* This is used to prevent keyboard shortcuts from firing during IME composition
* @param e - The keyboard event to check
* @returns true if IME is currently composing, false otherwise
*/
export function isIMEComposing(e: KeyboardEvent): boolean {
// Handle null/undefined events gracefully
if (!e) {
return false;
}
// Standard check for composition state
// e.isComposing is true when IME is actively composing
// e.keyCode === 229 is a fallback for older browsers where 229 indicates IME processing
return e.isComposing || e.keyCode === 229;
} }
function removeGlobalShortcut(namespace: string) { function removeGlobalShortcut(namespace: string) {
@@ -95,13 +68,6 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
} }
const e = evt as KeyboardEvent; const e = evt as KeyboardEvent;
// Skip processing if IME is composing to prevent shortcuts from
// interfering with text input in CJK languages
if (isIMEComposing(e)) {
return;
}
if (matchesShortcut(e, keyboardShortcut)) { if (matchesShortcut(e, keyboardShortcut)) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -171,12 +137,6 @@ export function matchesShortcut(e: KeyboardEvent, shortcut: string): boolean {
const expectedShift = modifiers.includes('shift'); const expectedShift = modifiers.includes('shift');
const expectedMeta = modifiers.includes('meta') || modifiers.includes('cmd') || modifiers.includes('command'); const expectedMeta = modifiers.includes('meta') || modifiers.includes('cmd') || modifiers.includes('command');
// Refuse key combinations that don't include modifiers because they interfere with the normal usage of the application.
// Some keys such as function keys are an exception.
if (!(expectedCtrl || expectedAlt || expectedShift || expectedMeta) && !KEYCODES_WITH_NO_MODIFIER.has(e.code)) {
return false;
}
return e.ctrlKey === expectedCtrl && return e.ctrlKey === expectedCtrl &&
e.altKey === expectedAlt && e.altKey === expectedAlt &&
e.shiftKey === expectedShift && e.shiftKey === expectedShift &&

View File

@@ -61,11 +61,7 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
highlightedText = highlightAuto(text); highlightedText = highlightAuto(text);
} else if (normalizedMimeType) { } else if (normalizedMimeType) {
await ensureMimeTypesForHighlighting(normalizedMimeType); await ensureMimeTypesForHighlighting(normalizedMimeType);
try {
highlightedText = highlight(text, { language: normalizedMimeType }); highlightedText = highlight(text, { language: normalizedMimeType });
} catch (e) {
console.warn("Unable to apply syntax highlight.", e);
}
} }
if (highlightedText) { if (highlightedText) {
@@ -80,7 +76,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
// Load theme. // Load theme.
const currentThemeName = String(options.get("codeBlockTheme")); const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName); loadHighlightingTheme(currentThemeName);
// Load mime types. // Load mime types.
let mimeTypes: MimeType[]; let mimeTypes: MimeType[];
@@ -102,16 +98,17 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
highlightingLoaded = true; highlightingLoaded = true;
} }
export async function loadHighlightingTheme(themeName: string) { export function loadHighlightingTheme(themeName: string) {
const themePrefix = "default:"; const themePrefix = "default:";
let theme: Theme | null = null; let theme: Theme | null = null;
if (glob.device === "print") { if (themeName.includes(themePrefix)) {
theme = Themes.vs;
} else if (themeName.includes(themePrefix)) {
theme = Themes[themeName.substring(themePrefix.length)]; theme = Themes[themeName.substring(themePrefix.length)];
} }
if (!theme) {
theme = Themes.default;
}
await loadTheme(theme ?? Themes.default); loadTheme(theme);
} }
/** /**

View File

@@ -1,9 +1,10 @@
import ws from "./ws.js";
import utils from "./utils.js"; import utils from "./utils.js";
export interface ToastOptions { export interface ToastOptions {
id?: string; id?: string;
icon: string; icon: string;
title?: string; title: string;
message: string; message: string;
delay?: number; delay?: number;
autohide?: boolean; autohide?: boolean;
@@ -11,9 +12,8 @@ export interface ToastOptions {
} }
function toast(options: ToastOptions) { function toast(options: ToastOptions) {
const $toast = $(options.title const $toast = $(
? `\ `<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<strong class="me-auto"> <strong class="me-auto">
<span class="bx bx-${options.icon}"></span> <span class="bx bx-${options.icon}"></span>
@@ -23,20 +23,9 @@ function toast(options: ToastOptions) {
</div> </div>
<div class="toast-body"></div> <div class="toast-body"></div>
</div>` </div>`
: `
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-icon">
<span class="bx bx-${options.icon}"></span>
</div>
<div class="toast-body"></div>
<div class="toast-header">
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>`
); );
$toast.toggleClass("no-title", !options.title); $toast.find(".toast-title").text(options.title);
$toast.find(".toast-title").text(options.title ?? "");
$toast.find(".toast-body").html(options.message); $toast.find(".toast-body").html(options.message);
if (options.id) { if (options.id) {
@@ -81,6 +70,7 @@ function showMessage(message: string, delay = 2000) {
console.debug(utils.now(), "message:", message); console.debug(utils.now(), "message:", message);
toast({ toast({
title: "Info",
icon: "check", icon: "check",
message: message, message: message,
autohide: true, autohide: true,
@@ -92,6 +82,7 @@ export function showError(message: string, delay = 10000) {
console.log(utils.now(), "error: ", message); console.log(utils.now(), "error: ", message);
toast({ toast({
title: "Error",
icon: "alert", icon: "alert",
message: message, message: message,
autohide: true, autohide: true,

View File

@@ -4,6 +4,9 @@ import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js"; import hoistedNoteService from "./hoisted_note.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
/**
* @returns {string|null}
*/
async function resolveNotePath(notePath: string, hoistedNoteId = "root") { async function resolveNotePath(notePath: string, hoistedNoteId = "root") {
const runPath = await resolveNotePathToSegments(notePath, hoistedNoteId); const runPath = await resolveNotePathToSegments(notePath, hoistedNoteId);

View File

@@ -1,6 +1,5 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import type { ViewScope } from "./link.js"; import type { ViewScope } from "./link.js";
import FNote from "../entities/fnote";
const SVG_MIME = "image/svg+xml"; const SVG_MIME = "image/svg+xml";
@@ -47,6 +46,27 @@ function parseDate(str: string) {
} }
} }
// Source: https://stackoverflow.com/a/30465299/4898894
function getMonthsInDateRange(startDate: string, endDate: string) {
const start = startDate.split("-");
const end = endDate.split("-");
const startYear = parseInt(start[0]);
const endYear = parseInt(end[0]);
const dates: string[] = [];
for (let i = startYear; i <= endYear; i++) {
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
const startMon = i === startYear ? parseInt(start[1]) - 1 : 0;
for (let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j + 1) {
const month = j + 1;
const displayMonth = month < 10 ? "0" + month : month;
dates.push([i, displayMonth].join("-"));
}
}
return dates;
}
function padNum(num: number) { function padNum(num: number) {
return `${num <= 9 ? "0" : ""}${num}`; return `${num <= 9 ? "0" : ""}${num}`;
} }
@@ -128,19 +148,7 @@ export function isElectron() {
return !!(window && window.process && window.process.type); return !!(window && window.process && window.process.type);
} }
/** function isMac() {
* Returns `true` if the client is running as a PWA, otherwise `false`.
*/
export function isPWA() {
return (
window.matchMedia('(display-mode: standalone)').matches
|| window.matchMedia('(display-mode: window-controls-overlay)').matches
|| window.navigator.standalone
|| window.navigator.windowControlsOverlay
);
}
export function isMac() {
return navigator.platform.indexOf("Mac") > -1; return navigator.platform.indexOf("Mac") > -1;
} }
@@ -177,11 +185,7 @@ export function escapeQuotes(value: string) {
return value.replaceAll('"', "&quot;"); return value.replaceAll('"', "&quot;");
} }
export function formatSize(size: number | null | undefined) { function formatSize(size: number) {
if (size === null || size === undefined) {
return "";
}
size = Math.max(Math.round(size / 1024), 1); size = Math.max(Math.round(size / 1024), 1);
if (size < 1024) { if (size < 1024) {
@@ -288,55 +292,7 @@ function isHtmlEmpty(html: string) {
); );
} }
function formatHtml(html: string) { async function clearBrowserCache() {
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() {
if (isElectron()) { if (isElectron()) {
const win = dynamicRequire("@electron/remote").getCurrentWindow(); const win = dynamicRequire("@electron/remote").getCurrentWindow();
await win.webContents.session.clearCache(); await win.webContents.session.clearCache();
@@ -350,13 +306,7 @@ function copySelectionToClipboard() {
} }
} }
type dynamicRequireMappings = { export function dynamicRequire(moduleName: string) {
"@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]>{
if (typeof __non_webpack_require__ !== "undefined") { if (typeof __non_webpack_require__ !== "undefined") {
return __non_webpack_require__(moduleName); return __non_webpack_require__(moduleName);
} else { } else {
@@ -487,7 +437,7 @@ function sleep(time_ms: number) {
}); });
} }
export function escapeRegExp(str: string) { function escapeRegExp(str: string) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
} }
@@ -620,7 +570,8 @@ function copyHtmlToClipboard(content: string) {
document.removeEventListener("copy", listener); 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()}`; return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`;
} }
@@ -789,7 +740,7 @@ function isUpdateAvailable(latestVersion: string | null | undefined, currentVers
return compareVersions(latestVersion, currentVersion) > 0; return compareVersions(latestVersion, currentVersion) > 0;
} }
export function isLaunchBarConfig(noteId: string) { function isLaunchBarConfig(noteId: string) {
return ["_lbRoot", "_lbAvailableLaunchers", "_lbVisibleLaunchers", "_lbMobileRoot", "_lbMobileAvailableLaunchers", "_lbMobileVisibleLaunchers"].includes(noteId); return ["_lbRoot", "_lbAvailableLaunchers", "_lbVisibleLaunchers", "_lbMobileRoot", "_lbMobileAvailableLaunchers", "_lbMobileVisibleLaunchers"].includes(noteId);
} }
@@ -837,55 +788,12 @@ export function arrayEqual<T>(a: T[], b: T[]) {
return true; 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";
}
}
/**
* Handles left or right placement of e.g. tooltips in case of right-to-left languages. If the current language is a RTL one, then left and right are swapped. Other directions are unaffected.
* @param placement a string optionally containing a "left" or "right" value.
* @returns a left/right value swapped if needed, or the same as input otherwise.
*/
export function handleRightToLeftPlacement<T extends string>(placement: T) {
if (!glob.isRtl) return placement;
if (placement === "left") return "right";
if (placement === "right") return "left";
return placement;
}
export default { export default {
reloadFrontendApp, reloadFrontendApp,
restartDesktopApp, restartDesktopApp,
reloadTray, reloadTray,
parseDate, parseDate,
getMonthsInDateRange,
formatDateISO, formatDateISO,
formatDateTime, formatDateTime,
formatTimeInterval, formatTimeInterval,
@@ -893,7 +801,6 @@ export default {
localNowDateTime, localNowDateTime,
now, now,
isElectron, isElectron,
isPWA,
isMac, isMac,
isCtrlKey, isCtrlKey,
assertArguments, assertArguments,
@@ -906,7 +813,6 @@ export default {
getNoteTypeClass, getNoteTypeClass,
getMimeTypeClass, getMimeTypeClass,
isHtmlEmpty, isHtmlEmpty,
formatHtml,
clearBrowserCache, clearBrowserCache,
copySelectionToClipboard, copySelectionToClipboard,
dynamicRequire, dynamicRequire,

View File

@@ -6,11 +6,9 @@ import frocaUpdater from "./froca_updater.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import { t } from "./i18n.js"; import { t } from "./i18n.js";
import type { EntityChange } from "../server_types.js"; import type { EntityChange } from "../server_types.js";
import { WebSocketMessage } from "@triliumnext/commons";
import toast from "./toast.js";
type MessageHandler = (message: WebSocketMessage) => void; type MessageHandler = (message: any) => void;
let messageHandlers: MessageHandler[] = []; const messageHandlers: MessageHandler[] = [];
let ws: WebSocket; let ws: WebSocket;
let lastAcceptedEntityChangeId = window.glob.maxEntityChangeIdAtLoad; let lastAcceptedEntityChangeId = window.glob.maxEntityChangeIdAtLoad;
@@ -49,14 +47,10 @@ function logInfo(message: string) {
window.logError = logError; window.logError = logError;
window.logInfo = logInfo; window.logInfo = logInfo;
export function subscribeToMessages(messageHandler: MessageHandler) { function subscribeToMessages(messageHandler: MessageHandler) {
messageHandlers.push(messageHandler); messageHandlers.push(messageHandler);
} }
export function unsubscribeToMessage(messageHandler: MessageHandler) {
messageHandlers = messageHandlers.filter(handler => handler !== messageHandler);
}
// used to serialize frontend update operations // used to serialize frontend update operations
let consumeQueuePromise: Promise<void> | null = null; let consumeQueuePromise: Promise<void> | null = null;
@@ -279,17 +273,13 @@ function connectWebSocket() {
async function sendPing() { async function sendPing() {
if (Date.now() - lastPingTs > 30000) { if (Date.now() - lastPingTs > 30000) {
console.warn(utils.now(), "Lost websocket connection to the backend"); console.log(
toast.showPersistent({ utils.now(),
id: "lost-websocket-connection", "Lost websocket connection to the backend. If you keep having this issue repeatedly, you might want to check your reverse proxy (nginx, apache) configuration and allow/unblock WebSocket."
title: t("ws.lost-websocket-connection-title"), );
message: t("ws.lost-websocket-connection-message"),
icon: "no-signal"
});
} }
if (ws.readyState === ws.OPEN) { if (ws.readyState === ws.OPEN) {
toast.closePersistent("lost-websocket-connection");
ws.send( ws.send(
JSON.stringify({ JSON.stringify({
type: "ping", type: "ping",
@@ -304,8 +294,6 @@ async function sendPing() {
} }
setTimeout(() => { setTimeout(() => {
if (glob.device === "print") return;
ws = connectWebSocket(); ws = connectWebSocket();
lastPingTs = Date.now(); lastPingTs = Date.now();

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import "normalize.css"; import "normalize.css";
import "boxicons/css/boxicons.min.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/styles/index.css";
import "@triliumnext/share-theme/scripts/index.js"; 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

@@ -60,7 +60,7 @@
appearance: none; appearance: none;
text-align: center; text-align: center;
border: 0; border: 0;
border-inline-start: unset; border-left: unset;
background-color: var(--menu-background-color); background-color: var(--menu-background-color);
font-weight: bold; font-weight: bold;
outline: 0; outline: 0;
@@ -102,7 +102,7 @@
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
inset-inline-end: 0; right: 0;
bottom: 0; bottom: 0;
width: 1px; width: 1px;
background-color: var(--main-border-color); background-color: var(--main-border-color);

View File

@@ -299,7 +299,7 @@
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
inset-inline-start: -100%; left: -100%;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: linear-gradient(90deg, transparent, var(--hover-item-background-color, rgba(0, 0, 0, 0.03)), transparent); background: linear-gradient(90deg, transparent, var(--hover-item-background-color, rgba(0, 0, 0, 0.03)), transparent);
@@ -406,10 +406,10 @@
@keyframes shimmer { @keyframes shimmer {
0% { 0% {
inset-inline-start: -100%; left: -100%;
} }
100% { 100% {
inset-inline-start: 100%; left: 100%;
} }
} }

View File

@@ -0,0 +1,322 @@
:root {
--main-background-color: white;
--root-background: var(--main-background-color);
--launcher-pane-background-color: var(--main-background-color);
--main-text-color: black;
--input-text-color: var(--main-text-color);
--print-font-size: 11pt;
}
@page {
margin: 2cm;
}
.ck-content {
font-size: var(--print-font-size);
text-align: justify;
}
.note-detail-readonly-text {
padding: 0 !important;
}
.no-print,
.no-print *,
.tab-row-container,
.tab-row-widget,
.title-bar-buttons,
#launcher-pane,
#left-pane,
#center-pane > *:not(.split-note-container-widget),
#right-pane,
.title-row .note-icon-widget,
.title-row .button-widget,
.ribbon-container,
.promoted-attributes-widget,
.scroll-padding-widget,
.note-list-widget,
.spacer {
display: none !important;
}
body.mobile #mobile-sidebar-wrapper,
body.mobile .classic-toolbar-widget,
body.mobile .action-button {
display: none !important;
}
body.mobile #detail-container {
max-height: unset;
}
body.mobile .note-title-widget {
padding: 0 !important;
}
body,
#root-widget,
#rest-pane > div.component:first-child,
.note-detail-printable,
.note-detail-editable-text-editor {
height: unset !important;
overflow: auto;
}
.ck.ck-editor__editable_inline {
overflow: hidden !important;
}
.note-title-widget input,
.note-detail-editable-text,
.note-detail-editable-text-editor {
padding: 0 !important;
}
html,
body {
width: unset !important;
height: unset !important;
overflow: visible;
position: unset;
/* https://github.com/zadam/trilium/issues/3202 */
color: black;
}
#root-widget,
#horizontal-main-container,
#rest-pane,
#vertical-main-container,
#center-pane,
.split-note-container-widget,
.note-split:not(.hidden-ext),
body.mobile #mobile-rest-container {
display: block !important;
overflow: auto;
border-radius: 0 !important;
}
#center-pane,
#rest-pane,
.note-split,
body.mobile #detail-container {
width: unset !important;
max-width: unset !important;
}
.component {
contain: none !important;
}
/* Respect page breaks */
.page-break {
page-break-after: always;
break-after: always;
}
.page-break > * {
display: none !important;
}
.relation-map-wrapper {
height: 100vh !important;
}
.table thead th,
.table td,
.table th {
/* Fix center vertical alignment of table cells */
vertical-align: middle;
}
pre {
box-shadow: unset !important;
border: 0.75pt solid gray !important;
border-radius: 2pt !important;
}
th,
span[style] {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
/*
* Text note specific fixes
*/
.ck-widget {
outline: none !important;
}
.ck-placeholder,
.ck-widget__type-around,
.ck-widget__selection-handle {
display: none !important;
}
.ck-widget.table td.ck-editor__nested-editable.ck-editor__nested-editable_focused,
.ck-widget.table td.ck-editor__nested-editable:focus,
.ck-widget.table th.ck-editor__nested-editable.ck-editor__nested-editable_focused,
.ck-widget.table th.ck-editor__nested-editable:focus {
background: unset !important;
outline: unset !important;
}
.include-note .include-note-content {
max-height: unset !important;
overflow: unset !important;
}
/* TODO: This will break once we translate the language */
.ck-content pre[data-language="Auto-detected"]:after {
display: none !important;
}
/*
* Code note specific fixes.
*/
.note-detail-code pre {
border: unset !important;
border-radius: unset !important;
}
/*
* Links
*/
.note-detail-printable a {
text-decoration: none;
}
.note-detail-printable a:not([href^="#root/"]) {
text-decoration: underline;
color: #374a75;
}
.note-detail-printable a::after {
/* Hide the external link trailing arrow */
display: none !important;
}
/*
* TODO list check boxes
*/
.note-detail-printable .todo-list__label * {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
@supports selector(.todo-list__label__description:has(*)) and (height: 1lh) {
.note-detail-printable .todo-list__label__description {
/* The percentage of the line height that the check box occupies */
--box-ratio: 0.75;
/* The size of the gap between the check box and the caption */
--box-text-gap: 0.25em;
--box-size: calc(1lh * var(--box-ratio));
--box-vert-offset: calc((1lh - var(--box-size)) / 2);
display: inline-block;
padding-left: calc(var(--box-size) + var(--box-text-gap));
/* Source: https://pictogrammers.com/library/mdi/icon/checkbox-blank-outline/ */
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5C3.89%2c3 3%2c3.89 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5C21%2c3.89 20.1%2c3 19%2c3M19%2c5V19H5V5H19Z' /%3e%3c/svg%3e");
background-position: 0 var(--box-vert-offset);
background-size: var(--box-size);
background-repeat: no-repeat;
}
.note-detail-printable .todo-list__label:has(input[type="checkbox"]:checked) .todo-list__label__description {
/* Source: https://pictogrammers.com/library/mdi/icon/checkbox-outline/ */
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5A2%2c2 0 0%2c0 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5A2%2c2 0 0%2c0 19%2c3M19%2c5V19H5V5H19M10%2c17L6%2c13L7.41%2c11.58L10%2c14.17L16.59%2c7.58L18%2c9' /%3e%3c/svg%3e");
}
.note-detail-printable .todo-list__label input[type="checkbox"] {
display: none !important;
}
}
/*
* Blockquotes
*/
.note-detail-printable blockquote {
box-shadow: unset;
}
/*
* Figures
*/
.note-detail-printable figcaption {
--accented-background-color: transparent;
font-style: italic;
}
/*
* Footnotes
*/
.note-detail-printable .footnote-reference a,
.footnote-back-link a {
text-decoration: none;
}
/* Make the "^" link cover the whole area of the footnote item */
.footnote-section {
clear: both;
}
.note-detail-printable li.footnote-item {
position: relative;
width: fit-content;
}
.note-detail-printable .footnote-back-link,
.note-detail-printable .footnote-back-link *,
.note-detail-printable .footnote-back-link a {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.note-detail-printable .footnote-back-link a {
color: transparent;
}
.note-detail-printable .footnote-content {
display: inline-block;
width: unset;
}
/*
* Widows and orphans
*/
p,
blockquote {
widows: 4;
orphans: 4;
}
pre > code {
widows: 6;
orphans: 6;
overflow: auto;
white-space: pre-wrap !important;
}
h1,
h2,
h3,
h4,
h5,
h6 {
page-break-after: avoid;
break-after: avoid;
}

View File

@@ -62,7 +62,7 @@
.note-detail-relation-map .endpoint { .note-detail-relation-map .endpoint {
position: absolute; position: absolute;
bottom: 37%; bottom: 37%;
inset-inline-end: 5px; right: 5px;
width: 1em; width: 1em;
height: 1em; height: 1em;
background-color: #333; background-color: #333;

View File

@@ -28,28 +28,6 @@
--ck-mention-list-max-height: 500px; --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 { .table {
--bs-table-bg: transparent !important; --bs-table-bg: transparent !important;
} }
@@ -161,8 +139,7 @@ textarea,
color: var(--muted-text-color); color: var(--muted-text-color);
} }
.form-group.disabled, .form-group.disabled {
.form-checkbox.disabled {
opacity: 0.5; opacity: 0.5;
pointer-events: none; pointer-events: none;
} }
@@ -174,12 +151,7 @@ textarea,
/* Add a gap between consecutive radios / check boxes */ /* Add a gap between consecutive radios / check boxes */
label.tn-radio + label.tn-radio, label.tn-radio + label.tn-radio,
label.tn-checkbox + label.tn-checkbox { label.tn-checkbox + label.tn-checkbox {
margin-inline-start: 12px; margin-left: 12px;
}
label.tn-radio input[type="radio"],
label.tn-checkbox input[type="checkbox"] {
margin-inline-end: .5em;
} }
#left-pane input, #left-pane input,
@@ -226,7 +198,7 @@ samp {
.badge { .badge {
--bs-badge-color: var(--muted-text-color); --bs-badge-color: var(--muted-text-color);
margin-inline-start: 8px; margin-left: 8px;
background: var(--accented-background-color); background: var(--accented-background-color);
} }
@@ -252,6 +224,10 @@ button.close:hover {
color: var(--main-text-color) !important; color: var(--main-text-color) !important;
} }
.note-title[readonly] {
background: inherit;
}
.tdialog { .tdialog {
display: none; display: none;
} }
@@ -290,11 +266,6 @@ button.close:hover {
pointer-events: none; pointer-events: none;
} }
.icon-action.btn {
padding: 0 8px;
min-width: unset !important;
}
.ui-widget-content a:not(.ui-tabs-anchor) { .ui-widget-content a:not(.ui-tabs-anchor) {
color: #337ab7 !important; color: #337ab7 !important;
} }
@@ -338,8 +309,8 @@ button kbd {
} }
.ui-menu kbd { .ui-menu kbd {
margin-inline-start: 30px; margin-left: 30px;
float: inline-end; float: right;
} }
.suppressed { .suppressed {
@@ -360,30 +331,31 @@ button kbd {
} }
.dropdown-menu, .dropdown-menu,
.tabulator-popup-container, .tabulator-popup-container {
:root .excalidraw .popover {
color: var(--menu-text-color) !important; color: var(--menu-text-color) !important;
font-size: inherit; font-size: inherit;
background: var(--menu-background-color) !important; background-color: var(--menu-background-color) !important;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
--bs-dropdown-zindex: 999; --bs-dropdown-zindex: 999;
--bs-dropdown-link-active-bg: var(--active-item-background-color) !important; --bs-dropdown-link-active-bg: var(--active-item-background-color) !important;
} }
.dropdown-menu .dropdown-divider {
break-before: avoid;
break-after: avoid;
}
body.desktop .dropdown-menu, body.desktop .dropdown-menu,
body.desktop .tabulator-popup-container, body.desktop .tabulator-popup-container {
:root .excalidraw .dropdown-menu .dropdown-menu-container,
:root .excalidraw .popover {
border: 1px solid var(--dropdown-border-color); border: 1px solid var(--dropdown-border-color);
column-rule: 1px solid var(--dropdown-border-color);
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity)); box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
animation: dropdown-menu-opening 100ms ease-in; animation: dropdown-menu-opening 100ms ease-in;
} }
@supports (animation-fill-mode: forwards) { @supports (animation-fill-mode: forwards) {
/* Delay the opening of submenus */ /* Delay the opening of submenus */
body.desktop:not(.motion-disabled) .dropdown-submenu .dropdown-menu { body.desktop .dropdown-submenu .dropdown-menu {
opacity: 0; opacity: 0;
animation-fill-mode: forwards; animation-fill-mode: forwards;
animation-delay: var(--submenu-opening-delay); animation-delay: var(--submenu-opening-delay);
@@ -395,7 +367,7 @@ body.desktop .tabulator-popup-container,
} }
.dropend .dropdown-toggle::after { .dropend .dropdown-toggle::after {
margin-inline-start: 0.5em; margin-left: 0.5em;
color: var(--muted-text-color); color: var(--muted-text-color);
} }
@@ -406,7 +378,7 @@ body.desktop .tabulator-popup-container,
.dropdown-menu .disabled .disabled-tooltip { .dropdown-menu .disabled .disabled-tooltip {
pointer-events: all; pointer-events: all;
margin-inline-start: 8px; margin-left: 8px;
font-size: 0.5em; font-size: 0.5em;
color: var(--disabled-tooltip-icon-color); color: var(--disabled-tooltip-icon-color);
cursor: help; cursor: help;
@@ -418,18 +390,17 @@ body.desktop .tabulator-popup-container,
} }
.dropdown-menu a:hover:not(.disabled), .dropdown-menu a:hover:not(.disabled),
.dropdown-item:hover:not(.disabled, .dropdown-container-item), .dropdown-item:hover:not(.disabled, .dropdown-item-container),
.tabulator-menu-item:hover, .tabulator-menu-item:hover {
:root .excalidraw .context-menu .context-menu-item:hover {
color: var(--hover-item-text-color) !important; color: var(--hover-item-text-color) !important;
background-color: var(--hover-item-background-color) !important; background-color: var(--hover-item-background-color) !important;
border-color: var(--hover-item-border-color) !important; border-color: var(--hover-item-border-color) !important;
cursor: pointer; cursor: pointer;
} }
.dropdown-container-item, .dropdown-item-container,
.dropdown-item.dropdown-container-item:hover, .dropdown-item-container:hover,
.dropdown-container-item:active { .dropdown-item-container:active {
background: transparent; background: transparent;
cursor: default; cursor: default;
} }
@@ -444,35 +415,22 @@ body #context-menu-container .dropdown-item > span {
align-items: center; align-items: center;
} }
.dropdown-item span.keyboard-shortcut,
.dropdown-item *:not(.keyboard-shortcut) > kbd {
flex-grow: 1;
text-align: end;
padding-inline-start: 12px;
}
.dropdown-menu kbd { .dropdown-menu kbd {
flex-grow: 1;
text-align: right;
color: var(--muted-text-color); color: var(--muted-text-color);
border: none; border: none;
background-color: transparent; background-color: transparent;
box-shadow: none; box-shadow: none;
padding-bottom: 0; padding-bottom: 0;
padding: 0;
} }
.dropdown-item, .dropdown-item,
.dropdown-header, .dropdown-header {
:root .excalidraw .context-menu .context-menu-item:hover {
color: var(--menu-text-color) !important; color: var(--menu-text-color) !important;
border: 1px solid transparent !important; border: 1px solid transparent !important;
} }
/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns.
* It usually wraps a menu item followed by a separator / header and another menu item. */
.dropdown-no-break {
break-inside: avoid;
}
.dropdown-item.disabled, .dropdown-item.disabled,
.dropdown-item.disabled kbd { .dropdown-item.disabled kbd {
color: #aaa !important; color: #aaa !important;
@@ -480,9 +438,9 @@ body #context-menu-container .dropdown-item > span {
.dropdown-item.active, .dropdown-item.active,
.dropdown-item:focus { .dropdown-item:focus {
color: var(--active-item-text-color); color: var(--active-item-text-color) !important;
background-color: var(--active-item-background-color); background-color: var(--active-item-background-color) !important;
border-color: var(--active-item-border-color); border-color: var(--active-item-border-color) !important;
outline: none; outline: none;
} }
@@ -509,7 +467,7 @@ body #context-menu-container .dropdown-item > span {
body .cm-editor .cm-gutters { body .cm-editor .cm-gutters {
background-color: inherit !important; background-color: inherit !important;
border-inline-end: none; border-right: none;
} }
body .cm-editor .cm-placeholder { body .cm-editor .cm-placeholder {
@@ -591,10 +549,6 @@ button.btn-sm {
z-index: 1000; z-index: 1000;
} }
body[dir=rtl] .ck.ck-block-toolbar-button {
transform: translateX(-7px);
}
pre:not(.hljs) { pre:not(.hljs) {
color: var(--main-text-color) !important; color: var(--main-text-color) !important;
white-space: pre-wrap; white-space: pre-wrap;
@@ -613,11 +567,11 @@ pre:not(.hljs) {
pre > button.copy-button { pre > button.copy-button {
position: absolute; position: absolute;
top: var(--copy-button-margin-size); top: var(--copy-button-margin-size);
inset-inline-end: var(--copy-button-margin-size); right: var(--copy-button-margin-size);
} }
:root pre:has(> button.copy-button) { :root pre:has(> button.copy-button) {
padding-inline-end: calc(var(--copy-button-width) + (var(--copy-button-margin-size) * 2)); padding-right: calc(var(--copy-button-width) + (var(--copy-button-margin-size) * 2));
} }
pre > button.copy-button:hover { pre > button.copy-button:hover {
@@ -643,31 +597,31 @@ pre > button.copy-button:active {
.full-text-search-button { .full-text-search-button {
cursor: pointer; cursor: pointer;
font-size: 1.3em; font-size: 1.3em;
padding-inline-start: 5px; padding-left: 5px;
padding-inline-end: 5px; padding-right: 5px;
} }
.input-clearer-button { .input-clearer-button {
cursor: pointer; cursor: pointer;
font-size: 1.3em; font-size: 1.3em;
background: inherit !important; background: inherit !important;
padding-inline-start: 5px; padding-left: 5px;
padding-inline-end: 5px; padding-right: 5px;
} }
.open-external-link-button { .open-external-link-button {
cursor: pointer; cursor: pointer;
font-size: 1.3em; font-size: 1.3em;
padding-inline-start: 5px; padding-left: 5px;
padding-inline-end: 5px; padding-right: 5px;
padding-top: 8px; padding-top: 8px;
} }
.go-to-selected-note-button { .go-to-selected-note-button {
cursor: pointer; cursor: pointer;
font-size: 1.3em; font-size: 1.3em;
padding-inline-start: 4px; padding-left: 4px;
padding-inline-end: 3px; padding-right: 3px;
} }
.go-to-selected-note-button.disabled, .go-to-selected-note-button.disabled,
@@ -680,7 +634,7 @@ pre > button.copy-button:active {
.note-autocomplete-input { .note-autocomplete-input {
/* this is for seamless integration of "input clearer" button */ /* this is for seamless integration of "input clearer" button */
border-inline-end: 0; border-right: 0;
} }
table.promoted-attributes-in-tooltip { table.promoted-attributes-in-tooltip {
@@ -713,10 +667,10 @@ table.promoted-attributes-in-tooltip th {
border-top-color: var(--main-border-color) !important; border-top-color: var(--main-border-color) !important;
} }
.bs-tooltip-left .tooltip-arrow::before { .bs-tooltip-left .tooltip-arrow::before {
border-inline-start-color: var(--main-border-color) !important; border-left-color: var(--main-border-color) !important;
} }
.bs-tooltip-right .tooltip-arrow::before { .bs-tooltip-right .tooltip-arrow::before {
border-inline-end-color: var(--main-border-color) !important; border-right-color: var(--main-border-color) !important;
} }
.bs-tooltip-bottom .tooltip-arrow::after { .bs-tooltip-bottom .tooltip-arrow::after {
@@ -726,17 +680,17 @@ table.promoted-attributes-in-tooltip th {
border-top-color: var(--tooltip-background-color) !important; border-top-color: var(--tooltip-background-color) !important;
} }
.bs-tooltip-left .tooltip-arrow::after { .bs-tooltip-left .tooltip-arrow::after {
border-inline-start-color: var(--tooltip-background-color) !important; border-left-color: var(--tooltip-background-color) !important;
} }
.bs-tooltip-right .tooltip-arrow::after { .bs-tooltip-right .tooltip-arrow::after {
border-inline-end-color: var(--tooltip-background-color) !important; border-right-color: var(--tooltip-background-color) !important;
} }
.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::before,
.bs-tooltip-left .tooltip-arrow::before { .bs-tooltip-left .tooltip-arrow::before {
inset-inline-start: -1px; left: -1px;
border-width: 0.4rem 0 0.4rem 0.4rem; border-width: 0.4rem 0 0.4rem 0.4rem;
border-inline-start-color: var(--main-border-color) !important; border-left-color: var(--main-border-color) !important;
} }
.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::before,
@@ -748,9 +702,9 @@ table.promoted-attributes-in-tooltip th {
.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::before,
.bs-tooltip-right .tooltip-arrow::before { .bs-tooltip-right .tooltip-arrow::before {
inset-inline-end: -1px; right: -1px;
border-width: 0.4rem 0.4rem 0.4rem 0; border-width: 0.4rem 0.4rem 0.4rem 0;
border-inline-end-color: var(--main-border-color) !important; border-right-color: var(--main-border-color) !important;
} }
.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::before,
@@ -762,9 +716,9 @@ table.promoted-attributes-in-tooltip th {
.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::after, .bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::after,
.bs-tooltip-left .tooltip-arrow::after { .bs-tooltip-left .tooltip-arrow::after {
inset-inline-start: -1px; left: -1px;
border-width: 0.4rem 0 0.4rem 0.4rem; border-width: 0.4rem 0 0.4rem 0.4rem;
border-inline-start-color: var(--tooltip-background-color) !important; border-left-color: var(--tooltip-background-color) !important;
} }
.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::after, .bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::after,
@@ -776,9 +730,9 @@ table.promoted-attributes-in-tooltip th {
.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::after, .bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::after,
.bs-tooltip-right .tooltip-arrow::after { .bs-tooltip-right .tooltip-arrow::after {
inset-inline-end: -1px; right: -1px;
border-width: 0.4rem 0.4rem 0.4rem 0; border-width: 0.4rem 0.4rem 0.4rem 0;
border-inline-end-color: var(--tooltip-background-color) !important; border-right-color: var(--tooltip-background-color) !important;
} }
.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::after, .bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::after,
@@ -797,7 +751,7 @@ table.promoted-attributes-in-tooltip th {
background-color: var(--tooltip-background-color) !important; background-color: var(--tooltip-background-color) !important;
border: 1px solid var(--main-border-color); border: 1px solid var(--main-border-color);
border-radius: 5px; border-radius: 5px;
text-align: start; text-align: left;
color: var(--main-text-color) !important; color: var(--main-text-color) !important;
max-width: 500px; max-width: 500px;
box-shadow: 10px 10px 93px -25px #aaaaaa; box-shadow: 10px 10px 93px -25px #aaaaaa;
@@ -830,7 +784,7 @@ table.promoted-attributes-in-tooltip th {
.note-tooltip-content .open-popup-button { .note-tooltip-content .open-popup-button {
position: absolute; position: absolute;
inset-inline-end: 15px; right: 15px;
bottom: 8px; bottom: 8px;
font-size: 1.2em; font-size: 1.2em;
color: inherit; color: inherit;
@@ -850,7 +804,7 @@ table.promoted-attributes-in-tooltip th {
} }
.tooltip-inner figure.image-style-side { .tooltip-inner figure.image-style-side {
float: inline-end; float: right;
} }
.tooltip.show { .tooltip.show {
@@ -886,34 +840,10 @@ table.promoted-attributes-in-tooltip th {
.aa-dropdown-menu .aa-suggestion { .aa-dropdown-menu .aa-suggestion {
cursor: pointer; cursor: pointer;
padding: 6px 16px; padding: 5px;
margin: 0; 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-inline-start: 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 { .aa-dropdown-menu .aa-suggestion p {
padding: 0; padding: 0;
margin: 0; margin: 0;
@@ -925,7 +855,7 @@ table.promoted-attributes-in-tooltip th {
} }
.help-button { .help-button {
float: inline-end; float: right;
background: none; background: none;
font-weight: 900; font-weight: 900;
color: orange; color: orange;
@@ -1001,11 +931,6 @@ div[data-notify="container"] {
font-family: var(--monospace-font-family); font-family: var(--monospace-font-family);
} }
svg.ck-icon .note-icon {
color: var(--main-text-color);
font-size: 20px;
}
.ck-content { .ck-content {
--ck-content-font-family: var(--detail-font-family); --ck-content-font-family: var(--detail-font-family);
--ck-content-font-size: 1.1em; --ck-content-font-size: 1.1em;
@@ -1013,7 +938,7 @@ svg.ck-icon .note-icon {
--ck-content-line-height: var(--bs-body-line-height); --ck-content-line-height: var(--bs-body-line-height);
} }
:root .ck-content .table table:not(.layout-table) th { .ck-content .table table th {
background-color: var(--accented-background-color); background-color: var(--accented-background-color);
} }
@@ -1042,7 +967,7 @@ svg.ck-icon .note-icon {
counter-increment: footnote-counter; counter-increment: footnote-counter;
display: flex; display: flex;
list-style: none; list-style: none;
margin-inline-start: 0.5em; margin-left: 0.5em;
} }
.ck-content .footnote-item > * { .ck-content .footnote-item > * {
@@ -1050,13 +975,13 @@ svg.ck-icon .note-icon {
} }
.ck-content .footnote-back-link { .ck-content .footnote-back-link {
margin-inline-end: 0.1em; margin-right: 0.1em;
position: relative; position: relative;
top: -0.2em; top: -0.2em;
} }
.ck-content .footnotes .footnote-back-link > sup { .ck-content .footnotes .footnote-back-link > sup {
margin-inline-end: 0; margin-right: 0;
} }
.ck-content .footnote-item:before { .ck-content .footnote-item:before {
@@ -1064,8 +989,8 @@ svg.ck-icon .note-icon {
display: inline-block; display: inline-block;
min-width: fit-content; min-width: fit-content;
position: relative; position: relative;
inset-inline-end: 0.2em; right: 0.2em;
text-align: end; text-align: right;
} }
.ck-content .footnote-content { .ck-content .footnote-content {
@@ -1081,11 +1006,11 @@ svg.ck-icon .note-icon {
} }
#options-dialog input[type="number"] { #options-dialog input[type="number"] {
text-align: end; text-align: right;
} }
.help-cards ul { .help-cards ul {
padding-inline-start: 20px; padding-left: 20px;
} }
.help-cards kbd { .help-cards kbd {
@@ -1147,27 +1072,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
.toast-body { .toast-body {
white-space: preserve-breaks; white-space: preserve-breaks;
overflow: hidden;
}
.toast.no-title {
display: flex;
flex-direction: row;
}
.toast.no-title .toast-icon {
display: flex;
align-items: center;
padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);
}
.toast.no-title .toast-body {
padding-inline-start: 0;
padding-inline-end: 0;
}
.toast.no-title .toast-header {
background-color: unset !important;
} }
.ck-mentions .ck-button { .ck-mentions .ck-button {
@@ -1276,15 +1180,11 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
cursor: row-resize; cursor: row-resize;
} }
.hidden-ext.note-split + .gutter {
display: none;
}
#context-menu-cover.show { #context-menu-cover.show {
position: fixed; position: fixed;
top: 0; top: 0;
inset-inline-start: 0; left: 0;
inset-inline-end: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 1000; z-index: 1000;
background: rgba(0, 0, 0, 0.1); background: rgba(0, 0, 0, 0.1);
@@ -1297,8 +1197,8 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
body.mobile #context-menu-container.mobile-bottom-menu { body.mobile #context-menu-container.mobile-bottom-menu {
position: fixed !important; position: fixed !important;
inset-inline-start: 0 !important; left: 0 !important;
inset-inline-end: 0 !important; right: 0 !important;
bottom: 0 !important; bottom: 0 !important;
top: unset !important; top: unset !important;
max-height: 70vh; max-height: 70vh;
@@ -1348,7 +1248,7 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu {
.dropdown-submenu > .dropdown-menu { .dropdown-submenu > .dropdown-menu {
top: 0; top: 0;
inset-inline-start: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */ left: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */
margin-top: -10px; margin-top: -10px;
min-width: 15rem; min-width: 15rem;
/* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */ /* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */
@@ -1357,7 +1257,7 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu {
} }
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
inset-inline-start: calc(-100% + 10px); left: calc(-100% + 10px);
} }
.right-dropdown-widget { .right-dropdown-widget {
@@ -1423,7 +1323,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
.ck.ck-slash-command-button__text-part, .ck.ck-slash-command-button__text-part,
.ck.ck-template-form__text-part { .ck.ck-template-form__text-part {
margin-inline-start: 0.5em; margin-left: 0.5em;
line-height: 1.2em !important; line-height: 1.2em !important;
} }
@@ -1454,8 +1354,8 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
} }
.area-expander-text { .area-expander-text {
padding-inline-start: 20px; padding-left: 20px;
padding-inline-end: 20px; padding-right: 20px;
white-space: nowrap; white-space: nowrap;
} }
@@ -1501,7 +1401,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
cursor: pointer; cursor: pointer;
border: none; border: none;
color: var(--launcher-pane-text-color); color: var(--launcher-pane-text-color);
background-color: transparent; background-color: var(--launcher-pane-background-color);
flex-shrink: 0; flex-shrink: 0;
} }
@@ -1542,16 +1442,16 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
position: fixed !important; position: fixed !important;
bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size)) !important; bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size)) !important;
top: unset !important; top: unset !important;
inset-inline-start: 0 !important; left: 0 !important;
inset-inline-end: 0 !important; right: 0 !important;
transform: unset !important; transform: unset !important;
} }
#mobile-sidebar-container { #mobile-sidebar-container {
position: fixed; position: fixed;
top: 0; top: 0;
inset-inline-start: 0; left: 0;
inset-inline-end: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 1000; z-index: 1000;
transition: background-color 250ms ease-in-out; transition: background-color 250ms ease-in-out;
@@ -1566,7 +1466,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
#mobile-sidebar-wrapper { #mobile-sidebar-wrapper {
position: absolute; position: absolute;
top: 0; top: 0;
inset-inline-start: 0; left: 0;
bottom: 0; bottom: 0;
width: 85vw; width: 85vw;
padding-top: env(safe-area-inset-top); padding-top: env(safe-area-inset-top);
@@ -1598,8 +1498,8 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
body.mobile .modal-dialog { body.mobile .modal-dialog {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
inset-inline-start: 0; left: 0;
inset-inline-end: 0; right: 0;
margin: 0 !important; margin: 0 !important;
max-height: 85vh; max-height: 85vh;
display: flex; display: flex;
@@ -1757,8 +1657,8 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
display: flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
flex-direction: column; flex-direction: column;
margin-inline-start: 10px; margin-left: 10px;
margin-inline-end: 5px; margin-right: 5px;
} }
#right-pane .card-header { #right-pane .card-header {
@@ -1798,7 +1698,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
} }
#right-pane .card-body ul { #right-pane .card-body ul {
padding-inline-start: 25px; padding-left: 25px;
margin-bottom: 5px; margin-bottom: 5px;
} }
@@ -1809,8 +1709,9 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
} }
.note-split { .note-split {
margin-inline-start: auto; flex-basis: 0; /* so that each split has same width */
margin-inline-end: auto; margin-left: auto;
margin-right: auto;
} }
.note-split.full-content-width { .note-split.full-content-width {
@@ -1829,7 +1730,7 @@ button.close:hover {
.reference-link .bx { .reference-link .bx {
position: relative; position: relative;
top: 1px; top: 1px;
margin-inline-end: 3px; margin-right: 3px;
} }
.options-section:first-of-type h4 { .options-section:first-of-type h4 {
@@ -1867,42 +1768,25 @@ textarea {
.attachment-help-button { .attachment-help-button {
display: inline-block; display: inline-block;
margin-inline-start: 10px; margin-left: 10px;
vertical-align: middle; vertical-align: middle;
font-size: 1em; font-size: 1em;
} }
.jump-to-note-dialog .modal-dialog {
max-width: 900px;
width: 90%;
}
.jump-to-note-dialog .modal-header { .jump-to-note-dialog .modal-header {
align-items: center; align-items: center;
} }
.jump-to-note-dialog .modal-body { .jump-to-note-dialog .modal-body {
padding: 0; padding: 0;
min-height: 200px;
} }
.jump-to-note-results .aa-dropdown-menu { .jump-to-note-results .aa-dropdown-menu {
max-height: calc(80vh - 200px); max-height: 40vh;
width: 100%;
max-width: none;
overflow-y: auto;
overflow-x: hidden;
text-overflow: ellipsis;
box-shadow: none;
}
.jump-to-note-results {
width: 100%;
} }
.jump-to-note-results .aa-suggestions { .jump-to-note-results .aa-suggestions {
padding: 0; padding: 1rem;
width: 100%;
} }
/* Command palette styling */ /* Command palette styling */
@@ -1920,24 +1804,8 @@ textarea {
.jump-to-note-dialog .aa-cursor .command-suggestion, .jump-to-note-dialog .aa-cursor .command-suggestion,
.jump-to-note-dialog .aa-suggestion:hover .command-suggestion { .jump-to-note-dialog .aa-suggestion:hover .command-suggestion {
background-color: transparent; border-left-color: var(--link-color);
} background-color: var(--hover-background-color);
.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;
} }
.jump-to-note-dialog .command-icon { .jump-to-note-dialog .command-icon {
@@ -1975,7 +1843,7 @@ textarea {
} }
body.electron.platform-darwin:not(.native-titlebar) .tab-row-container { body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
padding-inline-start: 1em; padding-left: 1em;
} }
#tab-row-left-spacer { #tab-row-left-spacer {
@@ -1983,12 +1851,8 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
-webkit-app-region: drag; -webkit-app-region: drag;
} }
body.electron.platform-darwin:not(.native-titlebar) #tab-row-left-spacer {
width: 80px;
}
.tab-row-widget { .tab-row-widget {
padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw)); padding-right: calc(100vw - env(titlebar-area-width, 100vw));
} }
.tab-row-container .toggle-button { .tab-row-container .toggle-button {
@@ -2038,20 +1902,16 @@ body.zen .ribbon-container:not(:has(.classic-toolbar-widget.visible)),
body.zen .ribbon-container:has(.classic-toolbar-widget.visible) .ribbon-top-row, body.zen .ribbon-container:has(.classic-toolbar-widget.visible) .ribbon-top-row,
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget.visible)), body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget.visible)),
body.zen .note-icon-widget, body.zen .note-icon-widget,
body.zen .title-row .icon-action, body.zen .title-row .button-widget,
body.zen .floating-buttons-children > *:not(.bx-edit-alt), body.zen .floating-buttons-children > *:not(.bx-edit-alt),
body.zen .action-button { body.zen .action-button {
display: none !important; display: none !important;
} }
body.zen .split-note-container-widget > .gutter {
display: unset !important;
}
body.zen #launcher-pane { body.zen #launcher-pane {
position: absolute !important; position: absolute !important;
top: 0 !important; top: 0 !important;
inset-inline-end: 0 !important; right: 0 !important;
width: 64px !important; width: 64px !important;
height: 64px !important; height: 64px !important;
background: transparent !important; background: transparent !important;
@@ -2062,8 +1922,8 @@ body.zen .title-row {
display: block !important; display: block !important;
height: unset !important; height: unset !important;
-webkit-app-region: drag; -webkit-app-region: drag;
padding-inline-start: env(titlebar-area-x); padding-left: env(titlebar-area-x);
padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw) + 2.5em); padding-right: calc(100vw - env(titlebar-area-width, 100vw) + 2.5em);
} }
body.zen .floating-buttons { body.zen .floating-buttons {
@@ -2071,7 +1931,7 @@ body.zen .floating-buttons {
} }
body.zen .floating-buttons-children { body.zen .floating-buttons-children {
inset-inline-end: 0; right: 0;
} }
body.zen .floating-buttons-children .button-widget { body.zen .floating-buttons-children .button-widget {
@@ -2230,7 +2090,7 @@ footer.webview-footer button {
.chat-input { .chat-input {
width: 100%; width: 100%;
resize: none; resize: none;
padding-inline-end: 40px; padding-right: 40px;
} }
.chat-buttons { .chat-buttons {
@@ -2286,13 +2146,14 @@ footer.webview-footer button {
.admonition { .admonition {
--accent-color: var(--card-border-color); --accent-color: var(--card-border-color);
background: color-mix(in srgb, var(--accent-color) 15%, transparent);
border: 1px solid var(--accent-color); border: 1px solid var(--accent-color);
box-shadow: var(--card-box-shadow);
background: var(--card-background-color);
border-radius: 0.5em; border-radius: 0.5em;
padding: 1em; padding: 1em;
margin: 1.25em 0; margin: 1.25em 0;
position: relative; position: relative;
padding-inline-start: 2.5em; padding-left: 2.5em;
overflow: hidden; overflow: hidden;
} }
@@ -2305,7 +2166,7 @@ footer.webview-footer button {
font-family: boxicons !important; font-family: boxicons !important;
position: absolute; position: absolute;
top: 1em; top: 1em;
inset-inline-start: 1em; left: 1em;
} }
.admonition.note { --accent-color: var(--admonition-note-accent-color); } .admonition.note { --accent-color: var(--admonition-note-accent-color); }
@@ -2331,18 +2192,18 @@ footer.webview-footer button {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 0.9em; font-size: 0.9em;
margin-inline-end: 15px; margin-right: 15px;
cursor: pointer; cursor: pointer;
} }
.chat-option input[type="checkbox"] { .chat-option input[type="checkbox"] {
margin-inline-end: 5px; margin-right: 5px;
} }
/* Style for thinking process in chat responses */ /* Style for thinking process in chat responses */
.thinking-process { .thinking-process {
background-color: rgba(0, 0, 0, 0.05); background-color: rgba(0, 0, 0, 0.05);
border-inline-start: 3px solid var(--main-text-color); border-left: 3px solid var(--main-text-color);
padding: 10px; padding: 10px;
margin: 10px 0; margin: 10px 0;
border-radius: 4px; border-radius: 4px;
@@ -2350,23 +2211,23 @@ footer.webview-footer button {
.thinking-step { .thinking-step {
margin-bottom: 8px; margin-bottom: 8px;
padding-inline-start: 10px; padding-left: 10px;
} }
.thinking-step.observation { .thinking-step.observation {
border-inline-start: 2px solid #69c7ff; border-left: 2px solid #69c7ff;
} }
.thinking-step.hypothesis { .thinking-step.hypothesis {
border-inline-start: 2px solid #9839f7; border-left: 2px solid #9839f7;
} }
.thinking-step.evidence { .thinking-step.evidence {
border-inline-start: 2px solid #40c025; border-left: 2px solid #40c025;
} }
.thinking-step.conclusion { .thinking-step.conclusion {
border-inline-start: 2px solid #e2aa03; border-left: 2px solid #e2aa03;
font-weight: bold; font-weight: bold;
} }
@@ -2381,17 +2242,17 @@ footer.webview-footer button {
.content-floating-buttons.top-left { .content-floating-buttons.top-left {
top: 10px; top: 10px;
inset-inline-start: 10px; left: 10px;
} }
.content-floating-buttons.bottom-left { .content-floating-buttons.bottom-left {
bottom: 10px; bottom: 10px;
inset-inline-start: 10px; left: 10px;
} }
.content-floating-buttons.bottom-right { .content-floating-buttons.bottom-right {
bottom: 10px; bottom: 10px;
inset-inline-end: 10px; right: 10px;
} }
.content-floating-buttons button.bx { .content-floating-buttons button.bx {
@@ -2399,41 +2260,16 @@ footer.webview-footer button {
padding: 1px 10px 1px 10px; 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 */ /* Customized icons */
.bx-tn-toc::before { .bx-tn-toc::before {
content: "\ec24"; content: "\ec24";
transform: rotate(180deg); 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;
}
iframe.print-iframe {
position: absolute;
top: 0;
left: -600px;
right: -600px;
bottom: 0;
width: 0;
height: 0;
}
.excalidraw.theme--dark canvas {
--theme-filter: invert(100%) hue-rotate(180deg);
}

View File

@@ -67,13 +67,13 @@
} }
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left { .tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
margin-inline-start: var(--cell-editing-border-width); margin-left: var(--cell-editing-border-width);
} }
.tabulator div.tabulator-header .tabulator-col, .tabulator div.tabulator-header .tabulator-col,
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left { .tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
background: var(--col-header-background-color); background: var(--col-header-background-color);
border-inline-end: var(--col-header-separator-border); border-right: var(--col-header-separator-border);
} }
/* Table body */ /* Table body */
@@ -90,8 +90,8 @@
} }
.tabulator-row .tabulator-cell input { .tabulator-row .tabulator-cell input {
padding-inline-start: var(--cell-horiz-padding-size) !important; padding-left: var(--cell-horiz-padding-size) !important;
padding-inline-end: var(--cell-horiz-padding-size) !important; padding-right: var(--cell-horiz-padding-size) !important;
} }
.tabulator-row { .tabulator-row {
@@ -117,12 +117,12 @@
/* Cell */ /* Cell */
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left { .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
margin-inline-end: var(--cell-editing-border-width); margin-right: var(--cell-editing-border-width);
} }
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left, .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left,
.tabulator-row .tabulator-cell { .tabulator-row .tabulator-cell {
border-inline-end-color: transparent; border-right-color: transparent;
} }
.tabulator-row .tabulator-cell:not(.tabulator-editable) { .tabulator-row .tabulator-cell:not(.tabulator-editable) {
@@ -156,14 +156,14 @@
/* Align items without children/expander to the ones with. */ /* Align items without children/expander to the ones with. */
.tabulator-cell[tabulator-field="title"] > span:first-child, /* 1st level */ .tabulator-cell[tabulator-field="title"] > span:first-child, /* 1st level */
.tabulator-cell[tabulator-field="title"] > div:first-child + span { /* sub-level */ .tabulator-cell[tabulator-field="title"] > div:first-child + span { /* sub-level */
padding-inline-start: 21px; padding-left: 21px;
} }
/* Checkbox cells */ /* Checkbox cells */
.tabulator .tabulator-cell:has(svg), .tabulator .tabulator-cell:has(svg),
.tabulator .tabulator-cell:has(input[type="checkbox"]) { .tabulator .tabulator-cell:has(input[type="checkbox"]) {
padding-inline-start: 8px; padding-left: 8px;
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;

View File

@@ -1,16 +1,16 @@
:root { :root {
--theme-style: dark; --theme-style: dark;
--main-font-family: Montserrat, sans-serif; --main-font-family: Montserrat;
--main-font-size: normal; --main-font-size: normal;
--tree-font-family: Montserrat, sans-serif; --tree-font-family: Montserrat;
--tree-font-size: normal; --tree-font-size: normal;
--detail-font-family: Montserrat, sans-serif; --detail-font-family: Montserrat;
--detail-font-size: normal; --detail-font-size: normal;
--monospace-font-family: JetBrainsLight, monospace; --monospace-font-family: JetBrainsLight;
--monospace-font-size: normal; --monospace-font-size: normal;
--main-background-color: #333; --main-background-color: #333;
@@ -82,17 +82,6 @@ body ::-webkit-calendar-picker-indicator {
filter: invert(1); filter: invert(1);
} }
#left-pane .fancytree-node.tinted {
--custom-color: var(--dark-theme-custom-color);
}
:root .reference-link,
:root .reference-link:hover,
.ck-content a.reference-link > span,
.board-note {
color: var(--dark-theme-custom-color, inherit);
}
.excalidraw.theme--dark { .excalidraw.theme--dark {
--theme-filter: invert(80%) hue-rotate(180deg) !important; --theme-filter: invert(80%) hue-rotate(180deg) !important;
} }
@@ -108,4 +97,3 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.ck-content pre { .ck-content pre {
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6) !important; box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6) !important;
} }

View File

@@ -5,16 +5,16 @@ html {
/* either light or dark, colored theme with darker tones are also dark, used e.g. for note map node colors */ /* either light or dark, colored theme with darker tones are also dark, used e.g. for note map node colors */
--theme-style: light; --theme-style: light;
--main-font-family: Montserrat, sans-serif; --main-font-family: Montserrat;
--main-font-size: normal; --main-font-size: normal;
--tree-font-family: Montserrat, sans-serif; --tree-font-family: Montserrat;
--tree-font-size: normal; --tree-font-size: normal;
--detail-font-family: Montserrat, sans-serif; --detail-font-family: Montserrat;
--detail-font-size: normal; --detail-font-size: normal;
--monospace-font-family: JetBrainsLight, monospace; --monospace-font-family: JetBrainsLight;
--monospace-font-size: normal; --monospace-font-size: normal;
--main-background-color: white; --main-background-color: white;
@@ -81,14 +81,3 @@ html {
--mermaid-theme: default; --mermaid-theme: default;
--native-titlebar-background: #ffffff00; --native-titlebar-background: #ffffff00;
} }
#left-pane .fancytree-node.tinted {
--custom-color: var(--light-theme-custom-color);
}
:root .reference-link,
:root .reference-link:hover,
.ck-content a.reference-link > span,
.board-note {
color: var(--light-theme-custom-color, inherit);
}

View File

@@ -13,13 +13,12 @@
--theme-style: dark; --theme-style: dark;
--native-titlebar-background: #00000000; --native-titlebar-background: #00000000;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: #272727; --main-background-color: #272727;
--main-text-color: #ccc; --main-text-color: #ccc;
--main-border-color: #454545; --main-border-color: #454545;
--subtle-border-color: #313131; --subtle-border-color: #313131;
--dropdown-border-color: #404040; --dropdown-border-color: #292929;
--dropdown-shadow-opacity: 0.6; --dropdown-shadow-opacity: 0.6;
--dropdown-item-icon-destructive-color: #de6e5b; --dropdown-item-icon-destructive-color: #de6e5b;
--disabled-tooltip-icon-color: #7fd2ef; --disabled-tooltip-icon-color: #7fd2ef;
@@ -90,7 +89,6 @@
--menu-text-color: #e3e3e3; --menu-text-color: #e3e3e3;
--menu-background-color: #222222d9; --menu-background-color: #222222d9;
--menu-background-color-no-backdrop: #1b1b1b;
--menu-item-icon-color: #8c8c8c; --menu-item-icon-color: #8c8c8c;
--menu-item-disabled-opacity: 0.5; --menu-item-disabled-opacity: 0.5;
--menu-item-keyboard-shortcut-color: #ffffff8f; --menu-item-keyboard-shortcut-color: #ffffff8f;
@@ -122,8 +120,6 @@
--quick-search-focus-border: #80808095; --quick-search-focus-border: #80808095;
--quick-search-focus-background: #ffffff1f; --quick-search-focus-background: #ffffff1f;
--quick-search-focus-color: white; --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-collapsed-border-color: #0009;
--left-pane-background-color: #1f1f1f; --left-pane-background-color: #1f1f1f;
@@ -148,27 +144,18 @@
--launcher-pane-vert-button-hover-background: #ffffff1c; --launcher-pane-vert-button-hover-background: #ffffff1c;
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2); --launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-vert-background-color-bgfx: #00000026; /* When background effects enabled */
--launcher-pane-horiz-border-color: rgb(22, 22, 22); --launcher-pane-horiz-border-color: rgb(22, 22, 22);
--launcher-pane-horiz-background-color: #282828; --launcher-pane-horiz-background-color: #282828;
--launcher-pane-horiz-text-color: #b8b8b8; --launcher-pane-horiz-text-color: #909090;
--launcher-pane-horiz-button-hover-color: #ffffff; --launcher-pane-horiz-button-hover-color: #ffffff;
--launcher-pane-horiz-button-hover-background: #ffffff1c; --launcher-pane-horiz-button-hover-background: #ffffff1c;
--launcher-pane-horiz-button-hover-shadow: unset; --launcher-pane-horiz-button-hover-shadow: unset;
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-horiz-background-color-bgfx: #ffffff17; /* When background effects enabled */
--launcher-pane-horiz-border-color-bgfx: #00000080; /* When background effects enabled */
--global-menu-update-available-badge-background-color: #7dbe61;
--global-menu-update-available-badge-color: black;
--protected-session-active-icon-color: #8edd8e; --protected-session-active-icon-color: #8edd8e;
--sync-status-error-pulse-color: #f47871; --sync-status-error-pulse-color: #f47871;
--center-pane-vert-layout-background-color-bgfx: #0c0c0c69;
--center-pane-horiz-layout-background-color-bgfx: #1e1e1ec7;
--right-pane-heading-color: gray; --right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color); --root-background: var(--left-pane-background-color);
@@ -181,7 +168,6 @@
--active-tab-background-color: #ffffff1c; --active-tab-background-color: #ffffff1c;
--active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-hover-background-color: var(--active-tab-background-color);
--active-tab-icon-color: #a9a9a9;
--active-tab-text-color: #ffffffcd; --active-tab-text-color: #ffffffcd;
--active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2), -1px -1px 3px rgba(0, 0, 0, 0.4); --active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2), -1px -1px 3px rgba(0, 0, 0, 0.4);
--active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, 0.4); --active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, 0.4);
@@ -195,8 +181,8 @@
--badge-background-color: #ffffff1a; --badge-background-color: #ffffff1a;
--badge-text-color: var(--muted-text-color); --badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: #ffffff21; --promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow: none; --promoted-attribute-card-shadow-color: #000000b3;
--floating-button-shadow-color: #00000080; --floating-button-shadow-color: #00000080;
--floating-button-background-color: #494949d2; --floating-button-background-color: #494949d2;
@@ -230,8 +216,8 @@
--card-background-color: #ffffff12; --card-background-color: #ffffff12;
--card-background-hover-color: #3c3c3c; --card-background-hover-color: #3c3c3c;
--card-background-press-color: #464646; --card-background-press-color: #464646;
--card-border-color: transparent; --card-border-color: #222222;
--card-box-shadow: none; --card-box-shadow: 0 0 12px rgba(0, 0, 0, 0.15);
--calendar-color: var(--menu-text-color); --calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color); --calendar-weekday-labels-color: var(--muted-text-color);
@@ -271,22 +257,6 @@
* Dark color scheme tweaks * Dark color scheme tweaks
*/ */
#left-pane .fancytree-node.tinted {
--custom-color: var(--dark-theme-custom-color);
/* The background color of the active item in the note tree.
* The --custom-color-hue variable contains the hue of the user-selected note color.
* This value is unset for gray tones. */
--custom-bg-color: hsl(var(--custom-color-hue), 20%, 33%, 0.4);
}
:root .reference-link,
:root .reference-link:hover,
.ck-content a.reference-link > span,
.board-note {
color: var(--dark-theme-custom-color, inherit);
}
body ::-webkit-calendar-picker-indicator { body ::-webkit-calendar-picker-indicator {
filter: invert(1); filter: invert(1);
} }
@@ -298,9 +268,3 @@ body ::-webkit-calendar-picker-indicator {
body .todo-list input[type="checkbox"]:not(:checked):before { body .todo-list input[type="checkbox"]:not(:checked):before {
border-color: var(--muted-text-color) !important; border-color: var(--muted-text-color) !important;
} }
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 8.8%, 11.2%);
--modal-border-color: hsl(var(--custom-color-hue), 9.4%, 25.1%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 13.2%, 20.8%);
}

View File

@@ -13,7 +13,6 @@
--theme-style: light; --theme-style: light;
--native-titlebar-background: #ffffff00; --native-titlebar-background: #ffffff00;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: white; --main-background-color: white;
--main-text-color: black; --main-text-color: black;
@@ -84,7 +83,6 @@
--menu-text-color: #272727; --menu-text-color: #272727;
--menu-background-color: #ffffffd9; --menu-background-color: #ffffffd9;
--menu-background-color-no-backdrop: #fdfdfd;
--menu-item-icon-color: #727272; --menu-item-icon-color: #727272;
--menu-item-disabled-opacity: 0.6; --menu-item-disabled-opacity: 0.6;
--menu-item-keyboard-shortcut-color: #666666a8; --menu-item-keyboard-shortcut-color: #666666a8;
@@ -116,18 +114,16 @@
--quick-search-focus-border: #00000029; --quick-search-focus-border: #00000029;
--quick-search-focus-background: #ffffff80; --quick-search-focus-background: #ffffff80;
--quick-search-focus-color: #000; --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-collapsed-border-color: #0000000d;
--left-pane-background-color: #f2f2f2; --left-pane-background-color: #f2f2f2;
--left-pane-text-color: #383838; --left-pane-text-color: #383838;
--left-pane-item-hover-background: rgba(0, 0, 0, 0.032); --left-pane-item-hover-background: #eaeaea;
--left-pane-item-selected-background: white; --left-pane-item-selected-background: white;
--left-pane-item-selected-color: black; --left-pane-item-selected-color: black;
--left-pane-item-selected-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); --left-pane-item-selected-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
--left-pane-item-action-button-background: rgba(0, 0, 0, 0.11); --left-pane-item-action-button-background: #d7d7d7;
--left-pane-item-action-button-color: var(--left-pane-text-color); --left-pane-item-action-button-color: inherit;
--left-pane-item-action-button-hover-background: white; --left-pane-item-action-button-hover-background: white;
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15); --left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
--left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25); --left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
@@ -142,7 +138,6 @@
--launcher-pane-vert-button-hover-background: white; --launcher-pane-vert-button-hover-background: white;
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.075); --launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.075);
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-vert-background-color-bgfx: #00000009; /* When background effects enabled */
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.1); --launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.1);
--launcher-pane-horiz-background-color: #fafafa; --launcher-pane-horiz-background-color: #fafafa;
@@ -150,18 +145,10 @@
--launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background); --launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background);
--launcher-pane-horiz-button-hover-shadow: unset; --launcher-pane-horiz-button-hover-shadow: unset;
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color); --launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
--launcher-pane-horiz-background-color-bgfx: #ffffffb3; /* When background effects enabled */
--launcher-pane-horiz-border-color-bgfx: #00000026; /* When background effects enabled */
--global-menu-update-available-badge-background-color: #4fa450;
--global-menu-update-available-badge-color: white;
--protected-session-active-icon-color: #16b516; --protected-session-active-icon-color: #16b516;
--sync-status-error-pulse-color: #ff5528; --sync-status-error-pulse-color: #ff5528;
--center-pane-vert-layout-background-color-bgfx: #ffffff75;
--center-pane-horiz-layout-background-color-bgfx: #ffffffd6;
--right-pane-heading-color: gray; --right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color); --root-background: var(--left-pane-background-color);
@@ -174,7 +161,6 @@
--active-tab-background-color: white; --active-tab-background-color: white;
--active-tab-hover-background-color: var(--active-tab-background-color); --active-tab-hover-background-color: var(--active-tab-background-color);
--active-tab-icon-color: gray;
--active-tab-text-color: black; --active-tab-text-color: black;
--active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, 0.1), -1px -1px 3px rgba(0, 0, 0, 0.05); --active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, 0.1), -1px -1px 3px rgba(0, 0, 0, 0.05);
--active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, 0.1); --active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, 0.1);
@@ -188,8 +174,8 @@
--badge-background-color: #00000011; --badge-background-color: #00000011;
--badge-text-color: var(--muted-text-color); --badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: #00000014; --promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow: none; --promoted-attribute-card-shadow-color: #00000033;
--floating-button-shadow-color: #00000042; --floating-button-shadow-color: #00000042;
--floating-button-background-color: #eaeaeacc; --floating-button-background-color: #eaeaeacc;
@@ -226,12 +212,12 @@
--code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2); --code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2);
--card-background-color: #0000000d; --card-background-color: var(--accented-background-color);
--card-background-hover-color: #f9f9f9; --card-background-hover-color: #f9f9f9;
--card-background-press-color: #efefef; --card-background-press-color: #efefef;
--card-border-color: transparent; --card-border-color: #eaeaea;
--card-shadow-color: rgba(0, 0, 0, 0.1); --card-shadow-color: rgba(0, 0, 0, 0.1);
--card-box-shadow: none; --card-box-shadow: 0 0 12px var(--card-shadow-color);
--calendar-color: var(--menu-text-color); --calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color); --calendar-weekday-labels-color: var(--muted-text-color);
@@ -264,19 +250,5 @@
--ck-editor-toolbar-button-on-color: black; --ck-editor-toolbar-button-on-color: black;
--ck-editor-toolbar-button-on-shadow: none; --ck-editor-toolbar-button-on-shadow: none;
--ck-editor-toolbar-dropdown-button-open-background: #0000000f; --ck-editor-toolbar-dropdown-button-open-background: #0000000f;
}
#left-pane .fancytree-node.tinted {
--custom-color: var(--light-theme-custom-color);
/* The background color of the active item in the note tree.
* The --custom-color-hue variable contains the hue of the user-selected note color.
* This value is unset for gray tones. */
--custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1);
}
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 56%, 96%);
--modal-border-color: hsl(var(--custom-color-hue), 33%, 41%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);
} }

View File

@@ -4,7 +4,6 @@
@import url(./pages.css); @import url(./pages.css);
@import url(./ribbon.css); @import url(./ribbon.css);
@import url(./notes/text.css); @import url(./notes/text.css);
@import url(./notes/canvas.css);
@import url(./notes/collections/table.css); @import url(./notes/collections/table.css);
@font-face { @font-face {
@@ -27,7 +26,7 @@
--detail-font-family: var(--main-font-family); --detail-font-family: var(--main-font-family);
--detail-font-size: normal; --detail-font-size: normal;
--monospace-font-family: JetBrainsLight, monospace; --monospace-font-family: JetBrainsLight;
--monospace-font-size: normal; --monospace-font-size: normal;
--left-pane-item-selected-shadow-size: 2px; --left-pane-item-selected-shadow-size: 2px;
@@ -82,26 +81,6 @@
/* Theme capabilities */ /* Theme capabilities */
--tab-note-icons: true; --tab-note-icons: true;
/* To ensure that a tree item's custom color remains sufficiently contrasted and readable,
* the color is adjusted based on the current color scheme (light or dark). The lightness
* component of the color represented in the CIELAB color space, will be
* constrained to a certain percentage defined below.
*
* Note: the tree background may vary when background effects are enabled, so it is recommended
* to maintain a higher contrast margin than on the usual note tree solid background. */
/* The maximum perceptual lightness for the custom color in the light theme (%): */
--tree-item-light-theme-max-color-lightness: 60;
/* The minimum perceptual lightness for the custom color in the dark theme (%): */
--tree-item-dark-theme-min-color-lightness: 65;
}
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);
} }
/* /*
@@ -111,13 +90,16 @@ body.backdrop-effects-disabled {
* supported when this class is used. * supported when this class is used.
*/ */
.dropdown-menu:not(.static), .dropdown-menu:not(.static) {
:root .excalidraw .popover {
border-radius: var(--dropdown-border-radius); border-radius: var(--dropdown-border-radius);
padding: var(--padding, var(--menu-padding-size)) !important; padding: var(--menu-padding-size) !important;
font-size: 0.9rem !important; font-size: 0.9rem !important;
} }
.dropdown-menu {
--scrollbar-background-color: var(--menu-background-color);
}
body.mobile .dropdown-menu { body.mobile .dropdown-menu {
backdrop-filter: var(--dropdown-backdrop-filter); backdrop-filter: var(--dropdown-backdrop-filter);
border-radius: var(--dropdown-border-radius); border-radius: var(--dropdown-border-radius);
@@ -130,15 +112,14 @@ body.mobile .dropdown-menu .dropdown-menu {
} }
body.desktop .dropdown-menu::before, body.desktop .dropdown-menu::before,
:root .ck.ck-dropdown__panel::before, :root .ck.ck-dropdown__panel::before {
:root .excalidraw .popover::before {
content: ""; content: "";
backdrop-filter: var(--dropdown-backdrop-filter); backdrop-filter: var(--dropdown-backdrop-filter);
border-radius: var(--dropdown-border-radius); border-radius: var(--dropdown-border-radius);
position: absolute; position: absolute;
top: 0; top: 0;
inset-inline-start: 0; left: 0;
inset-inline-end: 0; right: 0;
bottom: 0; bottom: 0;
z-index: -1; z-index: -1;
} }
@@ -165,32 +146,14 @@ body.desktop .dropdown-submenu .dropdown-menu {
} }
.dropdown-item, .dropdown-item,
body.mobile .dropdown-submenu .dropdown-toggle, body.mobile .dropdown-submenu .dropdown-toggle {
.excalidraw .context-menu .context-menu-item { padding: 2px 2px 2px 8px !important;
--menu-item-start-padding: 8px; padding-inline-end: 16px !important;
--menu-item-end-padding: 22px;
--menu-item-vertical-padding: 2px;
padding-top: var(--menu-item-vertical-padding) !important;
padding-bottom: var(--menu-item-vertical-padding) !important;
padding-inline-start: var(--menu-item-start-padding) !important;
padding-inline-end: var(--menu-item-end-padding) !important;
/* Note: the right padding should also accommodate the submenu arrow. */ /* Note: the right padding should also accommodate the submenu arrow. */
border-radius: 6px; border-radius: 6px;
cursor: default !important; cursor: default !important;
} }
:root .dropdown-item:focus-visible {
outline: 2px solid var(--input-focus-outline-color) !important;
background-color: transparent;
color: unset;
}
:root .dropdown-item:active {
background: unset;
}
body.mobile .dropdown-submenu { body.mobile .dropdown-submenu {
padding: 0 !important; padding: 0 !important;
} }
@@ -227,33 +190,26 @@ html body .dropdown-item[disabled] {
} }
/* Menu item keyboard shortcut */ /* Menu item keyboard shortcut */
.dropdown-item kbd, .dropdown-item kbd {
.excalidraw .context-menu-item__shortcut { margin-left: 16px;
font-family: unset !important; font-family: unset !important;
font-size: unset !important; font-size: unset !important;
color: var(--menu-item-keyboard-shortcut-color) !important; color: var(--menu-item-keyboard-shortcut-color) !important;
padding-top: 0; padding-top: 0;
} }
.dropdown-item span.keyboard-shortcut { .dropdown-divider {
color: var(--menu-item-keyboard-shortcut-color) !important;
margin-inline-start: 16px;
}
.dropdown-divider,
.excalidraw .context-menu hr {
position: relative; position: relative;
border-color: transparent !important; border-color: transparent !important;
overflow: visible; overflow: visible;
} }
.dropdown-divider::after, .dropdown-divider::after {
.excalidraw .context-menu hr::before {
position: absolute; position: absolute;
content: ""; content: "";
top: -1px; top: -1px;
inset-inline-start: calc(0px - var(--menu-padding-size)); left: calc(0px - var(--menu-padding-size));
inset-inline-end: calc(0px - var(--menu-padding-size)); right: calc(0px - var(--menu-padding-size));
border-top: 1px solid var(--menu-item-delimiter-color); border-top: 1px solid var(--menu-item-delimiter-color);
} }
@@ -265,7 +221,7 @@ html body .dropdown-item[disabled] {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
top: 0; top: 0;
inset-inline-end: 0; right: 0;
margin: unset !important; margin: unset !important;
border: unset !important; border: unset !important;
padding: 0 4px; padding: 0 4px;
@@ -274,16 +230,10 @@ html body .dropdown-item[disabled] {
color: var(--menu-item-arrow-color) !important; color: var(--menu-item-arrow-color) !important;
} }
body[dir=rtl] .dropdown-menu:not([data-popper-placement="bottom-start"]) .dropdown-toggle::after {
content: "\ea4d" !important;
}
/* Menu item group heading */ /* Menu item group heading */
/* The heading body */ /* The heading body */
.dropdown-menu h6, .dropdown-menu h6 {
.excalidraw .dropdown-menu-container .dropdown-menu-group-title,
.excalidraw .dropdown-menu-container div[data-testid="canvas-background-label"] {
position: relative; position: relative;
background: transparent; background: transparent;
padding: 1em 8px 14px 8px; padding: 1em 8px 14px 8px;
@@ -294,14 +244,12 @@ body[dir=rtl] .dropdown-menu:not([data-popper-placement="bottom-start"]) .dropdo
} }
/* The delimiter line */ /* The delimiter line */
.dropdown-menu h6::before, .dropdown-menu h6::before {
.excalidraw .dropdown-menu-container .dropdown-menu-group-title::before,
.excalidraw .dropdown-menu-container div[data-testid="canvas-background-label"]::before {
content: ""; content: "";
position: absolute; position: absolute;
bottom: 8px; bottom: 8px;
inset-inline-start: calc(0px - var(--menu-padding-size)); left: calc(0px - var(--menu-padding-size));
inset-inline-end: calc(0px - var(--menu-padding-size)); right: calc(0px - var(--menu-padding-size));
border-top: 1px solid var(--menu-item-delimiter-color); border-top: 1px solid var(--menu-item-delimiter-color);
} }
@@ -331,20 +279,6 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
transform: rotate(270deg); transform: rotate(270deg);
} }
/* Dropdown item button (used in zoom buttons in global menu) */
li.dropdown-item a.dropdown-item-button {
border: unset;
}
li.dropdown-item a.dropdown-item-button.bx {
color: var(--menu-text-color) !important;
}
li.dropdown-item a.dropdown-item-button:focus-visible {
outline: 2px solid var(--input-focus-outline-color) !important;
}
/* /*
* TOASTS * TOASTS
*/ */
@@ -363,49 +297,28 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
--modal-control-button-color: var(--bs-toast-color); --modal-control-button-color: var(--bs-toast-color);
display: flex; display: flex;
flex-direction: column; flex-direction: row-reverse;
backdrop-filter: blur(6px); backdrop-filter: blur(6px);
} }
#toast-container .toast .toast-header { #toast-container .toast .toast-header {
padding: 0 !important;
background: transparent !important; background: transparent !important;
border-bottom: none; border-bottom: none;
color: var(--toast-text-color) !important;
} }
#toast-container .toast .toast-header strong > * { #toast-container .toast .toast-header strong {
vertical-align: middle; /* The title of the toast is no longer displayed */
display: none;
} }
#toast-container .toast .toast-header .btn-close { #toast-container .toast .toast-header .btn-close {
margin: 0 0 0 12px; margin: 0 var(--bs-toast-padding-x) 0 12px;
}
#toast-container .toast.no-title {
flex-direction: row;
} }
#toast-container .toast .toast-body { #toast-container .toast .toast-body {
flex-grow: 1; flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 0;
}
#toast-container .toast:not(.no-title) .bx {
margin-inline-end: 0.5em;
font-size: 1.1em;
opacity: 0.85;
}
#toast-container .toast.no-title .bx {
margin-inline-end: 0;
font-size: 1.3em;
}
#toast-container .toast.no-title .toast-body {
padding-top: var(--bs-toast-padding-x);
color: var(--toast-text-color);
} }
/* /*
@@ -617,9 +530,10 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
} }
/* List item */ /* List item */
.jump-to-note-dialog .aa-suggestion, .jump-to-note-dialog .aa-suggestions div,
.note-detail-empty .aa-suggestion { .note-detail-empty .aa-suggestions div {
border-radius: 6px; border-radius: 6px;
padding: 6px 12px;
color: var(--menu-text-color); color: var(--menu-text-color);
cursor: default; cursor: default;
} }

View File

@@ -29,7 +29,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-inline-start: 8px; margin-left: 8px;
border: 0; border: 0;
border-radius: 50%; border-radius: 50%;
padding: 0; padding: 0;
@@ -56,7 +56,7 @@
} }
.modal .modal-header .help-button { .modal .modal-header .help-button {
margin-inline-end: 0; margin-right: 0;
font-size: calc(var(--modal-control-button-size) * .75); font-size: calc(var(--modal-control-button-size) * .75);
font-family: unset; font-family: unset;
font-weight: bold; font-weight: bold;
@@ -141,7 +141,7 @@ div.tn-tool-dialog {
/* Search box wrapper */ /* Search box wrapper */
.jump-to-note-dialog .input-group { .jump-to-note-dialog .input-group {
margin-inline-end: 16px; margin-right: 16px;
} }
.jump-to-note-dialog .input-group:hover { .jump-to-note-dialog .input-group:hover {
@@ -197,8 +197,8 @@ div.tn-tool-dialog {
border: unset; border: unset;
padding-top: var(--timeline-item-top-padding); padding-top: var(--timeline-item-top-padding);
padding-bottom: var(--timeline-item-bottom-padding); padding-bottom: var(--timeline-item-bottom-padding);
padding-inline-start: calc(var(--timeline-left-gap) + var(--timeline-right-gap)); padding-left: calc(var(--timeline-left-gap) + var(--timeline-right-gap));
padding-inline-end: var(--timeline-left-gap); padding-right: var(--timeline-left-gap);
color: var(--active-item-text-color); color: var(--active-item-text-color);
} }
@@ -259,7 +259,7 @@ div.tn-tool-dialog {
position: absolute; position: absolute;
content: ""; content: "";
top: var(--connector-top, 0); top: var(--connector-top, 0);
inset-inline-start: calc(var(--timeline-left-gap) + ((var(--timeline-bullet-size) - var(--timeline-connector-size)) / 2)); left: calc(var(--timeline-left-gap) + ((var(--timeline-bullet-size) - var(--timeline-connector-size)) / 2));
bottom: var(--connector-bottom, 0); bottom: var(--connector-bottom, 0);
width: var(--timeline-connector-size); width: var(--timeline-connector-size);
border-radius: var(--connector-radius, 0) var(--connector-radius, 0) 0 0; border-radius: var(--connector-radius, 0) var(--connector-radius, 0) 0 0;
@@ -291,7 +291,7 @@ div.tn-tool-dialog {
position: absolute; position: absolute;
content: ""; content: "";
top: calc(var(--timeline-item-top-padding) + var(--timeline-bullet-vertical-pos)); top: calc(var(--timeline-item-top-padding) + var(--timeline-bullet-vertical-pos));
inset-inline-start: var(--timeline-left-gap); left: var(--timeline-left-gap);
width: var(--timeline-bullet-size); width: var(--timeline-bullet-size);
height: var(--timeline-bullet-size); height: var(--timeline-bullet-size);
border-radius: 50%; border-radius: 50%;
@@ -374,7 +374,7 @@ div.tn-tool-dialog {
} }
.help-dialog .help-cards kbd:first-child { .help-dialog .help-cards kbd:first-child {
margin-inline-start: 0; margin-left: 0;
} }
/* Inline code - used for Markdown samples */ /* Inline code - used for Markdown samples */
@@ -392,8 +392,7 @@ div.tn-tool-dialog {
} }
.delete-notes-list .note-path { .delete-notes-list .note-path {
padding-inline-start: 8px; padding-left: 8px;
color: var(--muted-text-color)
} }
/* /*
@@ -402,7 +401,7 @@ div.tn-tool-dialog {
/* Labels */ /* Labels */
.attr-edit-table th { .attr-edit-table th {
padding-inline-end: 12px; padding-right: 12px;
font-weight: normal; font-weight: normal;
white-space: nowrap; white-space: nowrap;
} }
@@ -420,5 +419,5 @@ div.tn-tool-dialog {
} }
.note-type-chooser-dialog div.note-type-dropdown .dropdown-item span.bx { .note-type-chooser-dialog div.note-type-dropdown .dropdown-item span.bx {
margin-inline-end: .25em; margin-right: .25em;
} }

View File

@@ -5,8 +5,7 @@
button.btn.btn-primary, button.btn.btn-primary,
button.btn.btn-secondary, button.btn.btn-secondary,
button.btn.btn-sm:not(.select-button), button.btn.btn-sm:not(.select-button),
button.btn.btn-success, 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 {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: 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-primary:hover,
button.btn.btn-secondary:hover, button.btn.btn-secondary:hover,
button.btn.btn-sm:not(.select-button):hover, button.btn.btn-sm:not(.select-button):hover,
button.btn.btn-success: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 {
background: var(--cmd-button-hover-background-color); background: var(--cmd-button-hover-background-color);
color: var(--cmd-button-hover-text-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-primary:active,
button.btn.btn-secondary:active, button.btn.btn-secondary:active,
button.btn.btn-sm:not(.select-button):active, button.btn.btn-sm:not(.select-button):active,
button.btn.btn-success: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 {
opacity: 0.85; opacity: 0.85;
box-shadow: unset; box-shadow: unset;
background: var(--cmd-button-background-color) !important; 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-primary:disabled,
button.btn.btn-secondary:disabled, button.btn.btn-secondary:disabled,
button.btn.btn-sm:not(.select-button):disabled, button.btn.btn-sm:not(.select-button):disabled,
button.btn.btn-success: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 {
opacity: var(--cmd-button-disabled-opacity); opacity: var(--cmd-button-disabled-opacity);
} }
button.btn.btn-primary:focus-visible, button.btn.btn-primary:focus-visible,
button.btn.btn-secondary:focus-visible, button.btn.btn-secondary:focus-visible,
button.btn.btn-sm:not(.select-button):focus-visible, button.btn.btn-sm:not(.select-button):focus-visible,
button.btn.btn-success: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 {
outline: 2px solid var(--input-focus-outline-color); outline: 2px solid var(--input-focus-outline-color);
} }
@@ -62,7 +57,7 @@ button.btn.btn-secondary span.bx,
button.btn.btn-sm span.bx, button.btn.btn-sm span.bx,
button.btn.btn-success span.bx { button.btn.btn-success span.bx {
color: var(--cmd-button-icon-color); color: var(--cmd-button-icon-color);
padding-inline-end: 0.35em; padding-right: 0.35em;
font-size: 1.2em; font-size: 1.2em;
} }
@@ -71,7 +66,7 @@ button.btn.btn-primary kbd,
button.btn.btn-secondary kbd, button.btn.btn-secondary kbd,
button.btn.btn-sm kbd, button.btn.btn-sm kbd,
button.btn.btn-success kbd { button.btn.btn-success kbd {
margin-inline-start: 0.5em; margin-left: 0.5em;
background: var(--cmd-button-keyboard-shortcut-background); background: var(--cmd-button-keyboard-shortcut-background);
color: var(--cmd-button-keyboard-shortcut-color); color: var(--cmd-button-keyboard-shortcut-color);
font-size: 0.6em; font-size: 0.6em;
@@ -84,7 +79,7 @@ button.btn.btn-success kbd {
*/ */
:root .icon-action:not(.global-menu-button), :root .icon-action:not(.global-menu-button),
:root .tn-tool-button, :root .btn.tn-tool-button,
:root .btn-group .tn-tool-button:not(:last-child), :root .btn-group .tn-tool-button:not(:last-child),
:root .btn-group .tn-tool-button:last-child { :root .btn-group .tn-tool-button:last-child {
width: var(--icon-button-size); width: var(--icon-button-size);
@@ -96,13 +91,8 @@ button.btn.btn-success kbd {
color: var(--icon-button-color); color: var(--icon-button-color);
} }
:root .btn-group .icon-action:last-child {
border-top-left-radius: unset !important;
border-bottom-left-radius: unset !important;
}
.btn-group .tn-tool-button + .tn-tool-button { .btn-group .tn-tool-button + .tn-tool-button {
margin-inline-start: 4px !important; margin-left: 4px !important;
} }
/* The "x" icon button */ /* The "x" icon button */
@@ -159,11 +149,8 @@ input[type="password"],
input[type="date"], input[type="date"],
input[type="time"], input[type="time"],
input[type="datetime-local"], input[type="datetime-local"],
:root input.ck.ck-input-text,
:root input.ck.ck-input-number,
textarea.form-control, textarea.form-control,
textarea, textarea,
:root textarea.ck.ck-textarea,
.tn-input-field { .tn-input-field {
outline: 3px solid transparent; outline: 3px solid transparent;
outline-offset: 6px; outline-offset: 6px;
@@ -180,11 +167,8 @@ input[type="password"]:hover,
input[type="date"]:hover, input[type="date"]:hover,
input[type="time"]:hover, input[type="time"]:hover,
input[type="datetime-local"]: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.form-control:hover,
textarea:hover, textarea:hover,
:root textarea.ck.ck-textarea:hover,
.tn-input-field:hover { .tn-input-field:hover {
background: var(--input-hover-background); background: var(--input-hover-background);
color: var(--input-hover-color); color: var(--input-hover-color);
@@ -197,11 +181,8 @@ input[type="password"]:focus,
input[type="date"]:focus, input[type="date"]:focus,
input[type="time"]:focus, input[type="time"]:focus,
input[type="datetime-local"]:focus, input[type="datetime-local"]:focus,
:root input.ck.ck-input-text:not([readonly="true"]):focus,
:root input.ck.ck-input-number:not([readonly="true"]):focus,
textarea.form-control:focus, textarea.form-control:focus,
textarea:focus, textarea:focus,
:root textarea.ck.ck-textarea:focus,
.tn-input-field:focus, .tn-input-field:focus,
.tn-input-field:focus-within { .tn-input-field:focus-within {
box-shadow: unset; box-shadow: unset;
@@ -237,7 +218,7 @@ input::selection,
outline-offset: 6px; outline-offset: 6px;
background: var(--input-background-color); background: var(--input-background-color);
border-radius: 6px; border-radius: 6px;
padding-inline-end: 8px; padding-right: 8px;
color: var(--quick-search-color); color: var(--quick-search-color);
flex-wrap: nowrap; flex-wrap: nowrap;
} }
@@ -357,20 +338,13 @@ select.form-control,
outline: 3px solid transparent; outline: 3px solid transparent;
outline-offset: 6px; outline-offset: 6px;
padding-inline-end: calc(15px + 1.5rem); padding-right: calc(15px + 1.5rem);
background: var(--input-background-color) var(--dropdown-arrow);; background: var(--input-background-color) var(--dropdown-arrow);
color: var(--input-text-color); color: var(--input-text-color);
border: unset; border: unset;
border-radius: 0.375rem; border-radius: 0.375rem;
} }
body[dir=rtl] select,
body[dir=rtl] select.form-select,
body[dir=rtl] select.form-control,
body[dir=rtl] .select-button.dropdown-toggle.btn {
background-position: left 0.75rem center;
}
select:hover, select:hover,
select.form-select:hover, select.form-select:hover,
select.form-control:hover, select.form-control:hover,
@@ -451,7 +425,7 @@ optgroup {
content: "\eae1"; content: "\eae1";
width: 2em; width: 2em;
height: 100%; height: 100%;
inset-inline-end: 0; right: 0;
top: 0; top: 0;
font-size: 1.2em; font-size: 1.2em;
font-family: boxicons; font-family: boxicons;
@@ -469,7 +443,7 @@ optgroup {
--box-label-gap: 0.5em; --box-label-gap: 0.5em;
position: relative; position: relative;
padding-inline-start: calc(var(--box-size) + var(--box-label-gap)) !important; padding-left: calc(var(--box-size) + var(--box-label-gap)) !important;
user-select: none; user-select: none;
} }
@@ -478,10 +452,9 @@ optgroup {
label.tn-checkbox > input[type="checkbox"] { label.tn-checkbox > input[type="checkbox"] {
position: absolute; position: absolute;
top: 0; top: 0;
inset-inline-start: 0; left: 0;
width: var(--box-size); width: var(--box-size);
height: 100%; height: 100%;
margin: unset;
opacity: 0 !important; opacity: 0 !important;
} }
@@ -492,7 +465,7 @@ optgroup {
content: ""; content: "";
position: absolute; position: absolute;
top: 50%; top: 50%;
inset-inline-start: 0; left: 0;
translate: 0 -50%; translate: 0 -50%;
width: var(--box-size); width: var(--box-size);
height: var(--box-size); height: var(--box-size);

View File

@@ -19,11 +19,11 @@
} }
.chat-message.user-message { .chat-message.user-message {
margin-inline-start: auto; margin-left: auto;
} }
.chat-message.assistant-message { .chat-message.assistant-message {
margin-inline-end: auto; margin-right: auto;
} }
.message-avatar { .message-avatar {
@@ -33,7 +33,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-inline-end: 8px; margin-right: 8px;
} }
.user-message .message-avatar { .user-message .message-avatar {

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