mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 11:26:15 +01:00
Compare commits
1 Commits
fix/mkdocs
...
fix/resolv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0a55fec60 |
@@ -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
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -2,5 +2,3 @@
|
|||||||
|
|
||||||
github: [eliandoran]
|
github: [eliandoran]
|
||||||
custom: ["https://paypal.me/eliandoran"]
|
custom: ["https://paypal.me/eliandoran"]
|
||||||
liberapay: ElianDoran
|
|
||||||
buy_me_a_coffee: eliandoran
|
|
||||||
|
|||||||
24
.github/actions/build-electron/action.yml
vendored
24
.github/actions/build-electron/action.yml
vendored
@@ -86,7 +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 }}
|
||||||
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
|
run: pnpm nx --project=desktop electron-forge:make -- --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
|
||||||
|
|
||||||
# Add DMG signing step
|
# Add DMG signing step
|
||||||
- name: Sign DMG
|
- name: Sign DMG
|
||||||
@@ -162,25 +162,3 @@ runs:
|
|||||||
echo "Found ZIP: $zip_file"
|
echo "Found ZIP: $zip_file"
|
||||||
echo "Note: ZIP files are not code signed, but their contents should be"
|
echo "Note: ZIP files are not code signed, but their contents should be"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Sign the RPM
|
|
||||||
if: inputs.os == 'linux'
|
|
||||||
shell: ${{ inputs.shell }}
|
|
||||||
run: |
|
|
||||||
echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import
|
|
||||||
|
|
||||||
# Import the key into RPM for verification
|
|
||||||
gpg --export -a > pubkey
|
|
||||||
rpm --import pubkey
|
|
||||||
rm pubkey
|
|
||||||
|
|
||||||
# Sign the RPM
|
|
||||||
rpm_file=$(find ./apps/desktop/upload -name "*.rpm" -print -quit)
|
|
||||||
rpmsign --define "_gpg_name Trilium Notes Signing Key <triliumnotes@outlook.com>" --addsign "$rpm_file"
|
|
||||||
rpm -Kv "$rpm_file"
|
|
||||||
|
|
||||||
# Validate code signing
|
|
||||||
if ! rpm -K "$rpm_file" | grep -q "digests signatures OK"; then
|
|
||||||
echo .rpm file not signed
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|||||||
4
.github/actions/build-server/action.yml
vendored
4
.github/actions/build-server/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v5
|
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: |
|
||||||
|
|||||||
2
.github/actions/report-size/action.yml
vendored
2
.github/actions/report-size/action.yml
vendored
@@ -44,7 +44,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
# Checkout branch to compare to [required]
|
# Checkout branch to compare to [required]
|
||||||
- name: Checkout base branch
|
- name: Checkout base branch
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.branch }}
|
ref: ${{ inputs.branch }}
|
||||||
path: br-base
|
path: br-base
|
||||||
|
|||||||
18
.github/workflows/checks.yml
vendored
18
.github/workflows/checks.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Checks
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request_target:
|
|
||||||
types: [synchronize]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Check if PRs have conflicts
|
|
||||||
uses: eps1lon/actions-label-merge-conflict@v3
|
|
||||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
|
||||||
with:
|
|
||||||
dirtyLabel: "merge-conflicts"
|
|
||||||
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"
|
|
||||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -57,7 +57,7 @@ jobs:
|
|||||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||||
|
|||||||
190
.github/workflows/deploy-docs.yml
vendored
190
.github/workflows/deploy-docs.yml
vendored
@@ -1,190 +0,0 @@
|
|||||||
# GitHub Actions workflow for deploying MkDocs documentation to Cloudflare Pages
|
|
||||||
# This workflow builds and deploys your MkDocs site when changes are pushed to main
|
|
||||||
name: Deploy MkDocs Documentation
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Trigger on push to main branch
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- master # Also support master branch
|
|
||||||
# Only run when docs files change
|
|
||||||
paths:
|
|
||||||
- 'docs/**'
|
|
||||||
- 'README.md' # README is synced to docs/index.md
|
|
||||||
- 'mkdocs.yml'
|
|
||||||
- 'requirements-docs.txt'
|
|
||||||
- '.github/workflows/deploy-docs.yml'
|
|
||||||
- 'scripts/fix-mkdocs-structure.ts'
|
|
||||||
- 'validate-docs-links.ts'
|
|
||||||
|
|
||||||
# Allow manual triggering from Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# Run on pull requests for preview deployments
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
paths:
|
|
||||||
- 'docs/**'
|
|
||||||
- 'README.md' # README is synced to docs/index.md
|
|
||||||
- 'mkdocs.yml'
|
|
||||||
- 'requirements-docs.txt'
|
|
||||||
- '.github/workflows/deploy-docs.yml'
|
|
||||||
- 'scripts/fix-mkdocs-structure.ts'
|
|
||||||
- 'validate-docs-links.ts'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-deploy:
|
|
||||||
name: Build and Deploy MkDocs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 10
|
|
||||||
|
|
||||||
# Required permissions for deployment
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
deployments: write
|
|
||||||
pull-requests: write # For PR preview comments
|
|
||||||
id-token: write # For OIDC authentication (if needed)
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # Fetch all history for git info and mkdocs-git-revision-date plugin
|
|
||||||
|
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: '3.13'
|
|
||||||
cache: 'pip'
|
|
||||||
cache-dependency-path: 'requirements-docs.txt'
|
|
||||||
|
|
||||||
- name: Install MkDocs and Dependencies
|
|
||||||
run: |
|
|
||||||
pip install --upgrade pip
|
|
||||||
pip install -r requirements-docs.txt
|
|
||||||
env:
|
|
||||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
|
||||||
|
|
||||||
# Setup pnpm before fixing docs structure
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v4
|
|
||||||
|
|
||||||
# Setup Node.js with pnpm
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v5
|
|
||||||
with:
|
|
||||||
node-version: '22'
|
|
||||||
cache: 'pnpm'
|
|
||||||
|
|
||||||
# Install Node.js dependencies for the TypeScript script
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Fix Documentation Structure
|
|
||||||
run: |
|
|
||||||
# Fix duplicate navigation entries by moving overview pages to index.md
|
|
||||||
pnpm run chore:fix-mkdocs-structure
|
|
||||||
|
|
||||||
- name: Build MkDocs Site
|
|
||||||
run: |
|
|
||||||
# Build with strict mode but allow expected warnings
|
|
||||||
mkdocs build --verbose || {
|
|
||||||
EXIT_CODE=$?
|
|
||||||
# Check if the only issue is expected warnings
|
|
||||||
if mkdocs build 2>&1 | grep -E "WARNING.*(README|not found)" && \
|
|
||||||
[ $(mkdocs build 2>&1 | grep -c "ERROR") -eq 0 ]; then
|
|
||||||
echo "✅ Build succeeded with expected warnings"
|
|
||||||
mkdocs build --verbose
|
|
||||||
else
|
|
||||||
echo "❌ Build failed with unexpected errors"
|
|
||||||
exit $EXIT_CODE
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Validate Built Site
|
|
||||||
run: |
|
|
||||||
# Basic validation that important files exist
|
|
||||||
test -f site/index.html || (echo "ERROR: site/index.html not found" && exit 1)
|
|
||||||
test -f site/sitemap.xml || (echo "ERROR: site/sitemap.xml not found" && exit 1)
|
|
||||||
test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1)
|
|
||||||
echo "✅ Site validation passed"
|
|
||||||
|
|
||||||
- name: Validate Documentation Links
|
|
||||||
run: |
|
|
||||||
# Run the TypeScript link validation script
|
|
||||||
pnpm tsx validate-docs-links.ts
|
|
||||||
|
|
||||||
# Install wrangler globally to avoid workspace issues
|
|
||||||
- name: Install Wrangler
|
|
||||||
run: |
|
|
||||||
npm install -g wrangler
|
|
||||||
|
|
||||||
# Deploy using Wrangler (use pre-installed wrangler)
|
|
||||||
- name: Deploy to Cloudflare Pages
|
|
||||||
id: deploy
|
|
||||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
command: pages deploy site --project-name=trilium-docs --branch=${{ github.ref_name }}
|
|
||||||
wranglerVersion: '' # Use pre-installed version
|
|
||||||
|
|
||||||
# Deploy preview for PRs
|
|
||||||
- name: Deploy Preview to Cloudflare Pages
|
|
||||||
id: preview-deployment
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
command: pages deploy site --project-name=trilium-docs --branch=pr-${{ github.event.pull_request.number }}
|
|
||||||
wranglerVersion: '' # Use pre-installed version
|
|
||||||
|
|
||||||
# Post deployment URL as PR comment
|
|
||||||
- name: Comment PR with Preview URL
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
uses: actions/github-script@v8
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
const prNumber = context.issue.number;
|
|
||||||
// Construct preview URL based on Cloudflare Pages pattern
|
|
||||||
const previewUrl = `https://pr-${prNumber}.trilium-docs.pages.dev`;
|
|
||||||
const mainUrl = 'https://docs.triliumnotes.org';
|
|
||||||
|
|
||||||
// Check if we already commented
|
|
||||||
const comments = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: prNumber
|
|
||||||
});
|
|
||||||
|
|
||||||
const botComment = comments.data.find(comment =>
|
|
||||||
comment.user.type === 'Bot' &&
|
|
||||||
comment.body.includes('Documentation preview is ready')
|
|
||||||
);
|
|
||||||
|
|
||||||
const commentBody = `📚 Documentation preview is ready!\n\n🔗 Preview URL: ${previewUrl}\n📖 Production URL: ${mainUrl}\n\n✅ All checks passed\n\n_This preview will be updated automatically with new commits._`;
|
|
||||||
|
|
||||||
if (botComment) {
|
|
||||||
// Update existing comment
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: botComment.id,
|
|
||||||
body: commentBody
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Create new comment
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
issue_number: prNumber,
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
body: commentBody
|
|
||||||
});
|
|
||||||
}
|
|
||||||
47
.github/workflows/dev.yml
vendored
47
.github/workflows/dev.yml
vendored
@@ -19,24 +19,45 @@ permissions:
|
|||||||
pull-requests: write # for PR comments
|
pull-requests: write # for PR comments
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test_dev:
|
check-affected:
|
||||||
name: Test development
|
name: Check affected jobs (NX)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # needed for https://github.com/marketplace/actions/nx-set-shas
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: 'pnpm'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- uses: nrwl/nx-set-shas@v4
|
||||||
|
- name: Check affected
|
||||||
|
run: pnpm nx affected --verbose -t typecheck build rebuild-deps test-build
|
||||||
|
|
||||||
|
test_dev:
|
||||||
|
name: Test development
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- check-affected
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- name: Set up node & dependencies
|
||||||
|
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,15 +66,16 @@ 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@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
- 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:
|
||||||
@@ -80,7 +103,7 @@ jobs:
|
|||||||
- dockerfile: Dockerfile
|
- dockerfile: Dockerfile
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -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
|
||||||
|
|||||||
12
.github/workflows/main-docker.yml
vendored
12
.github/workflows/main-docker.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
- dockerfile: Dockerfile
|
- dockerfile: Dockerfile
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- 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
|
||||||
@@ -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@v5
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
@@ -82,7 +82,7 @@ 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()
|
||||||
@@ -141,10 +141,10 @@ jobs:
|
|||||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -223,7 +223,7 @@ jobs:
|
|||||||
- build
|
- build
|
||||||
steps:
|
steps:
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests
|
||||||
pattern: digests-*
|
pattern: digests-*
|
||||||
|
|||||||
14
.github/workflows/nightly.yml
vendored
14
.github/workflows/nightly.yml
vendored
@@ -27,7 +27,6 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
nightly-electron:
|
nightly-electron:
|
||||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
|
||||||
name: Deploy nightly
|
name: Deploy nightly
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -48,15 +47,16 @@ jobs:
|
|||||||
forge_platform: win32
|
forge_platform: win32
|
||||||
runs-on: ${{ matrix.os.image }}
|
runs-on: ${{ matrix.os.image }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v5
|
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
|
||||||
@@ -75,10 +75,9 @@ jobs:
|
|||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
|
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
|
||||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
uses: softprops/action-gh-release@v2.3.3
|
uses: softprops/action-gh-release@v2.3.2
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
make_latest: false
|
make_latest: false
|
||||||
@@ -97,7 +96,6 @@ jobs:
|
|||||||
path: apps/desktop/upload
|
path: apps/desktop/upload
|
||||||
|
|
||||||
nightly-server:
|
nightly-server:
|
||||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
|
||||||
name: Deploy server nightly
|
name: Deploy server nightly
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -110,7 +108,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run the build
|
- name: Run the build
|
||||||
uses: ./.github/actions/build-server
|
uses: ./.github/actions/build-server
|
||||||
@@ -119,7 +117,7 @@ jobs:
|
|||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
|
|
||||||
- name: Publish release
|
- name: Publish release
|
||||||
uses: softprops/action-gh-release@v2.3.3
|
uses: softprops/action-gh-release@v2.3.2
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
with:
|
with:
|
||||||
make_latest: false
|
make_latest: false
|
||||||
|
|||||||
24
.github/workflows/playwright.yml
vendored
24
.github/workflows/playwright.yml
vendored
@@ -14,13 +14,19 @@ jobs:
|
|||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
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@v5
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -28,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@v4
|
- run: pnpm exec nx affected -t e2e --exclude desktop-e2e
|
||||||
with:
|
|
||||||
name: e2e report
|
|
||||||
path: apps/server-e2e/test-output
|
|
||||||
|
|||||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -32,15 +32,16 @@ jobs:
|
|||||||
forge_platform: win32
|
forge_platform: win32
|
||||||
runs-on: ${{ matrix.os.image }}
|
runs-on: ${{ matrix.os.image }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- name: Set up node & dependencies
|
- name: Set up node & dependencies
|
||||||
uses: actions/setup-node@v5
|
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:
|
||||||
@@ -57,7 +58,6 @@ jobs:
|
|||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
|
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
|
||||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
|
||||||
|
|
||||||
- name: Upload the artifact
|
- name: Upload the artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ubuntu-24.04-arm
|
||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run the build
|
- name: Run the build
|
||||||
uses: ./.github/actions/build-server
|
uses: ./.github/actions/build-server
|
||||||
@@ -101,20 +101,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- run: mkdir upload
|
- run: mkdir upload
|
||||||
|
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
docs/Release Notes
|
docs/Release Notes
|
||||||
|
|
||||||
- name: Download all artifacts
|
- name: Download all artifacts
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v4
|
||||||
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.3.3
|
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
|
||||||
|
|||||||
11
.github/workflows/unblock_signing.yml
vendored
11
.github/workflows/unblock_signing.yml
vendored
@@ -1,11 +0,0 @@
|
|||||||
name: Unblock signing
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
unblock-win-signing:
|
|
||||||
runs-on: win-signing
|
|
||||||
steps:
|
|
||||||
- run: |
|
|
||||||
cat ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
|
|
||||||
rm ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
|
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -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
|
||||||
@@ -11,7 +10,6 @@ node_modules
|
|||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
/.idea
|
/.idea
|
||||||
.idea
|
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
.c9/
|
.c9/
|
||||||
@@ -33,11 +31,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 +47,3 @@ upload
|
|||||||
|
|
||||||
/result
|
/result
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
|
|
||||||
# docs
|
|
||||||
site/
|
|
||||||
|
|||||||
6
.idea/.gitignore
generated
vendored
Normal file
6
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/workspace.xml
|
||||||
|
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources.local.xml
|
||||||
|
/dataSources/
|
||||||
15
.idea/codeStyles/Project.xml
generated
Normal file
15
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="OTHER_INDENT_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<codeStyleSettings language="JSON">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="document.db" uuid="2a4ac1e6-b828-4a2a-8e4a-3f59f10aff26">
|
||||||
|
<driver-ref>sqlite.xerial</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/data/document.db</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.idea/encodings.xml
generated
Normal file
4
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||||
|
</project>
|
||||||
15
.idea/git_toolbox_prj.xml
generated
Normal file
15
.idea/git_toolbox_prj.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GitToolBoxProjectSettings">
|
||||||
|
<option name="commitMessageIssueKeyValidationOverride">
|
||||||
|
<BoolValueOverride>
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</BoolValueOverride>
|
||||||
|
</option>
|
||||||
|
<option name="commitMessageValidationEnabledOverride">
|
||||||
|
<BoolValueOverride>
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</BoolValueOverride>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/jsLibraryMappings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<includedPredefinedLibrary name="Node.js Core" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/jsLinters/jslint.xml
generated
Normal file
9
.idea/jsLinters/jslint.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JSLintConfiguration">
|
||||||
|
<option devel="true" />
|
||||||
|
<option es6="true" />
|
||||||
|
<option maxerr="50" />
|
||||||
|
<option node="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/misc.xml
generated
Normal file
8
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" default="true" project-jdk-name="openjdk-16" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/trilium.iml" filepath="$PROJECT_DIR$/trilium.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/sqldialects.xml
generated
Normal file
7
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="file://$PROJECT_DIR$" dialect="SQLite" />
|
||||||
|
<file url="PROJECT" dialect="SQLite" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.mailmap
4
.mailmap
@@ -1,2 +1,2 @@
|
|||||||
zadam <adam.zivner@gmail.com>
|
Adam Zivner <adam.zivner@gmail.com>
|
||||||
zadam <zadam.apps@gmail.com>
|
Adam Zivner <zadam.apps@gmail.com>
|
||||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -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",
|
||||||
|
|||||||
2
.vscode/i18n-ally-custom-framework.yml
vendored
2
.vscode/i18n-ally-custom-framework.yml
vendored
@@ -3,7 +3,6 @@
|
|||||||
languageIds:
|
languageIds:
|
||||||
- javascript
|
- javascript
|
||||||
- typescript
|
- typescript
|
||||||
- typescriptreact
|
|
||||||
- html
|
- html
|
||||||
|
|
||||||
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
||||||
@@ -26,7 +25,6 @@ scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
|
|||||||
# The "$1" will be replaced by the keypath specified.
|
# The "$1" will be replaced by the keypath specified.
|
||||||
refactorTemplates:
|
refactorTemplates:
|
||||||
- t("$1")
|
- t("$1")
|
||||||
- {t("$1")}
|
|
||||||
- ${t("$1")}
|
- ${t("$1")}
|
||||||
- <%= t("$1") %>
|
- <%= t("$1") %>
|
||||||
|
|
||||||
|
|||||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -28,12 +28,5 @@
|
|||||||
"typescript.validate.enable": true,
|
"typescript.validate.enable": true,
|
||||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||||
"search.exclude": {
|
|
||||||
"**/node_modules": true,
|
|
||||||
"docs/**/*.html": true,
|
|
||||||
"docs/**/*.png": true,
|
|
||||||
"apps/server/src/assets/doc_notes/**": true,
|
|
||||||
"apps/edit-docs/demo/**": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
5
.vscode/snippets.code-snippets
vendored
5
.vscode/snippets.code-snippets
vendored
@@ -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 */"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
152
CLAUDE.md
152
CLAUDE.md
@@ -1,152 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
- `pnpm install` - Install all dependencies
|
|
||||||
- `corepack enable` - Enable pnpm if not available
|
|
||||||
|
|
||||||
### Running Applications
|
|
||||||
- `pnpm run server:start` - Start development server (http://localhost:8080)
|
|
||||||
- `pnpm run server:start-prod` - Run server in production mode
|
|
||||||
|
|
||||||
### Building
|
|
||||||
- `pnpm run client:build` - Build client application
|
|
||||||
- `pnpm run server:build` - Build server application
|
|
||||||
- `pnpm run electron:build` - Build desktop application
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
- `pnpm test:all` - Run all tests (parallel + sequential)
|
|
||||||
- `pnpm test:parallel` - Run tests that can run in parallel
|
|
||||||
- `pnpm test:sequential` - Run tests that must run sequentially (server, ckeditor5-mermaid, ckeditor5-math)
|
|
||||||
- `pnpm coverage` - Generate coverage reports
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
### Monorepo Structure
|
|
||||||
- **apps/**: Runnable applications
|
|
||||||
- `client/` - Frontend application (shared by server and desktop)
|
|
||||||
- `server/` - Node.js server with web interface
|
|
||||||
- `desktop/` - Electron desktop application
|
|
||||||
- `web-clipper/` - Browser extension for saving web content
|
|
||||||
- Additional tools: `db-compare`, `dump-db`, `edit-docs`
|
|
||||||
|
|
||||||
- **packages/**: Shared libraries
|
|
||||||
- `commons/` - Shared interfaces and utilities
|
|
||||||
- `ckeditor5/` - Custom rich text editor with Trilium-specific plugins
|
|
||||||
- `codemirror/` - Code editor customizations
|
|
||||||
- `highlightjs/` - Syntax highlighting
|
|
||||||
- Custom CKEditor plugins: `ckeditor5-admonition`, `ckeditor5-footnotes`, `ckeditor5-math`, `ckeditor5-mermaid`
|
|
||||||
|
|
||||||
### Core Architecture Patterns
|
|
||||||
|
|
||||||
#### Three-Layer Cache System
|
|
||||||
- **Becca** (Backend Cache): Server-side entity cache (`apps/server/src/becca/`)
|
|
||||||
- **Froca** (Frontend Cache): Client-side mirror of backend data (`apps/client/src/services/froca.ts`)
|
|
||||||
- **Shaca** (Share Cache): Optimized cache for shared/published notes (`apps/server/src/share/`)
|
|
||||||
|
|
||||||
#### Entity System
|
|
||||||
Core entities are defined in `apps/server/src/becca/entities/`:
|
|
||||||
- `BNote` - Notes with content and metadata
|
|
||||||
- `BBranch` - Hierarchical relationships between notes (allows multiple parents)
|
|
||||||
- `BAttribute` - Key-value metadata attached to notes
|
|
||||||
- `BRevision` - Note version history
|
|
||||||
- `BOption` - Application configuration
|
|
||||||
|
|
||||||
#### Widget-Based UI
|
|
||||||
Frontend uses a widget system (`apps/client/src/widgets/`):
|
|
||||||
- `BasicWidget` - Base class for all UI components
|
|
||||||
- `NoteContextAwareWidget` - Widgets that respond to note changes
|
|
||||||
- `RightPanelWidget` - Widgets displayed in the right panel
|
|
||||||
- Type-specific widgets in `type_widgets/` directory
|
|
||||||
|
|
||||||
#### API Architecture
|
|
||||||
- **Internal API**: REST endpoints in `apps/server/src/routes/api/`
|
|
||||||
- **ETAPI**: External API for third-party integrations (`apps/server/src/etapi/`)
|
|
||||||
- **WebSocket**: Real-time synchronization (`apps/server/src/services/ws.ts`)
|
|
||||||
|
|
||||||
### Key Files for Understanding Architecture
|
|
||||||
|
|
||||||
1. **Application Entry Points**:
|
|
||||||
- `apps/server/src/main.ts` - Server startup
|
|
||||||
- `apps/client/src/desktop.ts` - Client initialization
|
|
||||||
|
|
||||||
2. **Core Services**:
|
|
||||||
- `apps/server/src/becca/becca.ts` - Backend data management
|
|
||||||
- `apps/client/src/services/froca.ts` - Frontend data synchronization
|
|
||||||
- `apps/server/src/services/backend_script_api.ts` - Scripting API
|
|
||||||
|
|
||||||
3. **Database Schema**:
|
|
||||||
- `apps/server/src/assets/db/schema.sql` - Core database structure
|
|
||||||
|
|
||||||
4. **Configuration**:
|
|
||||||
- `package.json` - Project dependencies and scripts
|
|
||||||
|
|
||||||
## Note Types and Features
|
|
||||||
|
|
||||||
Trilium supports multiple note types, each with specialized widgets:
|
|
||||||
- **Text**: Rich text with CKEditor5 (markdown import/export)
|
|
||||||
- **Code**: Syntax-highlighted code editing with CodeMirror
|
|
||||||
- **File**: Binary file attachments
|
|
||||||
- **Image**: Image display with editing capabilities
|
|
||||||
- **Canvas**: Drawing/diagramming with Excalidraw
|
|
||||||
- **Mermaid**: Diagram generation
|
|
||||||
- **Relation Map**: Visual note relationship mapping
|
|
||||||
- **Web View**: Embedded web pages
|
|
||||||
- **Doc/Book**: Hierarchical documentation structure
|
|
||||||
|
|
||||||
## Development Guidelines
|
|
||||||
|
|
||||||
### Testing Strategy
|
|
||||||
- Server tests run sequentially due to shared database
|
|
||||||
- Client tests can run in parallel
|
|
||||||
- E2E tests use Playwright for both server and desktop apps
|
|
||||||
- Build validation tests check artifact integrity
|
|
||||||
|
|
||||||
### Scripting System
|
|
||||||
Trilium provides powerful user scripting capabilities:
|
|
||||||
- Frontend scripts run in browser context
|
|
||||||
- Backend scripts run in Node.js context with full API access
|
|
||||||
- Script API documentation available in `docs/Script API/`
|
|
||||||
|
|
||||||
### Internationalization
|
|
||||||
- Translation files in `apps/client/src/translations/`
|
|
||||||
- Supported languages: English, German, Spanish, French, Romanian, Chinese
|
|
||||||
|
|
||||||
### Security Considerations
|
|
||||||
- Per-note encryption with granular protected sessions
|
|
||||||
- CSRF protection for API endpoints
|
|
||||||
- OpenID and TOTP authentication support
|
|
||||||
- Sanitization of user-generated content
|
|
||||||
|
|
||||||
## Common Development Tasks
|
|
||||||
|
|
||||||
### Adding New Note Types
|
|
||||||
1. Create widget in `apps/client/src/widgets/type_widgets/`
|
|
||||||
2. Register in `apps/client/src/services/note_types.ts`
|
|
||||||
3. Add backend handling in `apps/server/src/services/notes.ts`
|
|
||||||
|
|
||||||
### Extending Search
|
|
||||||
- Search expressions handled in `apps/server/src/services/search/`
|
|
||||||
- Add new search operators in search context files
|
|
||||||
|
|
||||||
### Custom CKEditor Plugins
|
|
||||||
- Create new package in `packages/` following existing plugin structure
|
|
||||||
- Register in `packages/ckeditor5/src/plugins.ts`
|
|
||||||
|
|
||||||
### Database Migrations
|
|
||||||
- Add migration scripts in `apps/server/src/migrations/`
|
|
||||||
- Update schema in `apps/server/src/assets/db/schema.sql`
|
|
||||||
|
|
||||||
## Build System Notes
|
|
||||||
- Uses pnpm for monorepo management
|
|
||||||
- Vite for fast development builds
|
|
||||||
- ESBuild for production optimization
|
|
||||||
- pnpm workspaces for dependency management
|
|
||||||
- Docker support with multi-stage builds
|
|
||||||
66
README.md
66
README.md
@@ -1,11 +1,11 @@
|
|||||||
# Trilium Notes
|
# Trilium Notes
|
||||||
|
|
||||||
 
|

|
||||||

|

|
||||||

|

|
||||||
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) [](https://hosted.weblate.org/engage/trilium/)
|
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
|
||||||
|
|
||||||
[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](./docs/README-ZH_CN.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.
|
||||||
|
|
||||||
@@ -46,15 +46,15 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
|
|||||||
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||||
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
||||||
|
|
||||||
## ❓Why TriliumNext?
|
## ⚠️ Why TriliumNext?
|
||||||
|
|
||||||
The original Trilium developer ([Zadam](https://github.com/zadam)) has graciously given the Trilium repository to the community project which resides at https://github.com/TriliumNext
|
[The original Trilium project is in maintenance mode](https://github.com/zadam/trilium/issues/4620).
|
||||||
|
|
||||||
### ⬆️Migrating from Zadam/Trilium?
|
### Migrating from Trilium?
|
||||||
|
|
||||||
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Trilium instance. Simply [install TriliumNext/Trilium](#-installation) as usual and it will use your existing database.
|
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Notes instance. Simply [install TriliumNext/Notes](#-installation) as usual and it will use your existing database.
|
||||||
|
|
||||||
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/Notes/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 have their sync versions incremented.
|
||||||
|
|
||||||
## 📖 Documentation
|
## 📖 Documentation
|
||||||
|
|
||||||
@@ -75,14 +75,14 @@ Feel free to join our official conversations. We would love to hear what feature
|
|||||||
|
|
||||||
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions.)
|
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions.)
|
||||||
- The `General` Matrix room is also bridged to [XMPP](xmpp:discuss@trilium.thisgreat.party?join)
|
- The `General` Matrix room is also bridged to [XMPP](xmpp:discuss@trilium.thisgreat.party?join)
|
||||||
- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For asynchronous discussions.)
|
- [Github Discussions](https://github.com/TriliumNext/Notes/discussions) (For asynchronous discussions.)
|
||||||
- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug reports and feature requests.)
|
- [Github Issues](https://github.com/TriliumNext/Notes/issues) (For bug reports and feature requests.)
|
||||||
|
|
||||||
## 🏗 Installation
|
## 🏗 Installation
|
||||||
|
|
||||||
### Windows / MacOS
|
### Windows / MacOS
|
||||||
|
|
||||||
Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package and run the `trilium` executable.
|
Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the `trilium` executable.
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ If your distribution is listed in the table below, use your distribution's packa
|
|||||||
|
|
||||||
[](https://repology.org/project/triliumnext/versions)
|
[](https://repology.org/project/triliumnext/versions)
|
||||||
|
|
||||||
You may also download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package and run the `trilium` executable.
|
You may also download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the `trilium` executable.
|
||||||
|
|
||||||
TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
|
TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
|
||||||
|
|
||||||
@@ -104,33 +104,23 @@ Currently only the latest versions of Chrome & Firefox are supported (and tested
|
|||||||
|
|
||||||
To use TriliumNext on a mobile device, you can use a mobile web browser to access the mobile interface of a server installation (see below).
|
To use TriliumNext on a mobile device, you can use a mobile web browser to access the mobile interface of a server installation (see below).
|
||||||
|
|
||||||
See issue https://github.com/TriliumNext/Trilium/issues/4962 for more information on mobile app support.
|
If you prefer a native Android app, you can use [TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid). Report bugs and missing features at [their repository](https://github.com/FliegendeWurst/TriliumDroid).
|
||||||
|
|
||||||
If you prefer a native Android app, you can use [TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid).
|
See issue https://github.com/TriliumNext/Notes/issues/72 for more information on mobile app support.
|
||||||
Report bugs and missing features at [their repository](https://github.com/FliegendeWurst/TriliumDroid).
|
|
||||||
Note: It is best to disable automatic updates on your server installation (see below) when using TriliumDroid since the sync version must match between Trilium and TriliumDroid.
|
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
|
||||||
To install TriliumNext on your own server (including via Docker from [Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
|
To install TriliumNext on your own server (including via Docker from [Dockerhub](https://hub.docker.com/r/triliumnext/notes)) follow [the server installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
|
||||||
|
|
||||||
|
|
||||||
## 💻 Contribute
|
## 💻 Contribute
|
||||||
|
|
||||||
### Translations
|
|
||||||
|
|
||||||
If you are a native speaker, help us translate Trilium by heading over to our [Weblate page](https://hosted.weblate.org/engage/trilium/).
|
|
||||||
|
|
||||||
Here's the language coverage we have so far:
|
|
||||||
|
|
||||||
[](https://hosted.weblate.org/engage/trilium/)
|
|
||||||
|
|
||||||
### Code
|
### Code
|
||||||
|
|
||||||
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TriliumNext/Trilium.git
|
git clone https://github.com/TriliumNext/Notes.git
|
||||||
cd Trilium
|
cd Notes
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm run server:start
|
pnpm run server:start
|
||||||
```
|
```
|
||||||
@@ -139,26 +129,26 @@ pnpm run server:start
|
|||||||
|
|
||||||
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TriliumNext/Trilium.git
|
git clone https://github.com/TriliumNext/Notes.git
|
||||||
cd Trilium
|
cd Notes
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm edit-docs:edit-docs
|
pnpm nx run edit-docs:edit-docs
|
||||||
```
|
```
|
||||||
|
|
||||||
### Building the Executable
|
### Building the Executable
|
||||||
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TriliumNext/Trilium.git
|
git clone https://github.com/TriliumNext/Notes.git
|
||||||
cd Trilium
|
cd Notes
|
||||||
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/Notes/blob/develop/docs/Developer%20Guide/Developer%20Guide/Building%20and%20deployment/Running%20a%20development%20build.md).
|
||||||
|
|
||||||
### Developer Documentation
|
### Developer Documentation
|
||||||
|
|
||||||
Please view the [documentation guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) for details. If you have more questions, feel free to reach out via the links described in the "Discuss with us" section above.
|
Please view the [documentation guide](./docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) for details. If you have more questions, feel free to reach out via the links described in the "Discuss with us" section above.
|
||||||
|
|
||||||
## 👏 Shoutouts
|
## 👏 Shoutouts
|
||||||
|
|
||||||
@@ -170,7 +160,7 @@ Please view the [documentation guide](https://github.com/TriliumNext/Trilium/blo
|
|||||||
## 🤝 Support
|
## 🤝 Support
|
||||||
|
|
||||||
Support for the TriliumNext organization will be possible in the near future. For now, you can:
|
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)
|
- 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/Notes/graphs/contributors))) for a full list)
|
||||||
- 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).
|
- 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).
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,13 @@
|
|||||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.55.0",
|
"@playwright/test": "1.53.2",
|
||||||
"@stylistic/eslint-plugin": "5.3.1",
|
"@stylistic/eslint-plugin": "5.1.0",
|
||||||
"@types/express": "5.0.3",
|
"@types/express": "5.0.3",
|
||||||
"@types/node": "22.18.1",
|
"@types/node": "22.16.2",
|
||||||
"@types/yargs": "17.0.33",
|
"@types/yargs": "17.0.33",
|
||||||
"@vitest/coverage-v8": "3.2.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"eslint": "9.35.0",
|
"eslint": "9.30.1",
|
||||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||||
"esm": "3.2.25",
|
"esm": "3.2.25",
|
||||||
"jsdoc": "4.0.4",
|
"jsdoc": "4.0.4",
|
||||||
@@ -49,8 +49,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.12",
|
"typedoc": "0.28.7",
|
||||||
"typedoc-plugin-missing-exports": "4.1.0"
|
"typedoc-plugin-missing-exports": "4.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"appdmg": "0.6.6"
|
"appdmg": "0.6.6"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@triliumnext/client",
|
"name": "@triliumnext/client",
|
||||||
"version": "0.98.1",
|
"version": "0.96.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",
|
||||||
@@ -9,22 +9,16 @@
|
|||||||
"email": "contact@eliandoran.me",
|
"email": "contact@eliandoran.me",
|
||||||
"url": "https://github.com/TriliumNext/Notes"
|
"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.35.0",
|
"@eslint/js": "9.30.1",
|
||||||
"@excalidraw/excalidraw": "0.18.0",
|
"@excalidraw/excalidraw": "0.18.0",
|
||||||
"@fullcalendar/core": "6.1.19",
|
"@fullcalendar/core": "6.1.18",
|
||||||
"@fullcalendar/daygrid": "6.1.19",
|
"@fullcalendar/daygrid": "6.1.18",
|
||||||
"@fullcalendar/interaction": "6.1.19",
|
"@fullcalendar/interaction": "6.1.18",
|
||||||
"@fullcalendar/list": "6.1.19",
|
"@fullcalendar/list": "6.1.18",
|
||||||
"@fullcalendar/multimonth": "6.1.19",
|
"@fullcalendar/multimonth": "6.1.18",
|
||||||
"@fullcalendar/timegrid": "6.1.19",
|
"@fullcalendar/timegrid": "6.1.18",
|
||||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
"@mermaid-js/layout-elk": "0.1.8",
|
||||||
"@mermaid-js/layout-elk": "0.2.0",
|
|
||||||
"@mind-elixir/node-menu": "5.0.0",
|
"@mind-elixir/node-menu": "5.0.0",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@triliumnext/ckeditor5": "workspace:*",
|
"@triliumnext/ckeditor5": "workspace:*",
|
||||||
@@ -33,17 +27,18 @@
|
|||||||
"@triliumnext/highlightjs": "workspace:*",
|
"@triliumnext/highlightjs": "workspace:*",
|
||||||
"@triliumnext/share-theme": "workspace:*",
|
"@triliumnext/share-theme": "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",
|
||||||
"dayjs": "1.11.18",
|
"dayjs": "1.11.13",
|
||||||
"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.3.0",
|
"globals": "16.3.0",
|
||||||
"i18next": "25.5.2",
|
"i18next": "25.3.2",
|
||||||
"i18next-http-backend": "3.0.2",
|
"i18next-http-backend": "3.0.2",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
|
"jquery-hotkeys": "0.2.2",
|
||||||
"jquery.fancytree": "2.38.5",
|
"jquery.fancytree": "2.38.5",
|
||||||
"jsplumb": "2.15.6",
|
"jsplumb": "2.15.6",
|
||||||
"katex": "0.16.22",
|
"katex": "0.16.22",
|
||||||
@@ -51,30 +46,41 @@
|
|||||||
"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.2.1",
|
"marked": "16.0.0",
|
||||||
"mermaid": "11.11.0",
|
"mermaid": "11.8.1",
|
||||||
"mind-elixir": "5.1.1",
|
"mind-elixir": "5.0.1",
|
||||||
"normalize.css": "8.0.1",
|
"normalize.css": "8.0.1",
|
||||||
"panzoom": "9.4.3",
|
"panzoom": "9.4.3",
|
||||||
"preact": "10.27.1",
|
"preact": "10.26.9",
|
||||||
"react-i18next": "15.7.3",
|
|
||||||
"split.js": "1.6.5",
|
"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"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
"@ckeditor/ckeditor5-inspector": "4.1.0",
|
||||||
"@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.20",
|
"@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/tabulator-tables": "6.2.10",
|
"@types/tabulator-tables": "6.2.7",
|
||||||
"copy-webpack-plugin": "13.0.1",
|
"copy-webpack-plugin": "13.0.0",
|
||||||
"happy-dom": "18.0.1",
|
"happy-dom": "18.0.1",
|
||||||
"script-loader": "0.7.2",
|
"script-loader": "0.7.2",
|
||||||
"vite-plugin-static-copy": "3.1.2"
|
"vite-plugin-static-copy": "3.1.0"
|
||||||
|
},
|
||||||
|
"nx": {
|
||||||
|
"name": "client",
|
||||||
|
"targets": {
|
||||||
|
"serve": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"circular-deps": {
|
||||||
|
"command": "pnpx dpdm -T {projectRoot}/src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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";
|
||||||
@@ -28,17 +28,16 @@ import TouchBarComponent from "./touch_bar.js";
|
|||||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||||
import type CodeMirror from "@triliumnext/codemirror";
|
import type CodeMirror from "@triliumnext/codemirror";
|
||||||
import { StartupChecks } from "./startup_checks.js";
|
import { StartupChecks } from "./startup_checks.js";
|
||||||
import type { CreateNoteOpts } from "../services/note_create.js";
|
|
||||||
import { ColumnComponent } from "tabulator-tables";
|
|
||||||
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
|
|
||||||
import type RootContainer from "../widgets/containers/root_container.js";
|
|
||||||
import { SqlExecuteResults } from "@triliumnext/commons";
|
|
||||||
|
|
||||||
interface Layout {
|
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 +82,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,14 +90,7 @@ export type CommandMappings = {
|
|||||||
closeTocCommand: CommandData;
|
closeTocCommand: CommandData;
|
||||||
closeHlt: CommandData;
|
closeHlt: CommandData;
|
||||||
showLaunchBarSubtree: CommandData;
|
showLaunchBarSubtree: CommandData;
|
||||||
showHiddenSubtree: CommandData;
|
showRevisions: CommandData;
|
||||||
showSQLConsoleHistory: CommandData;
|
|
||||||
logout: CommandData;
|
|
||||||
switchToMobileVersion: CommandData;
|
|
||||||
switchToDesktopVersion: CommandData;
|
|
||||||
showRevisions: CommandData & {
|
|
||||||
noteId?: string | null;
|
|
||||||
};
|
|
||||||
showLlmChat: CommandData;
|
showLlmChat: CommandData;
|
||||||
createAiChat: CommandData;
|
createAiChat: CommandData;
|
||||||
showOptions: CommandData & {
|
showOptions: CommandData & {
|
||||||
@@ -138,9 +131,6 @@ export type CommandMappings = {
|
|||||||
hideLeftPane: CommandData;
|
hideLeftPane: CommandData;
|
||||||
showCpuArchWarning: CommandData;
|
showCpuArchWarning: CommandData;
|
||||||
showLeftPane: CommandData;
|
showLeftPane: CommandData;
|
||||||
showAttachments: CommandData;
|
|
||||||
showSearchHistory: CommandData;
|
|
||||||
showShareSubtree: CommandData;
|
|
||||||
hoistNote: CommandData & { noteId: string };
|
hoistNote: CommandData & { noteId: string };
|
||||||
leaveProtectedSession: CommandData;
|
leaveProtectedSession: CommandData;
|
||||||
enterProtectedSession: CommandData;
|
enterProtectedSession: CommandData;
|
||||||
@@ -181,7 +171,7 @@ export type CommandMappings = {
|
|||||||
deleteNotes: ContextMenuCommandData;
|
deleteNotes: ContextMenuCommandData;
|
||||||
importIntoNote: ContextMenuCommandData;
|
importIntoNote: ContextMenuCommandData;
|
||||||
exportNote: ContextMenuCommandData;
|
exportNote: ContextMenuCommandData;
|
||||||
searchInSubtree: CommandData & { notePath: string; };
|
searchInSubtree: ContextMenuCommandData;
|
||||||
moveNoteUp: ContextMenuCommandData;
|
moveNoteUp: ContextMenuCommandData;
|
||||||
moveNoteDown: ContextMenuCommandData;
|
moveNoteDown: ContextMenuCommandData;
|
||||||
moveNoteUpInHierarchy: ContextMenuCommandData;
|
moveNoteUpInHierarchy: ContextMenuCommandData;
|
||||||
@@ -270,74 +260,6 @@ export type CommandMappings = {
|
|||||||
closeThisNoteSplit: CommandData;
|
closeThisNoteSplit: CommandData;
|
||||||
moveThisNoteSplit: CommandData & { isMovingLeft: boolean };
|
moveThisNoteSplit: CommandData & { isMovingLeft: boolean };
|
||||||
jumpToNote: CommandData;
|
jumpToNote: CommandData;
|
||||||
commandPalette: CommandData;
|
|
||||||
|
|
||||||
// Keyboard shortcuts
|
|
||||||
backInNoteHistory: CommandData;
|
|
||||||
forwardInNoteHistory: CommandData;
|
|
||||||
forceSaveRevision: CommandData;
|
|
||||||
scrollToActiveNote: CommandData;
|
|
||||||
quickSearch: CommandData;
|
|
||||||
collapseTree: CommandData;
|
|
||||||
createNoteAfter: CommandData;
|
|
||||||
createNoteInto: CommandData;
|
|
||||||
addNoteAboveToSelection: CommandData;
|
|
||||||
addNoteBelowToSelection: CommandData;
|
|
||||||
openNewTab: CommandData;
|
|
||||||
activateNextTab: CommandData;
|
|
||||||
activatePreviousTab: CommandData;
|
|
||||||
openNewWindow: CommandData;
|
|
||||||
toggleTray: CommandData;
|
|
||||||
firstTab: CommandData;
|
|
||||||
secondTab: CommandData;
|
|
||||||
thirdTab: CommandData;
|
|
||||||
fourthTab: CommandData;
|
|
||||||
fifthTab: CommandData;
|
|
||||||
sixthTab: CommandData;
|
|
||||||
seventhTab: CommandData;
|
|
||||||
eigthTab: CommandData;
|
|
||||||
ninthTab: CommandData;
|
|
||||||
lastTab: CommandData;
|
|
||||||
showNoteSource: CommandData;
|
|
||||||
showSQLConsole: CommandData;
|
|
||||||
showBackendLog: CommandData;
|
|
||||||
showCheatsheet: CommandData;
|
|
||||||
showHelp: CommandData;
|
|
||||||
addLinkToText: CommandData;
|
|
||||||
followLinkUnderCursor: CommandData;
|
|
||||||
insertDateTimeToText: CommandData;
|
|
||||||
pasteMarkdownIntoText: CommandData;
|
|
||||||
cutIntoNote: CommandData;
|
|
||||||
addIncludeNoteToText: CommandData;
|
|
||||||
editReadOnlyNote: CommandData;
|
|
||||||
toggleRibbonTabClassicEditor: CommandData;
|
|
||||||
toggleRibbonTabBasicProperties: CommandData;
|
|
||||||
toggleRibbonTabBookProperties: CommandData;
|
|
||||||
toggleRibbonTabFileProperties: CommandData;
|
|
||||||
toggleRibbonTabImageProperties: CommandData;
|
|
||||||
toggleRibbonTabOwnedAttributes: CommandData;
|
|
||||||
toggleRibbonTabInheritedAttributes: CommandData;
|
|
||||||
toggleRibbonTabPromotedAttributes: CommandData;
|
|
||||||
toggleRibbonTabNoteMap: CommandData;
|
|
||||||
toggleRibbonTabNoteInfo: CommandData;
|
|
||||||
toggleRibbonTabNotePaths: CommandData;
|
|
||||||
toggleRibbonTabSimilarNotes: CommandData;
|
|
||||||
toggleRightPane: CommandData;
|
|
||||||
printActiveNote: CommandData;
|
|
||||||
exportAsPdf: CommandData;
|
|
||||||
openNoteExternally: CommandData;
|
|
||||||
openNoteCustom: CommandData;
|
|
||||||
renderActiveNote: CommandData;
|
|
||||||
unhoist: CommandData;
|
|
||||||
reloadFrontendApp: CommandData;
|
|
||||||
openDevTools: CommandData;
|
|
||||||
findInText: CommandData;
|
|
||||||
toggleLeftPane: CommandData;
|
|
||||||
toggleFullscreen: CommandData;
|
|
||||||
zoomOut: CommandData;
|
|
||||||
zoomIn: CommandData;
|
|
||||||
zoomReset: CommandData;
|
|
||||||
copyWithoutFormatting: CommandData;
|
|
||||||
|
|
||||||
// Geomap
|
// Geomap
|
||||||
deleteFromMap: { noteId: string };
|
deleteFromMap: { noteId: string };
|
||||||
@@ -354,30 +276,12 @@ export type CommandMappings = {
|
|||||||
|
|
||||||
geoMapCreateChildNote: CommandData;
|
geoMapCreateChildNote: CommandData;
|
||||||
|
|
||||||
// Table view
|
|
||||||
addNewRow: CommandData & {
|
|
||||||
customOpts: CreateNoteOpts;
|
|
||||||
parentNotePath?: string;
|
|
||||||
};
|
|
||||||
addNewTableColumn: CommandData & {
|
|
||||||
columnToEdit?: ColumnComponent;
|
|
||||||
referenceColumn?: ColumnComponent;
|
|
||||||
direction?: "before" | "after";
|
|
||||||
type?: "label" | "relation";
|
|
||||||
};
|
|
||||||
deleteTableColumn: CommandData & {
|
|
||||||
columnToDelete?: ColumnComponent;
|
|
||||||
};
|
|
||||||
|
|
||||||
buildTouchBar: CommandData & {
|
buildTouchBar: CommandData & {
|
||||||
TouchBar: typeof TouchBar;
|
TouchBar: typeof TouchBar;
|
||||||
buildIcon(name: string): NativeImage;
|
buildIcon(name: string): NativeImage;
|
||||||
};
|
};
|
||||||
refreshTouchBar: CommandData;
|
refreshTouchBar: CommandData;
|
||||||
reloadTextEditor: CommandData;
|
reloadTextEditor: CommandData;
|
||||||
chooseNoteType: CommandData & {
|
|
||||||
callback: ChooseNoteTypeCallback
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type EventMappings = {
|
type EventMappings = {
|
||||||
@@ -530,7 +434,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 +527,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", {});
|
||||||
}
|
}
|
||||||
@@ -653,17 +557,13 @@ export class AppContext extends Component {
|
|||||||
return $(el).closest(".component").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 +573,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 +584,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";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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> {}
|
||||||
|
|||||||
@@ -10,16 +10,38 @@ 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() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
if (jQuery.hotkeys) {
|
||||||
|
// hot keys are active also inside inputs and content editables
|
||||||
|
jQuery.hotkeys.options.filterInputAcceptingElements = false;
|
||||||
|
jQuery.hotkeys.options.filterContentEditable = false;
|
||||||
|
jQuery.hotkeys.options.filterTextInputs = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openDevToolsCommand() {
|
openDevToolsCommand() {
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
utils.dynamicRequire("@electron/remote").getCurrentWindow().webContents.toggleDevTools();
|
utils.dynamicRequire("@electron/remote").getCurrentWindow().toggleDevTools();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,9 +113,7 @@ export default class Entrypoints extends Component {
|
|||||||
if (win.isFullScreenable()) {
|
if (win.isFullScreenable()) {
|
||||||
win.setFullScreen(!win.isFullScreen());
|
win.setFullScreen(!win.isFullScreen());
|
||||||
}
|
}
|
||||||
} else {
|
} // outside of electron this is handled by the browser
|
||||||
document.documentElement.requestFullscreen();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadFrontendAppCommand() {
|
reloadFrontendAppCommand() {
|
||||||
@@ -109,7 +129,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 +141,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 {
|
||||||
|
|||||||
@@ -325,9 +325,8 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collections must always display a note list, even if no children.
|
// Some book types must always display a note list, even if no children.
|
||||||
const viewType = note.getLabelValue("viewType") ?? "grid";
|
if (["calendar", "table", "geoMap"].includes(note.getLabelValue("viewType") ?? "")) {
|
||||||
if (!["list", "grid"].includes(viewType)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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">) {
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ 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 "bootstrap/dist/css/bootstrap.min.css";
|
import "./stylesheets/bootstrap.scss";
|
||||||
import "boxicons/css/boxicons.min.css";
|
import "boxicons/css/boxicons.min.css";
|
||||||
|
import "jquery-hotkeys";
|
||||||
import "autocomplete.js/index_jquery.js";
|
import "autocomplete.js/index_jquery.js";
|
||||||
|
|
||||||
await appContext.earlyInit();
|
await appContext.earlyInit();
|
||||||
|
|||||||
@@ -64,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;
|
||||||
@@ -256,20 +256,6 @@ export default class FNote {
|
|||||||
return this.children;
|
return this.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSubtreeNoteIds() {
|
|
||||||
let noteIds: (string | string[])[] = [];
|
|
||||||
for (const child of await this.getChildNotes()) {
|
|
||||||
noteIds.push(child.noteId);
|
|
||||||
noteIds.push(await child.getSubtreeNoteIds());
|
|
||||||
}
|
|
||||||
return noteIds.flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSubtreeNotes() {
|
|
||||||
const noteIds = await this.getSubtreeNoteIds();
|
|
||||||
return this.froca.getNotes(noteIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getChildNotes() {
|
async getChildNotes() {
|
||||||
return await this.froca.getNotes(this.children);
|
return await this.froca.getNotes(this.children);
|
||||||
}
|
}
|
||||||
@@ -1020,14 +1006,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.
|
||||||
*/
|
*/
|
||||||
@@ -1035,3 +1013,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;
|
||||||
|
|||||||
@@ -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 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 NoteListWidget from "../widgets/note_list.js";
|
||||||
import NoteIconWidget from "../widgets/note_icon.jsx";
|
import SearchDefinitionWidget from "../widgets/ribbon_widgets/search_definition.js";
|
||||||
|
import SqlResultWidget from "../widgets/sql_result.js";
|
||||||
|
import SqlTableSchemasWidget from "../widgets/sql_table_schemas.js";
|
||||||
|
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||||
|
import ImagePropertiesWidget from "../widgets/ribbon_widgets/image_properties.js";
|
||||||
|
import NotePropertiesWidget from "../widgets/ribbon_widgets/note_properties.js";
|
||||||
|
import NoteIconWidget from "../widgets/note_icon.js";
|
||||||
|
import SearchResultWidget from "../widgets/search_result.js";
|
||||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
import 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";
|
|
||||||
|
|
||||||
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(new MovePaneButton(true))
|
.child(new MovePaneButton(true))
|
||||||
.child(new MovePaneButton(false))
|
.child(new MovePaneButton(false))
|
||||||
.child(new ClosePaneButton())
|
.child(new ClosePaneButton())
|
||||||
.child(new 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(new NoteListWidget(false))
|
.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");
|
||||||
@@ -24,48 +24,46 @@ 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 NoteListWidget from "../widgets/note_list.js";
|
import NoteListWidget from "../widgets/note_list.js";
|
||||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
|
||||||
import NoteTitleWidget from "../widgets/note_title.jsx";
|
|
||||||
import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js";
|
|
||||||
|
|
||||||
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(<PopupEditorFormattingToolbar />)
|
.child(new ClassicEditorToolbar())
|
||||||
.child(new PromotedAttributesWidget())
|
.child(new PromotedAttributesWidget())
|
||||||
.child(new NoteDetailWidget())
|
.child(new NoteDetailWidget())
|
||||||
.child(new NoteListWidget(true)))
|
.child(new NoteListWidget(true)))
|
||||||
.child(<CallToActionDialog />);
|
|
||||||
}
|
}
|
||||||
@@ -3,27 +3,29 @@ 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 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 { 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";
|
|
||||||
|
|
||||||
const MOBILE_CSS = `
|
const MOBILE_CSS = `
|
||||||
<style>
|
<style>
|
||||||
@@ -132,22 +134,28 @@ export default class MobileLayout {
|
|||||||
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
new ScreenContainer("detail", "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()
|
||||||
@@ -155,10 +163,9 @@ export default class MobileLayout {
|
|||||||
.contentSized()
|
.contentSized()
|
||||||
.child(new NoteDetailWidget())
|
.child(new NoteDetailWidget())
|
||||||
.child(new NoteListWidget(false))
|
.child(new NoteListWidget(false))
|
||||||
.child(<FilePropertiesWrapper />)
|
.child(new FilePropertiesWidget().css("font-size", "smaller"))
|
||||||
)
|
|
||||||
.child(<MobileEditorToolbar />)
|
|
||||||
)
|
)
|
||||||
|
.child(new MobileEditorToolbar())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -166,25 +173,9 @@ 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 />);
|
|
||||||
applyModals(rootContainer);
|
applyModals(rootContainer);
|
||||||
return rootContainer;
|
return rootContainer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilePropertiesWrapper() {
|
|
||||||
const { note } = useNoteContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{note?.type === "file" && <FilePropertiesTab note={note} />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
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
|
||||||
|
|||||||
@@ -26,11 +26,6 @@ export interface MenuCommandItem<T> {
|
|||||||
title: string;
|
title: string;
|
||||||
command?: T;
|
command?: T;
|
||||||
type?: string;
|
type?: string;
|
||||||
/**
|
|
||||||
* The icon to display in the menu item.
|
|
||||||
*
|
|
||||||
* If not set, no icon is displayed and the item will appear shifted slightly to the left if there are other items with icons. To avoid this, use `bx bx-empty`.
|
|
||||||
*/
|
|
||||||
uiIcon?: string;
|
uiIcon?: string;
|
||||||
badges?: MenuItemBadge[];
|
badges?: MenuItemBadge[];
|
||||||
templateNoteId?: string;
|
templateNoteId?: string;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ let lastTargetNode: HTMLElement | null = null;
|
|||||||
|
|
||||||
// This will include all commands that implement ContextMenuCommandData, but it will not work if it additional options are added via the `|` operator,
|
// This will include all commands that implement ContextMenuCommandData, but it will not work if it additional options are added via the `|` operator,
|
||||||
// so they need to be added manually.
|
// so they need to be added manually.
|
||||||
export type TreeCommandNames = FilteredCommandNames<ContextMenuCommandData> | "openBulkActionsDialog" | "searchInSubtree";
|
export type TreeCommandNames = FilteredCommandNames<ContextMenuCommandData> | "openBulkActionsDialog";
|
||||||
|
|
||||||
export default class TreeContextMenu implements SelectMenuItemEventListener<TreeCommandNames> {
|
export default class TreeContextMenu implements SelectMenuItemEventListener<TreeCommandNames> {
|
||||||
private treeWidget: NoteTreeWidget;
|
private treeWidget: NoteTreeWidget;
|
||||||
@@ -129,6 +129,12 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
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 },
|
||||||
|
{
|
||||||
|
title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`,
|
||||||
|
command: "duplicateSubtree",
|
||||||
|
uiIcon: "bx bx-outline",
|
||||||
|
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
||||||
|
},
|
||||||
|
|
||||||
{ title: "----" },
|
{ title: "----" },
|
||||||
|
|
||||||
@@ -182,13 +188,6 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
|
|||||||
|
|
||||||
{ 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.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
|
||||||
|
|
||||||
{
|
|
||||||
title: `${t("tree-context-menu.duplicate")} <kbd data-command="duplicateSubtree">`,
|
|
||||||
command: "duplicateSubtree",
|
|
||||||
uiIcon: "bx bx-outline",
|
|
||||||
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
|
title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
|
||||||
command: "deleteNotes",
|
command: "deleteNotes",
|
||||||
|
|||||||
@@ -1,7 +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 "bootstrap/dist/css/bootstrap.min.css";
|
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";
|
||||||
|
|
||||||
|
|||||||
@@ -79,19 +79,7 @@ async function renderAttributes(attributes: FAttribute[], renderIsInheritable: b
|
|||||||
return $container;
|
return $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HIDDEN_ATTRIBUTES = [
|
const HIDDEN_ATTRIBUTES = ["originalFileName", "fileSize", "template", "inherit", "cssClass", "iconClass", "pageSize", "viewType", "geolocation", "docName"];
|
||||||
"originalFileName",
|
|
||||||
"fileSize",
|
|
||||||
"template",
|
|
||||||
"inherit",
|
|
||||||
"cssClass",
|
|
||||||
"iconClass",
|
|
||||||
"pageSize",
|
|
||||||
"viewType",
|
|
||||||
"geolocation",
|
|
||||||
"docName",
|
|
||||||
"webViewSrc"
|
|
||||||
];
|
|
||||||
|
|
||||||
async function renderNormalAttributes(note: FNote) {
|
async function renderNormalAttributes(note: FNote) {
|
||||||
const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes();
|
const promotedDefinitionAttributes = note.getPromotedDefinitionAttributes();
|
||||||
|
|||||||
@@ -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`, {
|
||||||
@@ -13,12 +12,11 @@ async function addLabel(noteId: string, name: string, value: string = "", isInhe
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
|
export async function setLabel(noteId: string, name: string, value: string = "") {
|
||||||
await server.put(`notes/${noteId}/set-attribute`, {
|
await server.put(`notes/${noteId}/set-attribute`, {
|
||||||
type: "label",
|
type: "label",
|
||||||
name: name,
|
name: name,
|
||||||
value: value,
|
value: value
|
||||||
isInheritable
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,14 +24,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 +51,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 {
|
||||||
|
|||||||
@@ -95,15 +95,7 @@ async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false) {
|
||||||
* Shows the delete confirmation screen
|
|
||||||
*
|
|
||||||
* @param branchIdsToDelete the list of branch IDs to delete.
|
|
||||||
* @param forceDeleteAllClones whether to check by default the "Delete also all clones" checkbox.
|
|
||||||
* @param moveToParent whether to automatically go to the parent note path after a succesful delete. Usually makes sense if deleting the active note(s).
|
|
||||||
* @returns promise that returns false if the operation was cancelled or there was nothing to delete, true if the operation succeeded.
|
|
||||||
*/
|
|
||||||
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true) {
|
|
||||||
branchIdsToDelete = filterRootNote(branchIdsToDelete);
|
branchIdsToDelete = filterRootNote(branchIdsToDelete);
|
||||||
|
|
||||||
if (branchIdsToDelete.length === 0) {
|
if (branchIdsToDelete.length === 0) {
|
||||||
@@ -118,13 +110,11 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moveToParent) {
|
|
||||||
try {
|
try {
|
||||||
await activateParentNotePath();
|
await activateParentNotePath();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const taskId = utils.randomString(10);
|
const taskId = utils.randomString(10);
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,8 @@ import AddRelationBulkAction from "../widgets/bulk_actions/relation/add_relation
|
|||||||
import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js";
|
import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import toast from "./toast.js";
|
|
||||||
import { BulkAction } from "@triliumnext/commons";
|
|
||||||
|
|
||||||
export const ACTION_GROUPS = [
|
const ACTION_GROUPS = [
|
||||||
{
|
{
|
||||||
title: t("bulk_actions.labels"),
|
title: t("bulk_actions.labels"),
|
||||||
actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction]
|
actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction]
|
||||||
@@ -91,17 +89,6 @@ function parseActions(note: FNote) {
|
|||||||
.filter((action) => !!action);
|
.filter((action) => !!action);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function executeBulkActions(targetNoteIds: string[], actions: BulkAction[], includeDescendants = false) {
|
|
||||||
await server.post("bulk-action/execute", {
|
|
||||||
noteIds: targetNoteIds,
|
|
||||||
includeDescendants,
|
|
||||||
actions
|
|
||||||
});
|
|
||||||
|
|
||||||
await ws.waitForMaxKnownEntityChangeId();
|
|
||||||
toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
addAction,
|
addAction,
|
||||||
parseActions,
|
parseActions,
|
||||||
|
|||||||
@@ -1,295 +0,0 @@
|
|||||||
import { ActionKeyboardShortcut } from "@triliumnext/commons";
|
|
||||||
import appContext, { type CommandNames } from "../components/app_context.js";
|
|
||||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
|
||||||
import { t, translationsInitializedPromise } from "./i18n.js";
|
|
||||||
import keyboardActions from "./keyboard_actions.js";
|
|
||||||
import utils from "./utils.js";
|
|
||||||
|
|
||||||
export interface CommandDefinition {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
icon?: string;
|
|
||||||
shortcut?: string;
|
|
||||||
commandName?: CommandNames;
|
|
||||||
handler?: () => Promise<unknown> | null | undefined | void;
|
|
||||||
aliases?: string[];
|
|
||||||
source?: "manual" | "keyboard-action";
|
|
||||||
/** Reference to the original keyboard action for scope checking. */
|
|
||||||
keyboardAction?: ActionKeyboardShortcut;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CommandRegistry {
|
|
||||||
private commands: Map<string, CommandDefinition> = new Map();
|
|
||||||
private aliases: Map<string, string> = new Map();
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.loadCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadCommands() {
|
|
||||||
await translationsInitializedPromise;
|
|
||||||
this.registerDefaultCommands();
|
|
||||||
await this.loadKeyboardActionsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerDefaultCommands() {
|
|
||||||
this.register({
|
|
||||||
id: "export-note",
|
|
||||||
name: t("command_palette.export_note_title"),
|
|
||||||
description: t("command_palette.export_note_description"),
|
|
||||||
icon: "bx bx-export",
|
|
||||||
handler: () => {
|
|
||||||
const notePath = appContext.tabManager.getActiveContextNotePath();
|
|
||||||
if (notePath) {
|
|
||||||
appContext.triggerCommand("showExportDialog", {
|
|
||||||
notePath,
|
|
||||||
defaultType: "single"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.register({
|
|
||||||
id: "show-attachments",
|
|
||||||
name: t("command_palette.show_attachments_title"),
|
|
||||||
description: t("command_palette.show_attachments_description"),
|
|
||||||
icon: "bx bx-paperclip",
|
|
||||||
handler: () => appContext.triggerCommand("showAttachments")
|
|
||||||
});
|
|
||||||
|
|
||||||
// Special search commands with custom logic
|
|
||||||
this.register({
|
|
||||||
id: "search-notes",
|
|
||||||
name: t("command_palette.search_notes_title"),
|
|
||||||
description: t("command_palette.search_notes_description"),
|
|
||||||
icon: "bx bx-search",
|
|
||||||
handler: () => appContext.triggerCommand("searchNotes", {})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.register({
|
|
||||||
id: "search-in-subtree",
|
|
||||||
name: t("command_palette.search_subtree_title"),
|
|
||||||
description: t("command_palette.search_subtree_description"),
|
|
||||||
icon: "bx bx-search-alt",
|
|
||||||
handler: () => {
|
|
||||||
const notePath = appContext.tabManager.getActiveContextNotePath();
|
|
||||||
if (notePath) {
|
|
||||||
appContext.triggerCommand("searchInSubtree", { notePath });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.register({
|
|
||||||
id: "show-search-history",
|
|
||||||
name: t("command_palette.search_history_title"),
|
|
||||||
description: t("command_palette.search_history_description"),
|
|
||||||
icon: "bx bx-history",
|
|
||||||
handler: () => appContext.triggerCommand("showSearchHistory")
|
|
||||||
});
|
|
||||||
|
|
||||||
this.register({
|
|
||||||
id: "show-launch-bar",
|
|
||||||
name: t("command_palette.configure_launch_bar_title"),
|
|
||||||
description: t("command_palette.configure_launch_bar_description"),
|
|
||||||
icon: "bx bx-sidebar",
|
|
||||||
handler: () => appContext.triggerCommand("showLaunchBarSubtree")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadKeyboardActionsAsync() {
|
|
||||||
try {
|
|
||||||
const actions = await keyboardActions.getActions();
|
|
||||||
this.registerKeyboardActions(actions);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load keyboard actions:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerKeyboardActions(actions: ActionKeyboardShortcut[]) {
|
|
||||||
for (const action of actions) {
|
|
||||||
// Skip actions that we've already manually registered
|
|
||||||
if (this.commands.has(action.actionName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip actions that don't have a description (likely separators)
|
|
||||||
if (!action.description) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip Electron-only actions if not in Electron environment
|
|
||||||
if (action.isElectronOnly && !utils.isElectron()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip actions that should not appear in the command palette
|
|
||||||
if (action.ignoreFromCommandPalette) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the primary shortcut (first one in the list)
|
|
||||||
const primaryShortcut = action.effectiveShortcuts?.[0];
|
|
||||||
|
|
||||||
let name = action.friendlyName;
|
|
||||||
if (action.scope === "note-tree") {
|
|
||||||
name = t("command_palette.tree-action-name", { name: action.friendlyName });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a command definition from the keyboard action
|
|
||||||
const commandDef: CommandDefinition = {
|
|
||||||
id: action.actionName,
|
|
||||||
name,
|
|
||||||
description: action.description,
|
|
||||||
icon: action.iconClass,
|
|
||||||
shortcut: primaryShortcut ? this.formatShortcut(primaryShortcut) : undefined,
|
|
||||||
commandName: action.actionName as CommandNames,
|
|
||||||
source: "keyboard-action",
|
|
||||||
keyboardAction: action
|
|
||||||
};
|
|
||||||
|
|
||||||
this.register(commandDef);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatShortcut(shortcut: string): string {
|
|
||||||
// Convert electron accelerator format to display format
|
|
||||||
return shortcut
|
|
||||||
.replace(/CommandOrControl/g, 'Ctrl')
|
|
||||||
.replace(/\+/g, ' + ');
|
|
||||||
}
|
|
||||||
|
|
||||||
register(command: CommandDefinition) {
|
|
||||||
this.commands.set(command.id, command);
|
|
||||||
|
|
||||||
// Register aliases
|
|
||||||
if (command.aliases) {
|
|
||||||
for (const alias of command.aliases) {
|
|
||||||
this.aliases.set(alias.toLowerCase(), command.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getCommand(id: string): CommandDefinition | undefined {
|
|
||||||
return this.commands.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllCommands(): CommandDefinition[] {
|
|
||||||
const commands = Array.from(this.commands.values());
|
|
||||||
|
|
||||||
// Sort commands by name
|
|
||||||
commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
|
|
||||||
return commands;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchCommands(query: string): CommandDefinition[] {
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
const results: { command: CommandDefinition; score: number }[] = [];
|
|
||||||
|
|
||||||
for (const command of this.commands.values()) {
|
|
||||||
let score = 0;
|
|
||||||
|
|
||||||
// Exact match on name
|
|
||||||
if (command.name.toLowerCase() === normalizedQuery) {
|
|
||||||
score = 100;
|
|
||||||
}
|
|
||||||
// Name starts with query
|
|
||||||
else if (command.name.toLowerCase().startsWith(normalizedQuery)) {
|
|
||||||
score = 80;
|
|
||||||
}
|
|
||||||
// Name contains query
|
|
||||||
else if (command.name.toLowerCase().includes(normalizedQuery)) {
|
|
||||||
score = 60;
|
|
||||||
}
|
|
||||||
// Description contains query
|
|
||||||
else if (command.description?.toLowerCase().includes(normalizedQuery)) {
|
|
||||||
score = 40;
|
|
||||||
}
|
|
||||||
// Check aliases
|
|
||||||
else if (command.aliases?.some(alias => alias.toLowerCase().includes(normalizedQuery))) {
|
|
||||||
score = 50;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (score > 0) {
|
|
||||||
results.push({ command, score });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by score (highest first) and then by name
|
|
||||||
results.sort((a, b) => {
|
|
||||||
if (a.score !== b.score) {
|
|
||||||
return b.score - a.score;
|
|
||||||
}
|
|
||||||
return a.command.name.localeCompare(b.command.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
return results.map(r => r.command);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeCommand(commandId: string) {
|
|
||||||
const command = this.getCommand(commandId);
|
|
||||||
if (!command) {
|
|
||||||
console.error(`Command not found: ${commandId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute custom handler if provided
|
|
||||||
if (command.handler) {
|
|
||||||
await command.handler();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle keyboard action with scope-aware execution
|
|
||||||
if (command.keyboardAction && command.commandName) {
|
|
||||||
if (command.keyboardAction.scope === "note-tree") {
|
|
||||||
this.executeWithNoteTreeFocus(command.commandName);
|
|
||||||
} else if (command.keyboardAction.scope === "text-detail") {
|
|
||||||
this.executeWithTextDetail(command.commandName);
|
|
||||||
} else {
|
|
||||||
appContext.triggerCommand(command.commandName, {
|
|
||||||
ntxId: appContext.tabManager.activeNtxId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for commands without keyboard action reference
|
|
||||||
if (command.commandName) {
|
|
||||||
appContext.triggerCommand(command.commandName, {
|
|
||||||
ntxId: appContext.tabManager.activeNtxId
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`Command ${commandId} has no handler or commandName`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private executeWithNoteTreeFocus(actionName: CommandNames) {
|
|
||||||
const tree = document.querySelector(".tree-wrapper") as HTMLElement;
|
|
||||||
if (!tree) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const treeComponent = appContext.getComponentByEl(tree) as NoteTreeWidget;
|
|
||||||
const activeNode = treeComponent.getActiveNode();
|
|
||||||
treeComponent.triggerCommand(actionName, {
|
|
||||||
ntxId: appContext.tabManager.activeNtxId,
|
|
||||||
node: activeNode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeWithTextDetail(actionName: CommandNames) {
|
|
||||||
const typeWidget = await appContext.tabManager.getActiveContext()?.getTypeWidget();
|
|
||||||
if (!typeWidget) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
typeWidget.triggerCommand(actionName, {
|
|
||||||
ntxId: appContext.tabManager.activeNtxId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandRegistry = new CommandRegistry();
|
|
||||||
export default commandRegistry;
|
|
||||||
@@ -65,9 +65,6 @@ async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FA
|
|||||||
|
|
||||||
$renderedContent.append($("<div>").append("<div>This note is protected and to access it you need to enter password.</div>").append("<br/>").append($button));
|
$renderedContent.append($("<div>").append("<div>This note is protected and to access it you need to enter password.</div>").append("<br/>").append($button));
|
||||||
} else if (entity instanceof FNote) {
|
} else if (entity instanceof FNote) {
|
||||||
$renderedContent
|
|
||||||
.css("display", "flex")
|
|
||||||
.css("flex-direction", "column");
|
|
||||||
$renderedContent.append(
|
$renderedContent.append(
|
||||||
$("<div>")
|
$("<div>")
|
||||||
.css("display", "flex")
|
.css("display", "flex")
|
||||||
@@ -75,33 +72,8 @@ async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FA
|
|||||||
.css("align-items", "center")
|
.css("align-items", "center")
|
||||||
.css("height", "100%")
|
.css("height", "100%")
|
||||||
.css("font-size", "500%")
|
.css("font-size", "500%")
|
||||||
.css("flex-grow", "1")
|
|
||||||
.append($("<span>").addClass(entity.getIcon()))
|
.append($("<span>").addClass(entity.getIcon()))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (entity.type === "webView" && entity.hasLabel("webViewSrc")) {
|
|
||||||
const $footer = $("<footer>")
|
|
||||||
.addClass("webview-footer");
|
|
||||||
const $openButton = $(`
|
|
||||||
<button class="file-open btn btn-primary" type="button">
|
|
||||||
<span class="bx bx-link-external"></span>
|
|
||||||
${t("content_renderer.open_externally")}
|
|
||||||
</button>
|
|
||||||
`)
|
|
||||||
.appendTo($footer)
|
|
||||||
.on("click", () => {
|
|
||||||
const webViewSrc = entity.getLabelValue("webViewSrc");
|
|
||||||
if (webViewSrc) {
|
|
||||||
if (utils.isElectron()) {
|
|
||||||
const electron = utils.dynamicRequire("electron");
|
|
||||||
electron.shell.openExternal(webViewSrc);
|
|
||||||
} else {
|
|
||||||
window.open(webViewSrc, '_blank', 'noopener,noreferrer');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$footer.appendTo($renderedContent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity instanceof FNote) {
|
if (entity instanceof FNote) {
|
||||||
|
|||||||
@@ -41,14 +41,8 @@ async function info(message: string) {
|
|||||||
return new Promise((res) => appContext.triggerCommand("showInfoDialog", { message, callback: res }));
|
return new Promise((res) => appContext.triggerCommand("showInfoDialog", { message, callback: res }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a confirmation dialog with the given message.
|
|
||||||
*
|
|
||||||
* @param message the message to display in the dialog.
|
|
||||||
* @returns A promise that resolves to true if the user confirmed, false otherwise.
|
|
||||||
*/
|
|
||||||
async function confirm(message: string) {
|
async function confirm(message: string) {
|
||||||
return new Promise<boolean>((res) =>
|
return new Promise((res) =>
|
||||||
appContext.triggerCommand("showConfirmDialog", <ConfirmWithMessageOptions>{
|
appContext.triggerCommand("showConfirmDialog", <ConfirmWithMessageOptions>{
|
||||||
message,
|
message,
|
||||||
callback: (x: false | ConfirmDialogOptions) => res(x && x.confirmed)
|
callback: (x: false | ConfirmDialogOptions) => res(x && x.confirmed)
|
||||||
|
|||||||
@@ -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`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,8 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
|
|||||||
loadResults.addOption(attributeEntity.name);
|
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" || ec.entityName === "etapi_tokens") {
|
||||||
// NOOP - these entities are handled at the backend level and don't require frontend processing
|
// NOOP - these entities are handled at the backend level and don't require frontend processing
|
||||||
} else if (ec.entityName === "etapi_tokens") {
|
|
||||||
loadResults.hasEtapiTokenChanges = true;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown entityName '${ec.entityName}'`);
|
throw new Error(`Unknown entityName '${ec.entityName}'`);
|
||||||
}
|
}
|
||||||
@@ -79,7 +77,9 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
|
|||||||
noteAttributeCache.invalidate();
|
noteAttributeCache.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
const appContext = (await import("../components/app_context.js")).default;
|
// TODO: Remove after porting the file
|
||||||
|
// @ts-ignore
|
||||||
|
const appContext = (await import("../components/app_context.js")).default as any;
|
||||||
await appContext.triggerEvent("entitiesReloaded", { loadResults });
|
await appContext.triggerEvent("entitiesReloaded", { loadResults });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,14 @@ import i18next from "i18next";
|
|||||||
import i18nextHttpBackend from "i18next-http-backend";
|
import i18nextHttpBackend from "i18next-http-backend";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import type { Locale } from "@triliumnext/commons";
|
import type { Locale } from "@triliumnext/commons";
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
|
|
||||||
let locales: Locale[] | null;
|
let locales: Locale[] | null;
|
||||||
|
|
||||||
/**
|
|
||||||
* A deferred promise that resolves when translations are initialized.
|
|
||||||
*/
|
|
||||||
export let translationsInitializedPromise = $.Deferred();
|
|
||||||
|
|
||||||
export async function initLocale() {
|
export async function initLocale() {
|
||||||
const locale = (options.get("locale") as string) || "en";
|
const locale = (options.get("locale") as string) || "en";
|
||||||
|
|
||||||
locales = await server.get<Locale[]>("options/locales");
|
locales = await server.get<Locale[]>("options/locales");
|
||||||
|
|
||||||
i18next.use(initReactI18next);
|
|
||||||
await i18next.use(i18nextHttpBackend).init({
|
await i18next.use(i18nextHttpBackend).init({
|
||||||
lng: locale,
|
lng: locale,
|
||||||
fallbackLng: "en",
|
fallbackLng: "en",
|
||||||
@@ -26,8 +19,6 @@ export async function initLocale() {
|
|||||||
},
|
},
|
||||||
returnEmptyString: false
|
returnEmptyString: false
|
||||||
});
|
});
|
||||||
|
|
||||||
translationsInitializedPromise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAvailableLocales() {
|
export function getAvailableLocales() {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { NoteType } from "@triliumnext/commons";
|
|
||||||
import { ViewTypeOptions } from "./note_list_renderer";
|
|
||||||
import FNote from "../entities/fnote";
|
|
||||||
|
|
||||||
export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
|
||||||
canvas: null,
|
|
||||||
code: null,
|
|
||||||
contentWidget: null,
|
|
||||||
doc: null,
|
|
||||||
file: null,
|
|
||||||
image: null,
|
|
||||||
launcher: null,
|
|
||||||
mermaid: null,
|
|
||||||
mindMap: null,
|
|
||||||
noteMap: null,
|
|
||||||
relationMap: null,
|
|
||||||
render: null,
|
|
||||||
search: null,
|
|
||||||
text: null,
|
|
||||||
webView: null,
|
|
||||||
aiChat: null
|
|
||||||
};
|
|
||||||
|
|
||||||
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
|
||||||
list: "mULW0Q3VojwY",
|
|
||||||
grid: "8QqnMzx393bx",
|
|
||||||
calendar: "xWbu3jpNWapp",
|
|
||||||
table: "2FvYrpmOXm29",
|
|
||||||
geoMap: "81SGnPGMk7Xc",
|
|
||||||
board: "CtBQqbwXDx1w"
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getHelpUrlForNote(note: FNote | null | undefined) {
|
|
||||||
if (note && note.type !== "book" && byNoteType[note.type]) {
|
|
||||||
return byNoteType[note.type];
|
|
||||||
} else if (note?.hasLabel("calendarRoot")) {
|
|
||||||
return "l0tKav7yLHGF";
|
|
||||||
} else if (note?.hasLabel("textSnippet")) {
|
|
||||||
return "pwc194wlRzcH";
|
|
||||||
} else if (note && note.type === "book") {
|
|
||||||
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,15 +2,21 @@ import server from "./server.js";
|
|||||||
import appContext, { type CommandNames } from "../components/app_context.js";
|
import appContext, { type CommandNames } from "../components/app_context.js";
|
||||||
import shortcutService from "./shortcuts.js";
|
import shortcutService from "./shortcuts.js";
|
||||||
import type Component from "../components/component.js";
|
import type Component from "../components/component.js";
|
||||||
import type { ActionKeyboardShortcut } from "@triliumnext/commons";
|
|
||||||
|
|
||||||
const keyboardActionRepo: Record<string, ActionKeyboardShortcut> = {};
|
const keyboardActionRepo: Record<string, Action> = {};
|
||||||
|
|
||||||
const keyboardActionsLoaded = server.get<ActionKeyboardShortcut[]>("keyboard-actions").then((actions) => {
|
// TODO: Deduplicate with server.
|
||||||
|
export interface Action {
|
||||||
|
actionName: CommandNames;
|
||||||
|
effectiveShortcuts: string[];
|
||||||
|
scope: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyboardActionsLoaded = server.get<Action[]>("keyboard-actions").then((actions) => {
|
||||||
actions = actions.filter((a) => !!a.actionName); // filter out separators
|
actions = actions.filter((a) => !!a.actionName); // filter out separators
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
action.effectiveShortcuts = (action.effectiveShortcuts ?? []).filter((shortcut) => !shortcut.startsWith("global:"));
|
action.effectiveShortcuts = action.effectiveShortcuts.filter((shortcut) => !shortcut.startsWith("global:"));
|
||||||
|
|
||||||
keyboardActionRepo[action.actionName] = action;
|
keyboardActionRepo[action.actionName] = action;
|
||||||
}
|
}
|
||||||
@@ -32,7 +38,7 @@ async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, c
|
|||||||
const actions = await getActionsForScope(scope);
|
const actions = await getActionsForScope(scope);
|
||||||
|
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
for (const shortcut of action.effectiveShortcuts ?? []) {
|
for (const shortcut of action.effectiveShortcuts) {
|
||||||
shortcutService.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, { ntxId: appContext.tabManager.activeNtxId }));
|
shortcutService.bindElShortcut($el, shortcut, () => component.triggerCommand(action.actionName, { ntxId: appContext.tabManager.activeNtxId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +46,7 @@ async function setupActionsForElement(scope: string, $el: JQuery<HTMLElement>, c
|
|||||||
|
|
||||||
getActionsForScope("window").then((actions) => {
|
getActionsForScope("window").then((actions) => {
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
for (const shortcut of action.effectiveShortcuts ?? []) {
|
for (const shortcut of action.effectiveShortcuts) {
|
||||||
shortcutService.bindGlobalShortcut(shortcut, () => appContext.triggerCommand(action.actionName, { ntxId: appContext.tabManager.activeNtxId }));
|
shortcutService.bindGlobalShortcut(shortcut, () => appContext.triggerCommand(action.actionName, { ntxId: appContext.tabManager.activeNtxId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,10 +68,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.
|
||||||
@@ -78,7 +80,7 @@ function updateDisplayedShortcuts($container: JQuery<HTMLElement>) {
|
|||||||
const action = await getAction(actionName, true);
|
const action = await getAction(actionName, true);
|
||||||
|
|
||||||
if (action) {
|
if (action) {
|
||||||
const keyboardActions = (action.effectiveShortcuts ?? []).join(", ");
|
const keyboardActions = action.effectiveShortcuts.join(", ");
|
||||||
|
|
||||||
if (keyboardActions || $(el).text() !== "not set") {
|
if (keyboardActions || $(el).text() !== "not set") {
|
||||||
$(el).text(keyboardActions);
|
$(el).text(keyboardActions);
|
||||||
@@ -97,7 +99,7 @@ function updateDisplayedShortcuts($container: JQuery<HTMLElement>) {
|
|||||||
|
|
||||||
if (action) {
|
if (action) {
|
||||||
const title = $(el).attr("title");
|
const title = $(el).attr("title");
|
||||||
const shortcuts = (action.effectiveShortcuts ?? []).join(", ");
|
const shortcuts = action.effectiveShortcuts.join(", ");
|
||||||
|
|
||||||
if (title?.includes(shortcuts)) {
|
if (title?.includes(shortcuts)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
|||||||
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
||||||
|
|
||||||
if (notePath) {
|
if (notePath) {
|
||||||
if (isLeftClick && openInPopup) {
|
if (openInPopup) {
|
||||||
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
|
||||||
} else if (openInNewWindow) {
|
} else if (openInNewWindow) {
|
||||||
appContext.triggerCommand("openInWindow", { notePath, viewScope });
|
appContext.triggerCommand("openInWindow", { notePath, viewScope });
|
||||||
@@ -405,7 +405,7 @@ function linkContextMenu(e: PointerEvent) {
|
|||||||
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
|
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
|
export async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | null | undefined = null) {
|
||||||
const $link = $el[0].tagName === "A" ? $el : $el.find("a");
|
const $link = $el[0].tagName === "A" ? $el : $el.find("a");
|
||||||
|
|
||||||
href = href || $link.attr("href");
|
href = href || $link.attr("href");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { AttachmentRow, EtapiTokenRow } from "@triliumnext/commons";
|
import type { AttachmentRow } 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";
|
||||||
|
|
||||||
@@ -53,7 +53,6 @@ type EntityRowMappings = {
|
|||||||
options: OptionRow;
|
options: OptionRow;
|
||||||
revisions: RevisionRow;
|
revisions: RevisionRow;
|
||||||
note_reordering: NoteReorderingRow;
|
note_reordering: NoteReorderingRow;
|
||||||
etapi_tokens: EtapiTokenRow;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EntityRowNames = keyof EntityRowMappings;
|
export type EntityRowNames = keyof EntityRowMappings;
|
||||||
@@ -69,7 +68,6 @@ export default class LoadResults {
|
|||||||
private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[];
|
private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[];
|
||||||
private optionNames: string[];
|
private optionNames: string[];
|
||||||
private attachmentRows: AttachmentRow[];
|
private attachmentRows: AttachmentRow[];
|
||||||
public hasEtapiTokenChanges: boolean = false;
|
|
||||||
|
|
||||||
constructor(entityChanges: EntityChange[]) {
|
constructor(entityChanges: EntityChange[]) {
|
||||||
const entities: Record<string, Record<string, any>> = {};
|
const entities: Record<string, Record<string, any>> = {};
|
||||||
@@ -217,8 +215,7 @@ export default class LoadResults {
|
|||||||
this.revisionRows.length === 0 &&
|
this.revisionRows.length === 0 &&
|
||||||
this.contentNoteIdToComponentId.length === 0 &&
|
this.contentNoteIdToComponentId.length === 0 &&
|
||||||
this.optionNames.length === 0 &&
|
this.optionNames.length === 0 &&
|
||||||
this.attachmentRows.length === 0 &&
|
this.attachmentRows.length === 0
|
||||||
!this.hasEtapiTokenChanges
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import appContext from "../components/app_context.js";
|
|||||||
import noteCreateService from "./note_create.js";
|
import noteCreateService from "./note_create.js";
|
||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
import commandRegistry from "./command_registry.js";
|
|
||||||
import type { MentionFeedObjectItem } from "@triliumnext/ckeditor5";
|
import type { MentionFeedObjectItem } from "@triliumnext/ckeditor5";
|
||||||
|
|
||||||
// this key needs to have this value, so it's hit by the tooltip
|
// this key needs to have this value, so it's hit by the tooltip
|
||||||
@@ -30,28 +29,18 @@ export interface Suggestion {
|
|||||||
notePathTitle?: string;
|
notePathTitle?: string;
|
||||||
notePath?: string;
|
notePath?: string;
|
||||||
highlightedNotePathTitle?: string;
|
highlightedNotePathTitle?: string;
|
||||||
action?: string | "create-note" | "search-notes" | "external-link" | "command";
|
action?: string | "create-note" | "search-notes" | "external-link";
|
||||||
parentNoteId?: string;
|
parentNoteId?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
commandId?: string;
|
|
||||||
commandDescription?: string;
|
|
||||||
commandShortcut?: string;
|
|
||||||
attributeSnippet?: string;
|
|
||||||
highlightedAttributeSnippet?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Options {
|
interface Options {
|
||||||
container?: HTMLElement | null;
|
container?: HTMLElement;
|
||||||
fastSearch?: boolean;
|
fastSearch?: boolean;
|
||||||
allowCreatingNotes?: boolean;
|
allowCreatingNotes?: boolean;
|
||||||
allowJumpToSearchNotes?: boolean;
|
allowJumpToSearchNotes?: boolean;
|
||||||
allowExternalLinks?: boolean;
|
allowExternalLinks?: boolean;
|
||||||
/** If set, hides the right-side button corresponding to go to selected note. */
|
|
||||||
hideGoToSelectedNoteButton?: boolean;
|
hideGoToSelectedNoteButton?: boolean;
|
||||||
/** If set, hides all right-side buttons in the autocomplete dropdown */
|
|
||||||
hideAllButtons?: boolean;
|
|
||||||
/** If set, enables command palette mode */
|
|
||||||
isCommandPalette?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autocompleteSourceForCKEditor(queryText: string) {
|
async function autocompleteSourceForCKEditor(queryText: string) {
|
||||||
@@ -81,31 +70,6 @@ async function autocompleteSourceForCKEditor(queryText: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void, options: Options = {}) {
|
async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void, options: Options = {}) {
|
||||||
// Check if we're in command mode
|
|
||||||
if (options.isCommandPalette && term.startsWith(">")) {
|
|
||||||
const commandQuery = term.substring(1).trim();
|
|
||||||
|
|
||||||
// Get commands (all if no query, filtered if query provided)
|
|
||||||
const commands = commandQuery.length === 0
|
|
||||||
? commandRegistry.getAllCommands()
|
|
||||||
: commandRegistry.searchCommands(commandQuery);
|
|
||||||
|
|
||||||
// Convert commands to suggestions
|
|
||||||
const commandSuggestions: Suggestion[] = commands.map(cmd => ({
|
|
||||||
action: "command",
|
|
||||||
commandId: cmd.id,
|
|
||||||
noteTitle: cmd.name,
|
|
||||||
notePathTitle: `>${cmd.name}`,
|
|
||||||
highlightedNotePathTitle: cmd.name,
|
|
||||||
commandDescription: cmd.description,
|
|
||||||
commandShortcut: cmd.shortcut,
|
|
||||||
icon: cmd.icon
|
|
||||||
}));
|
|
||||||
|
|
||||||
cb(commandSuggestions);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fastSearch = options.fastSearch === false ? false : true;
|
const fastSearch = options.fastSearch === false ? false : true;
|
||||||
if (fastSearch === false) {
|
if (fastSearch === false) {
|
||||||
if (term.trim().length === 0) {
|
if (term.trim().length === 0) {
|
||||||
@@ -179,12 +143,6 @@ function showRecentNotes($el: JQuery<HTMLElement>) {
|
|||||||
$el.trigger("focus");
|
$el.trigger("focus");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAllCommands($el: JQuery<HTMLElement>) {
|
|
||||||
searchDelay = 0;
|
|
||||||
$el.setSelectedNotePath("");
|
|
||||||
$el.autocomplete("val", ">").autocomplete("open");
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullTextSearch($el: JQuery<HTMLElement>, options: Options) {
|
function fullTextSearch($el: JQuery<HTMLElement>, options: Options) {
|
||||||
const searchString = $el.autocomplete("val") as unknown as string;
|
const searchString = $el.autocomplete("val") as unknown as string;
|
||||||
if (options.fastSearch === false || searchString?.trim().length === 0) {
|
if (options.fastSearch === false || searchString?.trim().length === 0) {
|
||||||
@@ -232,11 +190,9 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
|||||||
|
|
||||||
const $goToSelectedNoteButton = $("<a>").addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
const $goToSelectedNoteButton = $("<a>").addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
||||||
|
|
||||||
if (!options.hideAllButtons) {
|
|
||||||
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
|
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.hideGoToSelectedNoteButton && !options.hideAllButtons) {
|
if (!options.hideGoToSelectedNoteButton) {
|
||||||
$el.after($goToSelectedNoteButton);
|
$el.after($goToSelectedNoteButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,50 +265,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
|||||||
},
|
},
|
||||||
displayKey: "notePathTitle",
|
displayKey: "notePathTitle",
|
||||||
templates: {
|
templates: {
|
||||||
suggestion: (suggestion) => {
|
suggestion: (suggestion) => `<span class="${suggestion.icon ?? "bx bx-note"}"></span> ${suggestion.highlightedNotePathTitle}`
|
||||||
if (suggestion.action === "command") {
|
|
||||||
let html = `<div class="command-suggestion">`;
|
|
||||||
html += `<span class="command-icon ${suggestion.icon || "bx bx-terminal"}"></span>`;
|
|
||||||
html += `<div class="command-content">`;
|
|
||||||
html += `<div class="command-name">${suggestion.highlightedNotePathTitle}</div>`;
|
|
||||||
if (suggestion.commandDescription) {
|
|
||||||
html += `<div class="command-description">${suggestion.commandDescription}</div>`;
|
|
||||||
}
|
|
||||||
html += `</div>`;
|
|
||||||
if (suggestion.commandShortcut) {
|
|
||||||
html += `<kbd class="command-shortcut">${suggestion.commandShortcut}</kbd>`;
|
|
||||||
}
|
|
||||||
html += '</div>';
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
// Add special class for search-notes action
|
|
||||||
const actionClass = suggestion.action === "search-notes" ? "search-notes-action" : "";
|
|
||||||
|
|
||||||
// Choose appropriate icon based on action
|
|
||||||
let iconClass = suggestion.icon ?? "bx bx-note";
|
|
||||||
if (suggestion.action === "search-notes") {
|
|
||||||
iconClass = "bx bx-search";
|
|
||||||
} else if (suggestion.action === "create-note") {
|
|
||||||
iconClass = "bx bx-plus";
|
|
||||||
} else if (suggestion.action === "external-link") {
|
|
||||||
iconClass = "bx bx-link-external";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simplified HTML structure without nested divs
|
|
||||||
let html = `<div class="note-suggestion ${actionClass}">`;
|
|
||||||
html += `<span class="icon ${iconClass}"></span>`;
|
|
||||||
html += `<span class="text">`;
|
|
||||||
html += `<span class="search-result-title">${suggestion.highlightedNotePathTitle}</span>`;
|
|
||||||
|
|
||||||
// Add attribute snippet inline if available
|
|
||||||
if (suggestion.highlightedAttributeSnippet) {
|
|
||||||
html += `<span class="search-result-attributes">${suggestion.highlightedAttributeSnippet}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `</span>`;
|
|
||||||
html += `</div>`;
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// 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
|
||||||
cache: false
|
cache: false
|
||||||
@@ -362,12 +275,6 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
|||||||
|
|
||||||
// TODO: Types fail due to "autocomplete:selected" not being registered in type definitions.
|
// TODO: Types fail due to "autocomplete:selected" not being registered in type definitions.
|
||||||
($el as any).on("autocomplete:selected", async (event: Event, suggestion: Suggestion) => {
|
($el as any).on("autocomplete:selected", async (event: Event, suggestion: Suggestion) => {
|
||||||
if (suggestion.action === "command") {
|
|
||||||
$el.autocomplete("close");
|
|
||||||
$el.trigger("autocomplete:commandselected", [suggestion]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suggestion.action === "external-link") {
|
if (suggestion.action === "external-link") {
|
||||||
$el.setSelectedNotePath(null);
|
$el.setSelectedNotePath(null);
|
||||||
$el.setSelectedExternalLink(suggestion.externalLink);
|
$el.setSelectedExternalLink(suggestion.externalLink);
|
||||||
@@ -480,26 +387,10 @@ function init() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience function which triggers the display of recent notes in the autocomplete input and focuses it.
|
|
||||||
*
|
|
||||||
* @param inputElement - The input element to trigger recent notes on.
|
|
||||||
*/
|
|
||||||
export function triggerRecentNotes(inputElement: HTMLInputElement | null | undefined) {
|
|
||||||
if (!inputElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $el = $(inputElement);
|
|
||||||
showRecentNotes($el);
|
|
||||||
$el.trigger("focus").trigger("select");
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
autocompleteSourceForCKEditor,
|
autocompleteSourceForCKEditor,
|
||||||
initNoteAutocomplete,
|
initNoteAutocomplete,
|
||||||
showRecentNotes,
|
showRecentNotes,
|
||||||
showAllCommands,
|
|
||||||
setText,
|
setText,
|
||||||
init
|
init
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type FBranch from "../entities/fbranch.js";
|
|||||||
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
|
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
|
||||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||||
|
|
||||||
export interface CreateNoteOpts {
|
interface CreateNoteOpts {
|
||||||
isProtected?: boolean;
|
isProtected?: boolean;
|
||||||
saveSelection?: boolean;
|
saveSelection?: boolean;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
@@ -109,6 +109,8 @@ async function createNote(parentNotePath: string | undefined, options: CreateNot
|
|||||||
|
|
||||||
async function chooseNoteType() {
|
async function chooseNoteType() {
|
||||||
return new Promise<ChooseNoteTypeResponse>((res) => {
|
return new Promise<ChooseNoteTypeResponse>((res) => {
|
||||||
|
// TODO: Remove ignore after callback for chooseNoteType is defined in app_context.ts
|
||||||
|
//@ts-ignore
|
||||||
appContext.triggerCommand("chooseNoteType", { callback: res });
|
appContext.triggerCommand("chooseNoteType", { callback: res });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type FNote from "../entities/fnote.js";
|
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 CalendarView from "../widgets/view_widgets/calendar_view.js";
|
||||||
import GeoView from "../widgets/view_widgets/geo_view/index.js";
|
import GeoView from "../widgets/view_widgets/geo_view/index.js";
|
||||||
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
|
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
|
||||||
@@ -7,25 +6,39 @@ import TableView from "../widgets/view_widgets/table_view/index.js";
|
|||||||
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
|
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
|
||||||
import type ViewMode 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 ViewTypeOptions = "list" | "grid" | "calendar" | "table" | "geoMap";
|
||||||
export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">;
|
|
||||||
export type ViewTypeOptions = typeof allViewTypes[number];
|
|
||||||
|
|
||||||
export default class NoteListRenderer {
|
export default class NoteListRenderer {
|
||||||
|
|
||||||
private viewType: ViewTypeOptions;
|
private viewType: ViewTypeOptions;
|
||||||
private args: ArgsWithoutNoteId;
|
public viewMode: ViewMode<any> | null;
|
||||||
public viewMode?: ViewMode<any>;
|
|
||||||
|
|
||||||
constructor(args: ArgsWithoutNoteId) {
|
constructor(args: ViewModeArgs) {
|
||||||
this.args = args;
|
|
||||||
this.viewType = this.#getViewType(args.parentNote);
|
this.viewType = this.#getViewType(args.parentNote);
|
||||||
|
|
||||||
|
switch (this.viewType) {
|
||||||
|
case "list":
|
||||||
|
case "grid":
|
||||||
|
this.viewMode = new ListOrGridView(this.viewType, args);
|
||||||
|
break;
|
||||||
|
case "calendar":
|
||||||
|
this.viewMode = new CalendarView(args);
|
||||||
|
break;
|
||||||
|
case "table":
|
||||||
|
this.viewMode = new TableView(args);
|
||||||
|
break;
|
||||||
|
case "geoMap":
|
||||||
|
this.viewMode = new GeoView(args);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.viewMode = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#getViewType(parentNote: FNote): ViewTypeOptions {
|
#getViewType(parentNote: FNote): ViewTypeOptions {
|
||||||
const viewType = parentNote.getLabelValue("viewType");
|
const viewType = parentNote.getLabelValue("viewType");
|
||||||
|
|
||||||
if (!(allViewTypes as readonly string[]).includes(viewType || "")) {
|
if (!["list", "grid", "calendar", "table", "geoMap"].includes(viewType || "")) {
|
||||||
// when not explicitly set, decide based on the note type
|
// when not explicitly set, decide based on the note type
|
||||||
return parentNote.type === "search" ? "list" : "grid";
|
return parentNote.type === "search" ? "list" : "grid";
|
||||||
} else {
|
} else {
|
||||||
@@ -34,38 +47,15 @@ export default class NoteListRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isFullHeight() {
|
get isFullHeight() {
|
||||||
switch (this.viewType) {
|
return this.viewMode?.isFullHeight;
|
||||||
case "list":
|
|
||||||
case "grid":
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderList() {
|
async renderList() {
|
||||||
const args = this.args;
|
if (!this.viewMode) {
|
||||||
const viewMode = this.#buildViewMode(args);
|
return null;
|
||||||
this.viewMode = viewMode;
|
|
||||||
await viewMode.beforeRender();
|
|
||||||
return await viewMode.renderList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#buildViewMode(args: ViewModeArgs) {
|
return await this.viewMode.renderList();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ let openTooltipElements: JQuery<HTMLElement>[] = [];
|
|||||||
let dismissTimer: ReturnType<typeof setTimeout>;
|
let dismissTimer: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
function setupGlobalTooltip() {
|
function setupGlobalTooltip() {
|
||||||
$(document).on("mouseenter", "a:not(.no-tooltip-preview)", mouseEnterHandler);
|
$(document).on("mouseenter", "a", mouseEnterHandler);
|
||||||
$(document).on("mouseenter", "[data-href]:not(.no-tooltip-preview)", mouseEnterHandler);
|
$(document).on("mouseenter", "[data-href]", mouseEnterHandler);
|
||||||
|
|
||||||
// close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
|
// close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
|
||||||
$(document).on("click", (e) => {
|
$(document).on("click", (e) => {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { OptionNames } from "@triliumnext/commons";
|
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import { isShare } from "./utils.js";
|
import { isShare } from "./utils.js";
|
||||||
|
|
||||||
export type OptionValue = number | string;
|
type OptionValue = number | string;
|
||||||
|
|
||||||
class Options {
|
class Options {
|
||||||
initializedPromise: Promise<void>;
|
initializedPromise: Promise<void>;
|
||||||
@@ -77,14 +76,6 @@ class Options {
|
|||||||
await server.put(`options`, payload);
|
await server.put(`options`, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves multiple options at once, by supplying a record where the keys are the option names and the values represent the stringified value to set.
|
|
||||||
* @param newValues the record of keys and values.
|
|
||||||
*/
|
|
||||||
async saveMany<T extends OptionNames>(newValues: Record<T, OptionValue>) {
|
|
||||||
await server.put<void>("options", newValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggle(key: string) {
|
async toggle(key: string) {
|
||||||
await this.save(key, (!this.is(key)).toString());
|
await this.save(key, (!this.is(key)).toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url" | "color";
|
export type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
|
||||||
type Multiplicity = "single" | "multi";
|
type Multiplicity = "single" | "multi";
|
||||||
|
|
||||||
export interface DefinitionObject {
|
export interface DefinitionObject {
|
||||||
@@ -17,7 +17,7 @@ function parse(value: string) {
|
|||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
if (token === "promoted") {
|
if (token === "promoted") {
|
||||||
defObj.isPromoted = true;
|
defObj.isPromoted = true;
|
||||||
} else if (["text", "number", "boolean", "date", "datetime", "time", "url", "color"].includes(token)) {
|
} else if (["text", "number", "boolean", "date", "datetime", "time", "url"].includes(token)) {
|
||||||
defObj.labelType = token as LabelType;
|
defObj.labelType = token as LabelType;
|
||||||
} else if (["single", "multi"].includes(token)) {
|
} else if (["single", "multi"].includes(token)) {
|
||||||
defObj.multiplicity = token as Multiplicity;
|
defObj.multiplicity = token as Multiplicity;
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -87,86 +83,7 @@ function setupRightPaneResizer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findKeyByNtxId(ntxId: string): string[] | undefined {
|
|
||||||
// Find the corresponding key in noteSplitMap based on ntxId
|
|
||||||
for (const key of noteSplitMap.keys()) {
|
|
||||||
if (key.includes(ntxId)) return key;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupNoteSplitResizer(ntxIds: string[]) {
|
|
||||||
let targetNtxIds: string[] | undefined;
|
|
||||||
for (const ntxId of ntxIds) {
|
|
||||||
targetNtxIds = findKeyByNtxId(ntxId);
|
|
||||||
if (targetNtxIds) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetNtxIds) {
|
|
||||||
noteSplitMap.get(targetNtxIds)?.destroy();
|
|
||||||
for (const id of ntxIds) {
|
|
||||||
if (!targetNtxIds.includes(id)) {
|
|
||||||
targetNtxIds.push(id)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
targetNtxIds = [...ntxIds];
|
|
||||||
}
|
|
||||||
noteSplitMap.set(targetNtxIds, undefined);
|
|
||||||
createSplitInstance(targetNtxIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function delNoteSplitResizer(ntxIds: string[]) {
|
|
||||||
let targetNtxIds = findKeyByNtxId(ntxIds[0]);
|
|
||||||
if (!targetNtxIds) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
noteSplitMap.get(targetNtxIds)?.destroy();
|
|
||||||
noteSplitMap.delete(targetNtxIds);
|
|
||||||
targetNtxIds = targetNtxIds.filter(id => !ntxIds.includes(id));
|
|
||||||
|
|
||||||
if (targetNtxIds.length >= 2) {
|
|
||||||
noteSplitMap.set(targetNtxIds, undefined);
|
|
||||||
createSplitInstance(targetNtxIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveNoteSplitResizer(ntxId: string) {
|
|
||||||
const targetNtxIds = findKeyByNtxId(ntxId);
|
|
||||||
if (!targetNtxIds) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
noteSplitMap.get(targetNtxIds)?.destroy();
|
|
||||||
noteSplitMap.set(targetNtxIds, undefined);
|
|
||||||
createSplitInstance(targetNtxIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSplitInstance(targetNtxIds: string[]) {
|
|
||||||
const prevRafId = noteSplitRafMap.get(targetNtxIds);
|
|
||||||
if (prevRafId) {
|
|
||||||
cancelAnimationFrame(prevRafId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rafId = requestAnimationFrame(() => {
|
|
||||||
splitNoteContainer = splitNoteContainer ?? $("#center-pane").find(".split-note-container-widget")[0];
|
|
||||||
const splitPanels = [...splitNoteContainer.querySelectorAll<HTMLElement>(':scope > .note-split')]
|
|
||||||
.filter(el => targetNtxIds.includes(el.getAttribute('data-ntx-id') ?? ""));
|
|
||||||
const splitInstance = Split(splitPanels, {
|
|
||||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
|
||||||
minSize: 150,
|
|
||||||
});
|
|
||||||
noteSplitMap.set(targetNtxIds, splitInstance);
|
|
||||||
noteSplitRafMap.delete(targetNtxIds);
|
|
||||||
});
|
|
||||||
noteSplitRafMap.set(targetNtxIds, rafId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setupLeftPaneResizer,
|
setupLeftPaneResizer,
|
||||||
setupRightPaneResizer,
|
setupRightPaneResizer
|
||||||
setupNoteSplitResizer,
|
|
||||||
delNoteSplitResizer,
|
|
||||||
moveNoteSplitResizer
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,355 +0,0 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
||||||
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
|
|
||||||
|
|
||||||
// Mock utils module
|
|
||||||
vi.mock("./utils.js", () => ({
|
|
||||||
default: {
|
|
||||||
isDesktop: () => true
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock jQuery globally since it's used in the shortcuts module
|
|
||||||
const mockElement = {
|
|
||||||
addEventListener: vi.fn(),
|
|
||||||
removeEventListener: vi.fn()
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockJQuery = vi.fn(() => [mockElement]);
|
|
||||||
(mockJQuery as any).length = 1;
|
|
||||||
mockJQuery[0] = mockElement;
|
|
||||||
|
|
||||||
(global as any).$ = mockJQuery as any;
|
|
||||||
global.document = mockElement as any;
|
|
||||||
|
|
||||||
describe("shortcuts", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
// Clean up any active bindings after each test
|
|
||||||
shortcuts.removeGlobalShortcut("test-namespace");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("normalizeShortcut", () => {
|
|
||||||
it("should normalize shortcut to lowercase and remove whitespace", () => {
|
|
||||||
expect(shortcuts.normalizeShortcut("Ctrl + A")).toBe("ctrl+a");
|
|
||||||
expect(shortcuts.normalizeShortcut(" SHIFT + F1 ")).toBe("shift+f1");
|
|
||||||
expect(shortcuts.normalizeShortcut("Alt+Space")).toBe("alt+space");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle empty or null shortcuts", () => {
|
|
||||||
expect(shortcuts.normalizeShortcut("")).toBe("");
|
|
||||||
expect(shortcuts.normalizeShortcut(null as any)).toBe(null);
|
|
||||||
expect(shortcuts.normalizeShortcut(undefined as any)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle shortcuts with multiple spaces", () => {
|
|
||||||
expect(shortcuts.normalizeShortcut("Ctrl + Shift + A")).toBe("ctrl+shift+a");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should warn about malformed shortcuts", () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
|
|
||||||
shortcuts.normalizeShortcut("ctrl+");
|
|
||||||
shortcuts.normalizeShortcut("+a");
|
|
||||||
shortcuts.normalizeShortcut("ctrl++a");
|
|
||||||
|
|
||||||
expect(consoleSpy).toHaveBeenCalledTimes(3);
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("keyMatches", () => {
|
|
||||||
const createKeyboardEvent = (key: string, code?: string) => ({
|
|
||||||
key,
|
|
||||||
code: code || `Key${key.toUpperCase()}`
|
|
||||||
} as KeyboardEvent);
|
|
||||||
|
|
||||||
it("should match regular letter keys using key code", () => {
|
|
||||||
const event = createKeyboardEvent("a", "KeyA");
|
|
||||||
expect(keyMatches(event, "a")).toBe(true);
|
|
||||||
expect(keyMatches(event, "A")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should match number keys using digit codes", () => {
|
|
||||||
const event = createKeyboardEvent("1", "Digit1");
|
|
||||||
expect(keyMatches(event, "1")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should match special keys using key mapping", () => {
|
|
||||||
expect(keyMatches({ key: "Enter" } as KeyboardEvent, "return")).toBe(true);
|
|
||||||
expect(keyMatches({ key: "Enter" } as KeyboardEvent, "enter")).toBe(true);
|
|
||||||
expect(keyMatches({ key: "Delete" } as KeyboardEvent, "del")).toBe(true);
|
|
||||||
expect(keyMatches({ key: "Escape" } as KeyboardEvent, "esc")).toBe(true);
|
|
||||||
expect(keyMatches({ key: " " } as KeyboardEvent, "space")).toBe(true);
|
|
||||||
expect(keyMatches({ key: "ArrowUp" } as KeyboardEvent, "up")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should match function keys", () => {
|
|
||||||
expect(keyMatches({ key: "F1" } as KeyboardEvent, "f1")).toBe(true);
|
|
||||||
expect(keyMatches({ key: "F12" } as KeyboardEvent, "f12")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle undefined or null keys", () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
|
|
||||||
expect(keyMatches({} as KeyboardEvent, null as any)).toBe(false);
|
|
||||||
expect(keyMatches({} as KeyboardEvent, undefined as any)).toBe(false);
|
|
||||||
|
|
||||||
expect(consoleSpy).toHaveBeenCalled();
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("matchesShortcut", () => {
|
|
||||||
const createKeyboardEvent = (options: {
|
|
||||||
key: string;
|
|
||||||
code?: string;
|
|
||||||
ctrlKey?: boolean;
|
|
||||||
altKey?: boolean;
|
|
||||||
shiftKey?: boolean;
|
|
||||||
metaKey?: boolean;
|
|
||||||
}) => ({
|
|
||||||
key: options.key,
|
|
||||||
code: options.code || `Key${options.key.toUpperCase()}`,
|
|
||||||
ctrlKey: options.ctrlKey || false,
|
|
||||||
altKey: options.altKey || false,
|
|
||||||
shiftKey: options.shiftKey || false,
|
|
||||||
metaKey: options.metaKey || false
|
|
||||||
} 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", () => {
|
|
||||||
const event = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
|
|
||||||
expect(matchesShortcut(event, "ctrl+a")).toBe(true);
|
|
||||||
|
|
||||||
const shiftEvent = createKeyboardEvent({ key: "a", code: "KeyA", shiftKey: true });
|
|
||||||
expect(matchesShortcut(shiftEvent, "shift+a")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should match complex modifier combinations", () => {
|
|
||||||
const event = createKeyboardEvent({
|
|
||||||
key: "a",
|
|
||||||
code: "KeyA",
|
|
||||||
ctrlKey: true,
|
|
||||||
shiftKey: true
|
|
||||||
});
|
|
||||||
expect(matchesShortcut(event, "ctrl+shift+a")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not match when modifiers don't match", () => {
|
|
||||||
const event = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
|
|
||||||
expect(matchesShortcut(event, "alt+a")).toBe(false);
|
|
||||||
expect(matchesShortcut(event, "a")).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle alternative modifier names", () => {
|
|
||||||
const ctrlEvent = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
|
|
||||||
expect(matchesShortcut(ctrlEvent, "control+a")).toBe(true);
|
|
||||||
|
|
||||||
const metaEvent = createKeyboardEvent({ key: "a", code: "KeyA", metaKey: true });
|
|
||||||
expect(matchesShortcut(metaEvent, "cmd+a")).toBe(true);
|
|
||||||
expect(matchesShortcut(metaEvent, "command+a")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle empty or invalid shortcuts", () => {
|
|
||||||
const event = createKeyboardEvent({ key: "a", code: "KeyA" });
|
|
||||||
expect(matchesShortcut(event, "")).toBe(false);
|
|
||||||
expect(matchesShortcut(event, null as any)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle invalid events", () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
|
|
||||||
expect(matchesShortcut(null as any, "a")).toBe(false);
|
|
||||||
expect(matchesShortcut({} as KeyboardEvent, "a")).toBe(false);
|
|
||||||
|
|
||||||
expect(consoleSpy).toHaveBeenCalled();
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should warn about invalid shortcut formats", () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
const event = createKeyboardEvent({ key: "a", code: "KeyA" });
|
|
||||||
|
|
||||||
matchesShortcut(event, "ctrl+");
|
|
||||||
matchesShortcut(event, "+");
|
|
||||||
|
|
||||||
expect(consoleSpy).toHaveBeenCalled();
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("bindGlobalShortcut", () => {
|
|
||||||
it("should bind a global shortcut", () => {
|
|
||||||
const handler = vi.fn();
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
|
||||||
|
|
||||||
expect(mockElement.addEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not bind shortcuts when handler is null", () => {
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+a", null, "test-namespace");
|
|
||||||
|
|
||||||
expect(mockElement.addEventListener).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove previous bindings when namespace is reused", () => {
|
|
||||||
const handler1 = vi.fn();
|
|
||||||
const handler2 = vi.fn();
|
|
||||||
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+a", handler1, "test-namespace");
|
|
||||||
expect(mockElement.addEventListener).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+b", handler2, "test-namespace");
|
|
||||||
expect(mockElement.removeEventListener).toHaveBeenCalledTimes(1);
|
|
||||||
expect(mockElement.addEventListener).toHaveBeenCalledTimes(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("bindElShortcut", () => {
|
|
||||||
it("should bind shortcut to specific element", () => {
|
|
||||||
const mockEl = { addEventListener: vi.fn(), removeEventListener: vi.fn() };
|
|
||||||
const mockJQueryEl = [mockEl] as any;
|
|
||||||
mockJQueryEl.length = 1;
|
|
||||||
|
|
||||||
const handler = vi.fn();
|
|
||||||
shortcuts.bindElShortcut(mockJQueryEl, "ctrl+a", handler, "test-namespace");
|
|
||||||
|
|
||||||
expect(mockEl.addEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fall back to document when element is empty", () => {
|
|
||||||
const emptyJQuery = [] as any;
|
|
||||||
emptyJQuery.length = 0;
|
|
||||||
|
|
||||||
const handler = vi.fn();
|
|
||||||
shortcuts.bindElShortcut(emptyJQuery, "ctrl+a", handler, "test-namespace");
|
|
||||||
|
|
||||||
expect(mockElement.addEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("removeGlobalShortcut", () => {
|
|
||||||
it("should remove shortcuts for a specific namespace", () => {
|
|
||||||
const handler = vi.fn();
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
|
||||||
|
|
||||||
shortcuts.removeGlobalShortcut("test-namespace");
|
|
||||||
|
|
||||||
expect(mockElement.removeEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("event handling", () => {
|
|
||||||
it.skip("should call handler when shortcut matches", () => {
|
|
||||||
const handler = vi.fn();
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
|
||||||
|
|
||||||
// Get the listener that was registered
|
|
||||||
expect(mockElement.addEventListener.mock.calls).toHaveLength(1);
|
|
||||||
const [, listener] = mockElement.addEventListener.mock.calls[0];
|
|
||||||
|
|
||||||
// First verify that matchesShortcut works directly
|
|
||||||
const testEvent = {
|
|
||||||
type: "keydown",
|
|
||||||
key: "a",
|
|
||||||
code: "KeyA",
|
|
||||||
ctrlKey: true,
|
|
||||||
altKey: false,
|
|
||||||
shiftKey: false,
|
|
||||||
metaKey: false,
|
|
||||||
preventDefault: vi.fn(),
|
|
||||||
stopPropagation: vi.fn()
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
// Test matchesShortcut directly first
|
|
||||||
expect(matchesShortcut(testEvent, "ctrl+a")).toBe(true);
|
|
||||||
|
|
||||||
// Now test the actual listener
|
|
||||||
listener(testEvent);
|
|
||||||
|
|
||||||
expect(handler).toHaveBeenCalled();
|
|
||||||
expect(testEvent.preventDefault).toHaveBeenCalled();
|
|
||||||
expect(testEvent.stopPropagation).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not call handler for non-keyboard events", () => {
|
|
||||||
const handler = vi.fn();
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
|
||||||
|
|
||||||
const [, listener] = mockElement.addEventListener.mock.calls[0];
|
|
||||||
|
|
||||||
// Simulate a non-keyboard event
|
|
||||||
const event = {
|
|
||||||
type: "click"
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
listener(event);
|
|
||||||
|
|
||||||
expect(handler).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not call handler when shortcut doesn't match", () => {
|
|
||||||
const handler = vi.fn();
|
|
||||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
|
||||||
|
|
||||||
const [, listener] = mockElement.addEventListener.mock.calls[0];
|
|
||||||
|
|
||||||
// Simulate a non-matching keydown event
|
|
||||||
const event = {
|
|
||||||
type: "keydown",
|
|
||||||
key: "b",
|
|
||||||
code: "KeyB",
|
|
||||||
ctrlKey: true,
|
|
||||||
altKey: false,
|
|
||||||
shiftKey: false,
|
|
||||||
metaKey: false,
|
|
||||||
preventDefault: vi.fn(),
|
|
||||||
stopPropagation: vi.fn()
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
listener(event);
|
|
||||||
|
|
||||||
expect(handler).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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,62 +1,7 @@
|
|||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
|
|
||||||
type ElementType = HTMLElement | Document;
|
type ElementType = HTMLElement | Document;
|
||||||
type Handler = (e: KeyboardEvent) => void;
|
type Handler = (e: JQuery.TriggeredEvent<ElementType | Element, string, ElementType | Element, ElementType | Element>) => void;
|
||||||
|
|
||||||
interface ShortcutBinding {
|
|
||||||
element: HTMLElement | Document;
|
|
||||||
shortcut: string;
|
|
||||||
handler: Handler;
|
|
||||||
namespace: string | null;
|
|
||||||
listener: (evt: Event) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store all active shortcut bindings for management
|
|
||||||
const activeBindings: Map<string, ShortcutBinding[]> = new Map();
|
|
||||||
|
|
||||||
// Handle special key mappings and aliases
|
|
||||||
const keyMap: { [key: string]: string[] } = {
|
|
||||||
'return': ['Enter'],
|
|
||||||
'enter': ['Enter'], // alias for return
|
|
||||||
'del': ['Delete'],
|
|
||||||
'delete': ['Delete'], // alias for del
|
|
||||||
'esc': ['Escape'],
|
|
||||||
'escape': ['Escape'], // alias for esc
|
|
||||||
'space': [' ', 'Space'],
|
|
||||||
'tab': ['Tab'],
|
|
||||||
'backspace': ['Backspace'],
|
|
||||||
'home': ['Home'],
|
|
||||||
'end': ['End'],
|
|
||||||
'pageup': ['PageUp'],
|
|
||||||
'pagedown': ['PageDown'],
|
|
||||||
'up': ['ArrowUp'],
|
|
||||||
'down': ['ArrowDown'],
|
|
||||||
'left': ['ArrowLeft'],
|
|
||||||
'right': ['ArrowRight']
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function keys
|
|
||||||
for (let i = 1; i <= 19; i++) {
|
|
||||||
keyMap[`f${i}`] = [`F${i}`];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if IME (Input Method Editor) is composing
|
|
||||||
* This is used to prevent keyboard shortcuts from firing during IME composition
|
|
||||||
* @param e - The keyboard event to check
|
|
||||||
* @returns true if IME is currently composing, false otherwise
|
|
||||||
*/
|
|
||||||
export function isIMEComposing(e: KeyboardEvent): boolean {
|
|
||||||
// Handle null/undefined events gracefully
|
|
||||||
if (!e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard check for composition state
|
|
||||||
// e.isComposing is true when IME is actively composing
|
|
||||||
// e.keyCode === 229 is a fallback for older browsers where 229 indicates IME processing
|
|
||||||
return e.isComposing || e.keyCode === 229;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeGlobalShortcut(namespace: string) {
|
function removeGlobalShortcut(namespace: string) {
|
||||||
bindGlobalShortcut("", null, namespace);
|
bindGlobalShortcut("", null, namespace);
|
||||||
@@ -70,148 +15,38 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
|||||||
if (utils.isDesktop()) {
|
if (utils.isDesktop()) {
|
||||||
keyboardShortcut = normalizeShortcut(keyboardShortcut);
|
keyboardShortcut = normalizeShortcut(keyboardShortcut);
|
||||||
|
|
||||||
// If namespace is provided, remove all previous bindings for this namespace
|
let eventName = "keydown";
|
||||||
|
|
||||||
if (namespace) {
|
if (namespace) {
|
||||||
removeNamespaceBindings(namespace);
|
eventName += `.${namespace}`;
|
||||||
|
|
||||||
|
// if there's a namespace, then we replace the existing event handler with the new one
|
||||||
|
$el.off(eventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method can be called to remove the shortcut (e.g. when keyboardShortcut label is deleted)
|
// method can be called to remove the shortcut (e.g. when keyboardShortcut label is deleted)
|
||||||
if (keyboardShortcut && handler) {
|
if (keyboardShortcut) {
|
||||||
const element = $el.length > 0 ? $el[0] as (HTMLElement | Document) : document;
|
$el.bind(eventName, keyboardShortcut, (e) => {
|
||||||
|
if (handler) {
|
||||||
const listener = (evt: Event) => {
|
|
||||||
// Only handle keyboard events
|
|
||||||
if (evt.type !== 'keydown' || !(evt instanceof KeyboardEvent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const e = evt as KeyboardEvent;
|
|
||||||
|
|
||||||
// Skip processing if IME is composing to prevent shortcuts from
|
|
||||||
// interfering with text input in CJK languages
|
|
||||||
if (isIMEComposing(e)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchesShortcut(e, keyboardShortcut)) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
handler(e);
|
handler(e);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Add the event listener
|
e.preventDefault();
|
||||||
element.addEventListener('keydown', listener);
|
e.stopPropagation();
|
||||||
|
|
||||||
// Store the binding for later cleanup
|
|
||||||
const binding: ShortcutBinding = {
|
|
||||||
element,
|
|
||||||
shortcut: keyboardShortcut,
|
|
||||||
handler,
|
|
||||||
namespace,
|
|
||||||
listener
|
|
||||||
};
|
|
||||||
|
|
||||||
const key = namespace || 'global';
|
|
||||||
if (!activeBindings.has(key)) {
|
|
||||||
activeBindings.set(key, []);
|
|
||||||
}
|
|
||||||
activeBindings.get(key)!.push(binding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeNamespaceBindings(namespace: string) {
|
|
||||||
const bindings = activeBindings.get(namespace);
|
|
||||||
if (bindings) {
|
|
||||||
// Remove all event listeners for this namespace
|
|
||||||
bindings.forEach(binding => {
|
|
||||||
binding.element.removeEventListener('keydown', binding.listener);
|
|
||||||
});
|
});
|
||||||
activeBindings.delete(namespace);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function matchesShortcut(e: KeyboardEvent, shortcut: string): boolean {
|
|
||||||
if (!shortcut) return false;
|
|
||||||
|
|
||||||
// Ensure we have a proper KeyboardEvent with key property
|
|
||||||
if (!e || typeof e.key !== 'string') {
|
|
||||||
console.warn('matchesShortcut called with invalid event:', e);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = shortcut.toLowerCase().split('+');
|
|
||||||
const key = parts[parts.length - 1]; // Last part is the actual key
|
|
||||||
const modifiers = parts.slice(0, -1); // Everything before is modifiers
|
|
||||||
|
|
||||||
// Defensive check - ensure we have a valid key
|
|
||||||
if (!key || key.trim() === '') {
|
|
||||||
console.warn('Invalid shortcut format:', shortcut);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the main key matches
|
|
||||||
if (!keyMatches(e, key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check modifiers
|
|
||||||
const expectedCtrl = modifiers.includes('ctrl') || modifiers.includes('control');
|
|
||||||
const expectedAlt = modifiers.includes('alt');
|
|
||||||
const expectedShift = modifiers.includes('shift');
|
|
||||||
const expectedMeta = modifiers.includes('meta') || modifiers.includes('cmd') || modifiers.includes('command');
|
|
||||||
|
|
||||||
return e.ctrlKey === expectedCtrl &&
|
|
||||||
e.altKey === expectedAlt &&
|
|
||||||
e.shiftKey === expectedShift &&
|
|
||||||
e.metaKey === expectedMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function keyMatches(e: KeyboardEvent, key: string): boolean {
|
|
||||||
// Defensive check for undefined/null key
|
|
||||||
if (!key) {
|
|
||||||
console.warn('keyMatches called with undefined/null key');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mappedKeys = keyMap[key.toLowerCase()];
|
|
||||||
if (mappedKeys) {
|
|
||||||
return mappedKeys.includes(e.key) || mappedKeys.includes(e.code);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For number keys, use the physical key code regardless of modifiers
|
|
||||||
// This works across all keyboard layouts
|
|
||||||
if (key >= '0' && key <= '9') {
|
|
||||||
return e.code === `Digit${key}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For letter keys, use the physical key code for consistency
|
|
||||||
if (key.length === 1 && key >= 'a' && key <= 'z') {
|
|
||||||
return e.key.toLowerCase() === key.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For regular keys, check both key and code as fallback
|
|
||||||
return e.key.toLowerCase() === key.toLowerCase() ||
|
|
||||||
e.code.toLowerCase() === key.toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple normalization - just lowercase and trim whitespace
|
* Normalize to the form expected by the jquery.hotkeys.js
|
||||||
*/
|
*/
|
||||||
function normalizeShortcut(shortcut: string): string {
|
function normalizeShortcut(shortcut: string): string {
|
||||||
if (!shortcut) {
|
if (!shortcut) {
|
||||||
return shortcut;
|
return shortcut;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalized = shortcut.toLowerCase().trim().replace(/\s+/g, '');
|
return shortcut.toLowerCase().replace("enter", "return").replace("delete", "del").replace("ctrl+alt", "alt+ctrl").replace("meta+alt", "alt+meta"); // alt needs to be first;
|
||||||
|
|
||||||
// Warn about potentially problematic shortcuts
|
|
||||||
if (normalized.endsWith('+') || normalized.startsWith('+') || normalized.includes('++')) {
|
|
||||||
console.warn('Potentially malformed shortcut:', shortcut, '-> normalized to:', normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -51,14 +51,6 @@ export default class SpacedUpdate {
|
|||||||
this.lastUpdated = Date.now();
|
this.lastUpdated = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the update interval for the spaced update.
|
|
||||||
* @param interval The update interval in milliseconds.
|
|
||||||
*/
|
|
||||||
setUpdateInterval(interval: number) {
|
|
||||||
this.updateInterval = interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerUpdate() {
|
triggerUpdate() {
|
||||||
if (!this.changed) {
|
if (!this.changed) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -36,9 +36,7 @@ export function applyCopyToClipboardButton($codeBlock: JQuery<HTMLElement>) {
|
|||||||
const $copyButton = $("<button>")
|
const $copyButton = $("<button>")
|
||||||
.addClass("bx component icon-action tn-tool-button bx-copy copy-button")
|
.addClass("bx component icon-action tn-tool-button bx-copy copy-button")
|
||||||
.attr("title", t("code_block.copy_title"))
|
.attr("title", t("code_block.copy_title"))
|
||||||
.on("click", (e) => {
|
.on("click", () => {
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (!isShare) {
|
if (!isShare) {
|
||||||
copyTextWithToast($codeBlock.text());
|
copyTextWithToast($codeBlock.text());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
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";
|
||||||
|
|
||||||
export const isShare = !window.glob;
|
export const isShare = !window.glob;
|
||||||
|
|
||||||
export function reloadFrontendApp(reason?: string) {
|
function reloadFrontendApp(reason?: string) {
|
||||||
if (reason) {
|
if (reason) {
|
||||||
logInfo(`Frontend app reload: ${reason}`);
|
logInfo(`Frontend app reload: ${reason}`);
|
||||||
}
|
}
|
||||||
@@ -14,7 +13,7 @@ export function reloadFrontendApp(reason?: string) {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function restartDesktopApp() {
|
function restartDesktopApp() {
|
||||||
if (!isElectron()) {
|
if (!isElectron()) {
|
||||||
reloadFrontendApp();
|
reloadFrontendApp();
|
||||||
return;
|
return;
|
||||||
@@ -126,7 +125,7 @@ function formatDateISO(date: Date) {
|
|||||||
return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`;
|
return `${date.getFullYear()}-${padNum(date.getMonth() + 1)}-${padNum(date.getDate())}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatDateTime(date: Date, userSuppliedFormat?: string): string {
|
function formatDateTime(date: Date, userSuppliedFormat?: string): string {
|
||||||
if (userSuppliedFormat?.trim()) {
|
if (userSuppliedFormat?.trim()) {
|
||||||
return dayjs(date).format(userSuppliedFormat);
|
return dayjs(date).format(userSuppliedFormat);
|
||||||
} else {
|
} else {
|
||||||
@@ -145,11 +144,11 @@ function now() {
|
|||||||
/**
|
/**
|
||||||
* Returns `true` if the client is currently running under Electron, or `false` if running in a web browser.
|
* Returns `true` if the client is currently running under Electron, or `false` if running in a web browser.
|
||||||
*/
|
*/
|
||||||
export function isElectron() {
|
function isElectron() {
|
||||||
return !!(window && window.process && window.process.type);
|
return !!(window && window.process && window.process.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMac() {
|
function isMac() {
|
||||||
return navigator.platform.indexOf("Mac") > -1;
|
return navigator.platform.indexOf("Mac") > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,11 +185,7 @@ export function escapeQuotes(value: string) {
|
|||||||
return value.replaceAll('"', """);
|
return value.replaceAll('"', """);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -223,7 +218,7 @@ function randomString(len: number) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMobile() {
|
function isMobile() {
|
||||||
return (
|
return (
|
||||||
window.glob?.device === "mobile" ||
|
window.glob?.device === "mobile" ||
|
||||||
// window.glob.device is not available in setup
|
// window.glob.device is not available in setup
|
||||||
@@ -297,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();
|
||||||
@@ -359,13 +306,7 @@ function copySelectionToClipboard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type dynamicRequireMappings = {
|
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 {
|
||||||
@@ -433,17 +374,6 @@ async function openInAppHelp($button: JQuery<HTMLElement>) {
|
|||||||
|
|
||||||
const inAppHelpPage = $button.attr("data-in-app-help");
|
const inAppHelpPage = $button.attr("data-in-app-help");
|
||||||
if (inAppHelpPage) {
|
if (inAppHelpPage) {
|
||||||
openInAppHelpFromUrl(inAppHelpPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the in-app help at the given page in a split note. If there already is a split note open with a help page, it will be replaced by this one.
|
|
||||||
*
|
|
||||||
* @param inAppHelpPage the ID of the help note (excluding the `_help_` prefix).
|
|
||||||
* @returns a promise that resolves once the help has been opened.
|
|
||||||
*/
|
|
||||||
export async function openInAppHelpFromUrl(inAppHelpPage: string) {
|
|
||||||
// Dynamic import to avoid import issues in tests.
|
// Dynamic import to avoid import issues in tests.
|
||||||
const appContext = (await import("../components/app_context.js")).default;
|
const appContext = (await import("../components/app_context.js")).default;
|
||||||
const activeContext = appContext.tabManager.getActiveContext();
|
const activeContext = appContext.tabManager.getActiveContext();
|
||||||
@@ -469,6 +399,8 @@ export async function openInAppHelpFromUrl(inAppHelpPage: string) {
|
|||||||
// There is already a help window open, make sure it opens on the right note.
|
// There is already a help window open, make sure it opens on the right note.
|
||||||
helpSubcontext.setNote(targetNote, { viewScope });
|
helpSubcontext.setNote(targetNote, { viewScope });
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initHelpButtons($el: JQuery<HTMLElement> | JQuery<Window>) {
|
function initHelpButtons($el: JQuery<HTMLElement> | JQuery<Window>) {
|
||||||
@@ -629,7 +561,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()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,86 +731,10 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a class to the <body> of the page, where the class name is formed via a prefix and a value.
|
|
||||||
* Useful for configurable options such as `heading-style-markdown`, where `heading-style` is the prefix and `markdown` is the dynamic value.
|
|
||||||
* There is no separator between the prefix and the value, if needed it has to be supplied manually to the prefix.
|
|
||||||
*
|
|
||||||
* @param prefix the prefix.
|
|
||||||
* @param value the value to be appended to the prefix.
|
|
||||||
*/
|
|
||||||
export function toggleBodyClass(prefix: string, value: string) {
|
|
||||||
const $body = $("body");
|
|
||||||
for (const clazz of Array.from($body[0].classList)) {
|
|
||||||
// create copy to safely iterate over while removing classes
|
|
||||||
if (clazz.startsWith(prefix)) {
|
|
||||||
$body.removeClass(clazz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$body.addClass(prefix + value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic comparison for equality between the two arrays. The values are strictly checked via `===`.
|
|
||||||
*
|
|
||||||
* @param a the first array to compare.
|
|
||||||
* @param b the second array to compare.
|
|
||||||
* @returns `true` if both arrays are equals, `false` otherwise.
|
|
||||||
*/
|
|
||||||
export function arrayEqual<T>(a: T[], b: T[]) {
|
|
||||||
if (a === b) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (a.length !== b.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i=0; i < a.length; i++) {
|
|
||||||
if (a[i] !== b[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Indexed<T extends object> = T & { index: number };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an object array, alters every object in the array to have an index field assigned to it.
|
|
||||||
*
|
|
||||||
* @param items the objects to be numbered.
|
|
||||||
* @returns the same object for convenience, with the type changed to indicate the new index field.
|
|
||||||
*/
|
|
||||||
export function numberObjectsInPlace<T extends object>(items: T[]): Indexed<T>[] {
|
|
||||||
let index = 0;
|
|
||||||
for (const item of items) {
|
|
||||||
(item as Indexed<T>).index = index++;
|
|
||||||
}
|
|
||||||
return items as Indexed<T>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapToKeyValueArray<K extends string | number | symbol, V>(map: Record<K, V>) {
|
|
||||||
const values: { key: K, value: V }[] = [];
|
|
||||||
for (const [ key, value ] of Object.entries(map)) {
|
|
||||||
values.push({ key: key as K, value: value as V });
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getErrorMessage(e: unknown) {
|
|
||||||
if (e && typeof e === "object" && "message" in e && typeof e.message === "string") {
|
|
||||||
return e.message;
|
|
||||||
} else {
|
|
||||||
return "Unknown error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
reloadFrontendApp,
|
reloadFrontendApp,
|
||||||
restartDesktopApp,
|
restartDesktopApp,
|
||||||
@@ -903,7 +760,6 @@ export default {
|
|||||||
getNoteTypeClass,
|
getNoteTypeClass,
|
||||||
getMimeTypeClass,
|
getMimeTypeClass,
|
||||||
isHtmlEmpty,
|
isHtmlEmpty,
|
||||||
formatHtml,
|
|
||||||
clearBrowserCache,
|
clearBrowserCache,
|
||||||
copySelectionToClipboard,
|
copySelectionToClipboard,
|
||||||
dynamicRequire,
|
dynamicRequire,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
import "./stylesheets/bootstrap.scss";
|
||||||
import "./stylesheets/auth.css";
|
import "./stylesheets/auth.css";
|
||||||
|
|
||||||
// @TriliumNextTODO: is this even needed anymore?
|
// @TriliumNextTODO: is this even needed anymore?
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import "jquery";
|
import "jquery";
|
||||||
|
import "jquery-hotkeys";
|
||||||
import utils from "./services/utils.js";
|
import utils from "./services/utils.js";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
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" | "";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
@@ -29,14 +29,6 @@ async function formatCodeBlocks() {
|
|||||||
await formatCodeBlocks($("#content"));
|
await formatCodeBlocks($("#content"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupTextNote() {
|
|
||||||
formatCodeBlocks();
|
|
||||||
applyMath();
|
|
||||||
|
|
||||||
const setupMermaid = (await import("./share/mermaid.js")).default;
|
|
||||||
setupMermaid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch note with given ID from backend
|
* Fetch note with given ID from backend
|
||||||
*
|
*
|
||||||
@@ -55,11 +47,8 @@ async function fetchNote(noteId: string | null = null) {
|
|||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"DOMContentLoaded",
|
"DOMContentLoaded",
|
||||||
() => {
|
() => {
|
||||||
const noteType = determineNoteType();
|
formatCodeBlocks();
|
||||||
|
applyMath();
|
||||||
if (noteType === "text") {
|
|
||||||
setupTextNote();
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleMenuButton = document.getElementById("toggleMenuButton");
|
const toggleMenuButton = document.getElementById("toggleMenuButton");
|
||||||
const layout = document.getElementById("layout");
|
const layout = document.getElementById("layout");
|
||||||
@@ -71,12 +60,6 @@ document.addEventListener(
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
function determineNoteType() {
|
|
||||||
const bodyClass = document.body.className;
|
|
||||||
const match = bodyClass.match(/type-([^\s]+)/);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// workaround to prevent webpack from removing "fetchNote" as dead code:
|
// workaround to prevent webpack from removing "fetchNote" as dead code:
|
||||||
// add fetchNote as property to the window object
|
// add fetchNote as property to the window object
|
||||||
Object.defineProperty(window, "fetchNote", {
|
Object.defineProperty(window, "fetchNote", {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import mermaid from "mermaid";
|
|
||||||
|
|
||||||
export default function setupMermaid() {
|
|
||||||
for (const codeBlock of document.querySelectorAll("#content pre code.language-mermaid")) {
|
|
||||||
const parentPre = codeBlock.parentElement;
|
|
||||||
if (!parentPre) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mermaidDiv = document.createElement("div");
|
|
||||||
mermaidDiv.classList.add("mermaid");
|
|
||||||
mermaidDiv.innerHTML = codeBlock.innerHTML;
|
|
||||||
parentPre.replaceWith(mermaidDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
mermaid.init();
|
|
||||||
}
|
|
||||||
2
apps/client/src/stylesheets/bootstrap.scss
vendored
Normal file
2
apps/client/src/stylesheets/bootstrap.scss
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/* Import all of Bootstrap's CSS */
|
||||||
|
@use "bootstrap/scss/bootstrap";
|
||||||
@@ -81,8 +81,8 @@ body {
|
|||||||
|
|
||||||
/* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */
|
/* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */
|
||||||
|
|
||||||
--ck-content-color-image-caption-background: var(--main-background-color);
|
--ck-color-image-caption-background: var(--main-background-color);
|
||||||
--ck-content-color-image-caption-text: var(--main-text-color);
|
--ck-color-image-caption-text: var(--main-text-color);
|
||||||
|
|
||||||
/* -- Overrides the default colors used by the ckeditor5-widget package. -------------------- */
|
/* -- Overrides the default colors used by the ckeditor5-widget package. -------------------- */
|
||||||
|
|
||||||
|
|||||||
@@ -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,13 +139,10 @@ textarea,
|
|||||||
color: var(--muted-text-color);
|
color: var(--muted-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group.disabled {
|
/* Restore default apperance */
|
||||||
opacity: 0.5;
|
input[type="number"],
|
||||||
pointer-events: none;
|
input[type="checkbox"] {
|
||||||
}
|
appearance: auto !important;
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add a gap between consecutive radios / check boxes */
|
/* Add a gap between consecutive radios / check boxes */
|
||||||
@@ -176,11 +151,6 @@ label.tn-checkbox + label.tn-checkbox {
|
|||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.tn-radio input[type="radio"],
|
|
||||||
label.tn-checkbox input[type="checkbox"] {
|
|
||||||
margin-right: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#left-pane input,
|
#left-pane input,
|
||||||
#left-pane select,
|
#left-pane select,
|
||||||
#left-pane textarea {
|
#left-pane textarea {
|
||||||
@@ -357,8 +327,7 @@ button kbd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu,
|
.dropdown-menu {
|
||||||
.tabulator-popup-container {
|
|
||||||
color: var(--menu-text-color) !important;
|
color: var(--menu-text-color) !important;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
background-color: var(--menu-background-color) !important;
|
background-color: var(--menu-background-color) !important;
|
||||||
@@ -373,8 +342,7 @@ button kbd {
|
|||||||
break-after: avoid;
|
break-after: avoid;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop .dropdown-menu,
|
body.desktop .dropdown-menu {
|
||||||
body.desktop .tabulator-popup-container {
|
|
||||||
border: 1px solid var(--dropdown-border-color);
|
border: 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;
|
||||||
@@ -382,7 +350,7 @@ body.desktop .tabulator-popup-container {
|
|||||||
|
|
||||||
@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);
|
||||||
@@ -417,8 +385,7 @@ body.desktop .tabulator-popup-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu a:hover:not(.disabled),
|
.dropdown-menu a:hover:not(.disabled),
|
||||||
.dropdown-item:hover:not(.disabled, .dropdown-item-container),
|
.dropdown-item:hover:not(.disabled, .dropdown-item-container) {
|
||||||
.tabulator-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;
|
||||||
@@ -442,20 +409,14 @@ body #context-menu-container .dropdown-item > span {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item span.keyboard-shortcut {
|
.dropdown-menu kbd {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu kbd {
|
|
||||||
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;
|
|
||||||
flex-grow: 1;
|
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item,
|
.dropdown-item,
|
||||||
@@ -684,10 +645,6 @@ table.promoted-attributes-in-tooltip th {
|
|||||||
z-index: calc(var(--ck-z-panel) - 1) !important;
|
z-index: calc(var(--ck-z-panel) - 1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip.tooltip-top {
|
|
||||||
z-index: 32767 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-trigger {
|
.tooltip-trigger {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -873,34 +830,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-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aa-dropdown-menu .aa-suggestion .search-result-title {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aa-dropdown-menu .aa-suggestion .search-result-attributes {
|
|
||||||
display: block;
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: var(--muted-text-color);
|
|
||||||
opacity: 0.6;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aa-dropdown-menu .aa-suggestion p {
|
.aa-dropdown-menu .aa-suggestion p {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -988,18 +921,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-font-family: var(--detail-font-family);
|
|
||||||
--ck-content-font-size: 1.1em;
|
|
||||||
--ck-content-font-color: var(--main-text-color);
|
|
||||||
--ck-content-line-height: var(--bs-body-line-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck-content .table table th {
|
.ck-content .table table th {
|
||||||
background-color: var(--accented-background-color);
|
background-color: var(--accented-background-color);
|
||||||
}
|
}
|
||||||
@@ -1134,7 +1055,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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ck-mentions .ck-button {
|
.ck-mentions .ck-button {
|
||||||
@@ -1243,10 +1163,6 @@ 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;
|
||||||
@@ -1291,14 +1207,12 @@ body.mobile .dropdown-submenu > .dropdown-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#context-menu-container,
|
#context-menu-container,
|
||||||
#context-menu-container .dropdown-menu,
|
#context-menu-container .dropdown-menu {
|
||||||
.tabulator-popup-container {
|
padding: 3px 0 0;
|
||||||
padding: 3px 0;
|
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#context-menu-container .dropdown-item,
|
#context-menu-container .dropdown-item {
|
||||||
.tabulator-menu .tabulator-menu-item {
|
|
||||||
padding: 0 7px 0 10px;
|
padding: 0 7px 0 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -1468,7 +1382,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: transparent;
|
background-color: var(--launcher-pane-background-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1776,6 +1690,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.note-split {
|
.note-split {
|
||||||
|
flex-basis: 0; /* so that each split has same width */
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
@@ -1813,12 +1728,16 @@ button.close:hover {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-section input[type="number"] {
|
.options-number-input {
|
||||||
/* overriding settings from .form-control */
|
/* overriding settings from .form-control */
|
||||||
width: 10em !important;
|
width: 10em !important;
|
||||||
flex-grow: 0 !important;
|
flex-grow: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options-mime-types {
|
||||||
|
column-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
}
|
}
|
||||||
@@ -1839,106 +1758,20 @@ textarea {
|
|||||||
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%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-results .aa-dropdown-menu .aa-suggestion:hover,
|
|
||||||
.jump-to-note-results .aa-dropdown-menu .aa-cursor {
|
|
||||||
background-color: var(--hover-item-background-color, #f8f9fa);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Command palette styling */
|
|
||||||
.jump-to-note-dialog .command-suggestion {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .aa-suggestion .command-suggestion,
|
|
||||||
.jump-to-note-dialog .aa-suggestion .command-suggestion div {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .aa-cursor .command-suggestion,
|
|
||||||
.jump-to-note-dialog .aa-suggestion:hover .command-suggestion {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .show-in-full-search,
|
|
||||||
.jump-to-note-results .show-in-full-search {
|
|
||||||
border-top: 1px solid var(--main-border-color);
|
|
||||||
padding-top: 12px;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-results .aa-suggestion .search-notes-action {
|
|
||||||
border-top: 1px solid var(--main-border-color);
|
|
||||||
margin-top: 8px;
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-results .aa-suggestion:has(.search-notes-action)::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .command-icon {
|
|
||||||
color: var(--muted-text-color);
|
|
||||||
font-size: 1.125rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-top: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .command-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .command-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .command-description {
|
|
||||||
font-size: 0.8em;
|
|
||||||
line-height: 1.3;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog kbd.command-shortcut {
|
|
||||||
background-color: transparent;
|
|
||||||
color: inherit;
|
|
||||||
opacity: 0.75;
|
|
||||||
font-family: inherit !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-table-placeholder {
|
.empty-table-placeholder {
|
||||||
@@ -1999,7 +1832,6 @@ body.zen #launcher-container,
|
|||||||
body.zen #launcher-pane,
|
body.zen #launcher-pane,
|
||||||
body.zen #left-pane,
|
body.zen #left-pane,
|
||||||
body.zen #right-pane,
|
body.zen #right-pane,
|
||||||
body.zen #mobile-sidebar-wrapper,
|
|
||||||
body.zen .tab-row-container,
|
body.zen .tab-row-container,
|
||||||
body.zen .tab-row-widget,
|
body.zen .tab-row-widget,
|
||||||
body.zen .ribbon-container:not(:has(.classic-toolbar-widget.visible)),
|
body.zen .ribbon-container:not(:has(.classic-toolbar-widget.visible)),
|
||||||
@@ -2007,8 +1839,7 @@ 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 .button-widget,
|
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 {
|
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2050,20 +1881,14 @@ body.zen .note-title-widget input {
|
|||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.zen #detail-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content renderer */
|
/* Content renderer */
|
||||||
|
|
||||||
footer.file-footer,
|
footer.file-footer {
|
||||||
footer.webview-footer {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer.file-footer button,
|
footer.file-footer button {
|
||||||
footer.webview-footer button {
|
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2370,21 +2195,3 @@ footer.webview-footer button {
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
.tabulator {
|
|
||||||
--table-background-color: var(--main-background-color);
|
|
||||||
|
|
||||||
--col-header-background-color: var(--main-background-color);
|
|
||||||
--col-header-hover-background-color: var(--accented-background-color);
|
|
||||||
--col-header-text-color: var(--main-text-color);
|
|
||||||
--col-header-arrow-active-color: var(--main-text-color);
|
|
||||||
--col-header-arrow-inactive-color: var(--more-accented-background-color);
|
|
||||||
--col-header-separator-border: none;
|
|
||||||
--col-header-bottom-border: 2px solid var(--main-border-color);
|
|
||||||
|
|
||||||
--row-background-color: var(--main-background-color);
|
|
||||||
--row-alternate-background-color: var(--main-background-color);
|
|
||||||
--row-moving-background-color: var(--accented-background-color);
|
|
||||||
--row-text-color: var(--main-text-color);
|
|
||||||
--row-delimiter-color: var(--more-accented-background-color);
|
|
||||||
|
|
||||||
--cell-horiz-padding-size: 8px;
|
|
||||||
--cell-vert-padding-size: 8px;
|
|
||||||
|
|
||||||
--cell-editable-hover-outline-color: var(--main-border-color);
|
|
||||||
--cell-read-only-text-color: var(--muted-text-color);
|
|
||||||
|
|
||||||
--cell-editing-border-color: var(--main-border-color);
|
|
||||||
--cell-editing-border-width: 2px;
|
|
||||||
--cell-editing-background-color: var(--ck-color-selector-focused-cell-background);
|
|
||||||
--cell-editing-text-color: initial;
|
|
||||||
|
|
||||||
background: unset;
|
|
||||||
border: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator .tabulator-tableholder .tabulator-table {
|
|
||||||
background: var(--table-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Column headers */
|
|
||||||
|
|
||||||
.tabulator div.tabulator-header {
|
|
||||||
border-bottom: var(--col-header-bottom-border);
|
|
||||||
background: var(--col-header-background-color);
|
|
||||||
color: var(--col-header-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator .tabulator-col-content {
|
|
||||||
padding: 8px 4px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) and (pointer: fine) {
|
|
||||||
.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover {
|
|
||||||
background-color: var(--col-header-hover-background-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator div.tabulator-header .tabulator-col.tabulator-moving {
|
|
||||||
border: none;
|
|
||||||
background: var(--col-header-hover-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {
|
|
||||||
border-bottom-color: var(--col-header-arrow-active-color);
|
|
||||||
border-top-color: var(--col-header-arrow-active-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort="none"] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {
|
|
||||||
border-bottom-color: var(--col-header-arrow-inactive-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
|
|
||||||
margin-left: var(--cell-editing-border-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator div.tabulator-header .tabulator-col,
|
|
||||||
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
|
|
||||||
background: var(--col-header-background-color);
|
|
||||||
border-right: var(--col-header-separator-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table body */
|
|
||||||
|
|
||||||
.tabulator-tableholder {
|
|
||||||
padding-top: 10px;
|
|
||||||
height: unset !important; /* Don't extend on the full height */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rows */
|
|
||||||
|
|
||||||
.tabulator-row .tabulator-cell {
|
|
||||||
padding: var(--cell-vert-padding-size) var(--cell-horiz-padding-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row .tabulator-cell input {
|
|
||||||
padding-left: var(--cell-horiz-padding-size) !important;
|
|
||||||
padding-right: var(--cell-horiz-padding-size) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row {
|
|
||||||
background: transparent;
|
|
||||||
border-top: none;
|
|
||||||
border-bottom: 1px solid var(--row-delimiter-color);
|
|
||||||
color: var(--row-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row.tabulator-row-odd {
|
|
||||||
background: var(--row-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row.tabulator-row-even {
|
|
||||||
background: var(--row-alternate-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row.tabulator-moving {
|
|
||||||
border-color: transparent;
|
|
||||||
background-color: var(--row-moving-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cell */
|
|
||||||
|
|
||||||
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
|
|
||||||
margin-right: var(--cell-editing-border-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left,
|
|
||||||
.tabulator-row .tabulator-cell {
|
|
||||||
border-right-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row .tabulator-cell:not(.tabulator-editable) {
|
|
||||||
color: var(--cell-read-only-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator:not(.tabulator-editing) .tabulator-row .tabulator-cell.tabulator-editable:hover {
|
|
||||||
outline: 2px solid var(--cell-editable-hover-outline-color);
|
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row .tabulator-cell.tabulator-editing {
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row:not(.tabulator-moving) .tabulator-cell.tabulator-editing {
|
|
||||||
outline: calc(var(--cell-editing-border-width) - 1px) solid var(--cell-editing-border-color);
|
|
||||||
border-color: var(--cell-editing-border-color);
|
|
||||||
background: var(--cell-editing-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-row:not(.tabulator-moving) .tabulator-cell.tabulator-editing > * {
|
|
||||||
color: var(--cell-editing-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator .tree-collapse,
|
|
||||||
.tabulator .tree-expand {
|
|
||||||
color: var(--row-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Align items without children/expander to the ones with. */
|
|
||||||
.tabulator-cell[tabulator-field="title"] > span:first-child, /* 1st level */
|
|
||||||
.tabulator-cell[tabulator-field="title"] > div:first-child + span { /* sub-level */
|
|
||||||
padding-left: 21px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Checkbox cells */
|
|
||||||
|
|
||||||
.tabulator .tabulator-cell:has(svg),
|
|
||||||
.tabulator .tabulator-cell:has(input[type="checkbox"]) {
|
|
||||||
padding-left: 8px;
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator .tabulator-cell input[type="checkbox"] {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator .tabulator-footer {
|
|
||||||
color: var(--main-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Context menus */
|
|
||||||
|
|
||||||
.tabulator-popup-container {
|
|
||||||
min-width: 10em;
|
|
||||||
border-radius: var(--bs-border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabulator-menu .tabulator-menu-item {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
color: var(--menu-text-color);
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
|
|
||||||
:root .tabulator .tabulator-footer {
|
|
||||||
border-top: unset;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
@@ -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,7 +144,6 @@
|
|||||||
--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;
|
||||||
@@ -157,8 +152,6 @@
|
|||||||
--launcher-pane-horiz-button-hover-background: #ffffff1c;
|
--launcher-pane-horiz-button-hover-background: #ffffff1c;
|
||||||
--launcher-pane-horiz-button-hover-shadow: unset;
|
--launcher-pane-horiz-button-hover-shadow: unset;
|
||||||
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
||||||
--launcher-pane-horiz-background-color-bgfx: #ffffff17; /* When background effects enabled */
|
|
||||||
--launcher-pane-horiz-border-color-bgfx: #00000080; /* When background effects enabled */
|
|
||||||
|
|
||||||
--protected-session-active-icon-color: #8edd8e;
|
--protected-session-active-icon-color: #8edd8e;
|
||||||
--sync-status-error-pulse-color: #f47871;
|
--sync-status-error-pulse-color: #f47871;
|
||||||
|
|||||||
@@ -13,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,17 +114,15 @@
|
|||||||
--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: inherit;
|
--left-pane-item-action-button-color: inherit;
|
||||||
--left-pane-item-action-button-hover-background: white;
|
--left-pane-item-action-button-hover-background: white;
|
||||||
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
|
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
|
||||||
@@ -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,8 +145,6 @@
|
|||||||
--launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background);
|
--launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background);
|
||||||
--launcher-pane-horiz-button-hover-shadow: unset;
|
--launcher-pane-horiz-button-hover-shadow: unset;
|
||||||
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
||||||
--launcher-pane-horiz-background-color-bgfx: #ffffffb3; /* When background effects enabled */
|
|
||||||
--launcher-pane-horiz-border-color-bgfx: #00000026; /* When background effects enabled */
|
|
||||||
|
|
||||||
--protected-session-active-icon-color: #16b516;
|
--protected-session-active-icon-color: #16b516;
|
||||||
--sync-status-error-pulse-color: #ff5528;
|
--sync-status-error-pulse-color: #ff5528;
|
||||||
|
|||||||
@@ -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/collections/table.css);
|
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Inter";
|
font-family: "Inter";
|
||||||
@@ -83,12 +82,6 @@
|
|||||||
--tab-note-icons: true;
|
--tab-note-icons: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.backdrop-effects-disabled {
|
|
||||||
/* Backdrop effects are disabled, replace the menu background color with the
|
|
||||||
* no-backdrop fallback color */
|
|
||||||
--menu-background-color: var(--menu-background-color-no-backdrop);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MENUS
|
* MENUS
|
||||||
*
|
*
|
||||||
@@ -190,24 +183,20 @@ html body .dropdown-item[disabled] {
|
|||||||
|
|
||||||
/* Menu item icon */
|
/* Menu item icon */
|
||||||
.dropdown-item .bx {
|
.dropdown-item .bx {
|
||||||
translate: 0 var(--menu-item-icon-vert-offset);
|
transform: translateY(var(--menu-item-icon-vert-offset));
|
||||||
color: var(--menu-item-icon-color) !important;
|
color: var(--menu-item-icon-color) !important;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Menu item keyboard shortcut */
|
/* Menu item keyboard shortcut */
|
||||||
.dropdown-item kbd {
|
.dropdown-item kbd {
|
||||||
|
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 {
|
|
||||||
color: var(--menu-item-keyboard-shortcut-color) !important;
|
|
||||||
margin-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-divider {
|
.dropdown-divider {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-color: transparent !important;
|
border-color: transparent !important;
|
||||||
@@ -329,8 +318,6 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
|
|||||||
|
|
||||||
#toast-container .toast .toast-body {
|
#toast-container .toast .toast-body {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -470,11 +457,6 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-list-wrapper .note-book-card .note-book-content.type-image .rendered-content,
|
|
||||||
.note-list-wrapper .note-book-card .note-book-content.type-pdf .rendered-content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-list-wrapper .note-book-card .note-book-content .rendered-content.text-with-ellipsis {
|
.note-list-wrapper .note-book-card .note-book-content .rendered-content.text-with-ellipsis {
|
||||||
padding: 1rem !important;
|
padding: 1rem !important;
|
||||||
}
|
}
|
||||||
@@ -542,9 +524,10 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,15 +128,10 @@ div.tn-tool-dialog {
|
|||||||
|
|
||||||
.jump-to-note-dialog .modal-header {
|
.jump-to-note-dialog .modal-header {
|
||||||
padding: unset !important;
|
padding: unset !important;
|
||||||
padding-bottom: 26px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.jump-to-note-dialog .modal-body {
|
.jump-to-note-dialog .modal-body {
|
||||||
padding: 0 !important;
|
padding: 26px 0 !important;
|
||||||
}
|
|
||||||
|
|
||||||
.jump-to-note-dialog .modal-footer {
|
|
||||||
padding-top: 26px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search box wrapper */
|
/* Search box wrapper */
|
||||||
@@ -233,16 +228,16 @@ div.tn-tool-dialog {
|
|||||||
|
|
||||||
/* Item title link */
|
/* Item title link */
|
||||||
|
|
||||||
.recent-changes-content ul li a {
|
.recent-changes-content ul li .note-title a {
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recent-changes-content ul li a:hover {
|
.recent-changes-content ul li .note-title a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Item title for deleted notes */
|
/* Item title for deleted notes */
|
||||||
.recent-changes-content ul li.deleted-note .note-title {
|
.recent-changes-content ul li.deleted-note .note-title > .note-title {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,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;
|
||||||
@@ -175,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);
|
||||||
@@ -192,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:focus,
|
|
||||||
:root input.ck.ck-input-number: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;
|
||||||
@@ -469,7 +455,6 @@ optgroup {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: var(--box-size);
|
width: var(--box-size);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: unset;
|
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
:root .tabulator {
|
|
||||||
--col-header-hover-background-color: var(--hover-item-background-color);
|
|
||||||
--col-header-arrow-active-color: var(--active-item-text-color);
|
|
||||||
--col-header-arrow-inactive-color: var(--main-border-color);
|
|
||||||
|
|
||||||
--row-moving-background-color: var(--more-accented-background-color);
|
|
||||||
|
|
||||||
--cell-editable-hover-outline-color: var(--input-focus-outline-color);
|
|
||||||
|
|
||||||
--cell-editing-border-color: var(--input-focus-outline-color);
|
|
||||||
--cell-editing-background-color: var(--input-background-color);
|
|
||||||
--cell-editing-text-color: var(--input-text-color);
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--ck-font-face: var(--main-font-family);
|
--ck-font-face: var(--main-font-family);
|
||||||
--ck-input-label-height: 1.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -308,11 +307,6 @@
|
|||||||
fill: black !important;
|
fill: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hex color input box prefix */
|
|
||||||
:root .ck.ck-color-selector .ck-color-picker__hash-view {
|
|
||||||
margin-top: var(--ck-input-label-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Numbered list */
|
/* Numbered list */
|
||||||
|
|
||||||
:root .ck.ck-list-properties_with-numbered-properties .ck.ck-list-styles-list {
|
:root .ck.ck-list-properties_with-numbered-properties .ck.ck-list-styles-list {
|
||||||
@@ -369,86 +363,19 @@
|
|||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Text snippet dropdown */
|
/* Action buttons */
|
||||||
|
|
||||||
div.ck-template-form {
|
:root .ck-link-actions button.ck-button,
|
||||||
padding: 8px;
|
:root .ck-link-form button.ck-button {
|
||||||
|
--ck-border-radius: 6px;
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.ck-template-form .ck-labeled-field-view {
|
:root .ck-link-actions button.ck-button:hover,
|
||||||
margin-bottom: 8px;
|
:root .ck-link-form button.ck-button:hover {
|
||||||
}
|
background: var(--hover-item-background-color);
|
||||||
|
|
||||||
/* Template item */
|
|
||||||
|
|
||||||
:root div.ck-template-form li.ck-list__item button.ck-template-button {
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Template icon */
|
|
||||||
:root .ck-template-form .ck-button__icon {
|
|
||||||
--ck-spacing-medium: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root div.ck-template-form .note-icon {
|
|
||||||
color: var(--menu-item-icon-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Template name */
|
|
||||||
div.ck-template-form .ck-template-form__text-part {
|
|
||||||
color: var(--hover-item-text-color);
|
|
||||||
font-size: .9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ck-template-form .ck-template-form__text-part mark {
|
|
||||||
background: unset;
|
|
||||||
color: var(--quick-search-result-highlight-color);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Template description */
|
|
||||||
:root div.ck-template-form .ck-template-form__description {
|
|
||||||
opacity: .5;
|
|
||||||
font-size: .9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Messages */
|
|
||||||
div.ck-template-form .ck-search__info > span {
|
|
||||||
line-height: initial;
|
|
||||||
color: var(--muted-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.ck-template-form .ck-search__info span:nth-child(2) {
|
|
||||||
display: block;
|
|
||||||
opacity: .5;
|
|
||||||
margin-top: 8px;
|
|
||||||
font-size: .9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Link dropdown */
|
|
||||||
|
|
||||||
:root .ck.ck-form.ck-link-form ul.ck-link-form__providers-list {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Math popup */
|
|
||||||
|
|
||||||
.ck-math-form .ck-labeled-field-view {
|
|
||||||
--ck-input-label-height: 0;
|
|
||||||
|
|
||||||
margin-inline-end: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Emoji dropdown */
|
|
||||||
|
|
||||||
.ck-emoji-picker-form .ck-emoji__search .ck-button_with-text:not(.ck-list-item-button) {
|
|
||||||
margin-top: var(--ck-input-label-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find and replace dialog */
|
|
||||||
|
|
||||||
.ck-find-and-replace-form .ck-find-and-replace-form__inputs button {
|
|
||||||
margin-top: var(--ck-input-label-height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mention list (the autocompletion list for emojis, labels and relations) */
|
/* Mention list (the autocompletion list for emojis, labels and relations) */
|
||||||
@@ -465,58 +392,6 @@ div.ck-template-form .ck-search__info span:nth-child(2) {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* FORMS
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Buttons
|
|
||||||
*/
|
|
||||||
|
|
||||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck-button_with-text {
|
|
||||||
--ck-color-text: var(--cmd-button-text-color);
|
|
||||||
|
|
||||||
min-width: 60px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Text boxes
|
|
||||||
*/
|
|
||||||
|
|
||||||
.ck.ck-labeled-field-view {
|
|
||||||
padding-top: var(--ck-input-label-height) !important; /* Create space for the label */
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-labeled-field-view > .ck.ck-labeled-field-view__input-wrapper > label.ck.ck-label {
|
|
||||||
/* Move the label above the text box regardless of the text box state */
|
|
||||||
transform: translate(0, calc(-.2em - var(--ck-input-label-height))) !important;
|
|
||||||
|
|
||||||
padding-left: 0 !important;
|
|
||||||
background: transparent;
|
|
||||||
font-size: .85em;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root input.ck.ck-input-text[readonly="true"] {
|
|
||||||
cursor: not-allowed;
|
|
||||||
background: var(--input-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms */
|
|
||||||
|
|
||||||
:root .ck.ck-form__row.ck-form__row_with-submit > :not(:first-child) {
|
|
||||||
margin-inline-start: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-form__row_with-submit button {
|
|
||||||
margin-top: var(--ck-input-label-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-form__header {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EDITOR'S CONTENT
|
* EDITOR'S CONTENT
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -96,6 +96,7 @@
|
|||||||
background: var(--background) !important;
|
background: var(--background) !important;
|
||||||
color: var(--color) !important;
|
color: var(--color) !important;
|
||||||
line-height: unset;
|
line-height: unset;
|
||||||
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sql-table-schemas-widget .sql-table-schemas button:hover,
|
.sql-table-schemas-widget .sql-table-schemas button:hover,
|
||||||
@@ -105,6 +106,18 @@
|
|||||||
--color: var(--main-text-color);
|
--color: var(--main-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tooltip */
|
||||||
|
|
||||||
|
.tooltip .table-schema {
|
||||||
|
font-family: var(--monospace-font-family);
|
||||||
|
font-size: .85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data type */
|
||||||
|
.tooltip .table-schema td:nth-child(2) {
|
||||||
|
color: var(--muted-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE MAP
|
* NOTE MAP
|
||||||
*/
|
*/
|
||||||
@@ -169,6 +182,8 @@ div.note-detail-empty {
|
|||||||
|
|
||||||
.options-section:not(.tn-no-card) {
|
.options-section:not(.tn-no-card) {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
min-width: var(--options-card-min-width);
|
||||||
|
max-width: var(--options-card-max-width);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 1px solid var(--card-border-color) !important;
|
border: 1px solid var(--card-border-color) !important;
|
||||||
box-shadow: var(--card-box-shadow);
|
box-shadow: var(--card-box-shadow);
|
||||||
@@ -177,11 +192,6 @@ div.note-detail-empty {
|
|||||||
margin-bottom: calc(var(--options-title-offset) + 26px) !important;
|
margin-bottom: calc(var(--options-title-offset) + 26px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop .option-section:not(.tn-no-card) {
|
|
||||||
min-width: var(--options-card-min-width);
|
|
||||||
max-width: var(--options-card-max-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-content-widget-content.options {
|
.note-detail-content-widget-content.options {
|
||||||
--default-padding: 15px;
|
--default-padding: 15px;
|
||||||
padding-top: calc(var(--default-padding) + var(--options-title-offset) + var(--options-title-font-size));
|
padding-top: calc(var(--default-padding) + var(--options-title-offset) + var(--options-title-font-size));
|
||||||
@@ -223,6 +233,11 @@ body.desktop .option-section:not(.tn-no-card) {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options-section .options-mime-types {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.options-section .form-group {
|
.options-section .form-group {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,23 +36,32 @@ body.mobile {
|
|||||||
|
|
||||||
/* #region Mica */
|
/* #region Mica */
|
||||||
body.background-effects.platform-win32 {
|
body.background-effects.platform-win32 {
|
||||||
--background-material: tabbed;
|
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.15);
|
||||||
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
|
--launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.7);
|
||||||
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
|
--launcher-pane-vert-background-color: rgba(255, 255, 255, 0.055);
|
||||||
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
|
--tab-background-color: transparent;
|
||||||
--tab-background-color: var(--window-background-color-bgfx);
|
--new-tab-button-background: transparent;
|
||||||
--new-tab-button-background: var(--window-background-color-bgfx);
|
|
||||||
--active-tab-background-color: var(--launcher-pane-horiz-background-color);
|
--active-tab-background-color: var(--launcher-pane-horiz-background-color);
|
||||||
|
--background-material: tabbed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body.background-effects.platform-win32 {
|
||||||
|
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.5);
|
||||||
|
--launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.09);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.background-effects.platform-win32.layout-vertical {
|
body.background-effects.platform-win32.layout-vertical {
|
||||||
--left-pane-background-color: var(--window-background-color-bgfx);
|
--left-pane-background-color: transparent;
|
||||||
|
--left-pane-item-hover-background: rgba(127, 127, 127, 0.05);
|
||||||
--background-material: mica;
|
--background-material: mica;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.background-effects.platform-win32,
|
body.background-effects.platform-win32,
|
||||||
body.background-effects.platform-win32 #root-widget {
|
body.background-effects.platform-win32 #root-widget,
|
||||||
background: var(--window-background-color-bgfx) !important;
|
body.background-effects.platform-win32 #launcher-pane .launcher-button {
|
||||||
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container,
|
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container,
|
||||||
@@ -81,7 +90,7 @@ body.background-effects.zen #root-widget {
|
|||||||
* Gutter
|
* Gutter
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.gutter {
|
.gutter {
|
||||||
background: var(--gutter-color) !important;
|
background: var(--gutter-color) !important;
|
||||||
transition: background 150ms ease-out;
|
transition: background 150ms ease-out;
|
||||||
}
|
}
|
||||||
@@ -321,6 +330,7 @@ body.layout-horizontal > .horizontal {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.calendar-dropdown-widget {
|
.calendar-dropdown-widget {
|
||||||
|
width: unset !important;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
color: var(--calendar-color);
|
color: var(--calendar-color);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -566,18 +576,25 @@ div.quick-search .search-button.show {
|
|||||||
* Quick search results
|
* Quick search results
|
||||||
*/
|
*/
|
||||||
|
|
||||||
div.quick-search .dropdown-menu {
|
|
||||||
--quick-search-item-delimiter-color: transparent;
|
|
||||||
--menu-item-icon-vert-offset: -.065em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Item */
|
/* Item */
|
||||||
.quick-search .dropdown-menu *.dropdown-item {
|
.quick-search .dropdown-menu *.dropdown-item {
|
||||||
padding: 8px 12px !important;
|
padding: 8px 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-search .quick-search-item-icon {
|
/* Note icon */
|
||||||
vertical-align: text-bottom;
|
.quick-search .dropdown-menu .dropdown-item > .bx {
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Note title */
|
||||||
|
.quick-search .dropdown-menu .dropdown-item > a {
|
||||||
|
color: var(--menu-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-search .dropdown-menu .dropdown-item > a:hover {
|
||||||
|
--hover-item-background-color: transparent;
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note path */
|
/* Note path */
|
||||||
@@ -588,24 +605,6 @@ div.quick-search .dropdown-menu {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note content snippet */
|
|
||||||
:root .quick-search .search-result-content {
|
|
||||||
background-color: var(--quick-search-result-content-background);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Highlighted search terms */
|
|
||||||
:root .quick-search .search-result-title b,
|
|
||||||
:root .quick-search .search-result-content b,
|
|
||||||
:root .quick-search .search-result-attributes b {
|
|
||||||
color: var(--quick-search-result-highlight-color);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-search div.dropdown-divider {
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TREE PANE
|
* TREE PANE
|
||||||
*/
|
*/
|
||||||
@@ -878,80 +877,6 @@ body.layout-horizontal .tab-row-container {
|
|||||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container {
|
|
||||||
border-bottom: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .note-tab-wrapper {
|
|
||||||
top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .toggle-button {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .toggle-button:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: -10px;
|
|
||||||
right: -10px;
|
|
||||||
top: 29px;
|
|
||||||
height: 1px;
|
|
||||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-left,
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-right {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-left:after,
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-right:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
height: 1px;
|
|
||||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .note-tab[active]:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: -32768px;
|
|
||||||
top: var(--tab-height);
|
|
||||||
right: calc(100% - 1px);
|
|
||||||
height: 1px;
|
|
||||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .note-tab[active]:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 100%;
|
|
||||||
top: var(--tab-height);
|
|
||||||
right: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 1px;
|
|
||||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.electron.background-effects.layout-horizontal .tab-row-container .note-new-tab:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: -4px;
|
|
||||||
top: calc(var(--tab-height), -1);
|
|
||||||
right: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 1px;
|
|
||||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.layout-vertical.electron.platform-darwin .tab-row-container {
|
body.layout-vertical.electron.platform-darwin .tab-row-container {
|
||||||
border-bottom: 1px solid var(--subtle-border-color);
|
border-bottom: 1px solid var(--subtle-border-color);
|
||||||
}
|
}
|
||||||
@@ -1167,11 +1092,6 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
|
|||||||
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
|
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-note-container-widget > .gutter {
|
|
||||||
background: var(--root-background) !important;
|
|
||||||
transition: background 150ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ribbon & note header
|
* Ribbon & note header
|
||||||
*/
|
*/
|
||||||
@@ -1180,6 +1100,10 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
|
|||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-split:not(.hidden-ext) + .note-split:not(.hidden-ext) {
|
||||||
|
border-left: 4px solid var(--root-background);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes note-entrance {
|
@keyframes note-entrance {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -1451,7 +1375,7 @@ div.floating-buttons-children .floating-button:active {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* The first visible floating button */
|
/* The first visible floating button */
|
||||||
div.floating-buttons-children > *:first-child {
|
div.floating-buttons-children > *:nth-child(1 of .visible) {
|
||||||
--border-radius: var(--border-radius-size) 0 0 var(--border-radius-size);
|
--border-radius: var(--border-radius-size) 0 0 var(--border-radius-size);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
@@ -1553,6 +1477,13 @@ div.floating-buttons-children .close-floating-buttons:has(.close-floating-button
|
|||||||
padding-inline-start: 8px;
|
padding-inline-start: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Copy image reference */
|
||||||
|
|
||||||
|
.floating-buttons .copy-image-reference-button .hidden-image-copy {
|
||||||
|
/* Take out of the the hidden image from flexbox to prevent the layout being affected */
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
/* Code, relation map buttons */
|
/* Code, relation map buttons */
|
||||||
|
|
||||||
.floating-buttons .code-buttons-widget,
|
.floating-buttons .code-buttons-widget,
|
||||||
@@ -1748,41 +1679,3 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Canvas **/
|
|
||||||
|
|
||||||
.excalidraw {
|
|
||||||
--border-radius-lg: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw .Island {
|
|
||||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw .Island.App-toolbar {
|
|
||||||
--island-bg-color: var(--floating-button-background-color);
|
|
||||||
--shadow-island: 1px 1px 1px var(--floating-button-shadow-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw .dropdown-menu {
|
|
||||||
border: unset !important;
|
|
||||||
box-shadow: unset !important;
|
|
||||||
background-color: transparent !important;
|
|
||||||
--island-bg-color: var(--menu-background-color);
|
|
||||||
--shadow-island: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
|
|
||||||
--default-border-color: var(--bs-dropdown-divider-bg);
|
|
||||||
--button-hover-bg: var(--hover-item-background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw .dropdown-menu .dropdown-menu-container {
|
|
||||||
border-radius: var(--dropdown-border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw .dropdown-menu .dropdown-menu-container > div:not([class]):not(:last-child) {
|
|
||||||
margin-left: calc(var(--padding) * var(--space-factor) * -1) !important;
|
|
||||||
margin-right: calc(var(--padding) * var(--space-factor) * -1) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw .dropdown-menu:before {
|
|
||||||
content: unset !important;
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
{
|
|
||||||
"about": {
|
|
||||||
"title": "Sobre Trilium Notes",
|
|
||||||
"homepage": "Pàgina principal:"
|
|
||||||
},
|
|
||||||
"add_link": {
|
|
||||||
"note": "Nota"
|
|
||||||
},
|
|
||||||
"branch_prefix": {
|
|
||||||
"prefix": "Prefix: ",
|
|
||||||
"save": "Desa"
|
|
||||||
},
|
|
||||||
"bulk_actions": {
|
|
||||||
"labels": "Etiquetes",
|
|
||||||
"relations": "Relacions",
|
|
||||||
"notes": "Notes",
|
|
||||||
"other": "Altres"
|
|
||||||
},
|
|
||||||
"confirm": {
|
|
||||||
"confirmation": "Confirmació",
|
|
||||||
"cancel": "Cancel·la",
|
|
||||||
"ok": "OK"
|
|
||||||
},
|
|
||||||
"delete_notes": {
|
|
||||||
"close": "Tanca",
|
|
||||||
"cancel": "Cancel·la",
|
|
||||||
"ok": "OK"
|
|
||||||
},
|
|
||||||
"export": {
|
|
||||||
"close": "Tanca",
|
|
||||||
"export": "Exporta"
|
|
||||||
},
|
|
||||||
"help": {
|
|
||||||
"troubleshooting": "Solució de problemes",
|
|
||||||
"other": "Altres"
|
|
||||||
},
|
|
||||||
"import": {
|
|
||||||
"options": "Opcions",
|
|
||||||
"import": "Importa"
|
|
||||||
},
|
|
||||||
"include_note": {
|
|
||||||
"label_note": "Nota"
|
|
||||||
},
|
|
||||||
"info": {
|
|
||||||
"closeButton": "Tanca",
|
|
||||||
"okButton": "OK"
|
|
||||||
},
|
|
||||||
"note_type_chooser": {
|
|
||||||
"templates": "Plantilles:"
|
|
||||||
},
|
|
||||||
"prompt": {
|
|
||||||
"title": "Sol·licitud",
|
|
||||||
"defaultTitle": "Sol·licitud"
|
|
||||||
},
|
|
||||||
"protected_session_password": {
|
|
||||||
"close_label": "Tanca"
|
|
||||||
},
|
|
||||||
"recent_changes": {
|
|
||||||
"undelete_link": "recuperar"
|
|
||||||
},
|
|
||||||
"revisions": {
|
|
||||||
"restore_button": "Restaura",
|
|
||||||
"delete_button": "Suprimeix",
|
|
||||||
"download_button": "Descarrega",
|
|
||||||
"mime": "MIME: ",
|
|
||||||
"preview": "Vista prèvia:"
|
|
||||||
},
|
|
||||||
"sort_child_notes": {
|
|
||||||
"title": "títol",
|
|
||||||
"ascending": "ascendent",
|
|
||||||
"descending": "descendent",
|
|
||||||
"folders": "Carpetes"
|
|
||||||
},
|
|
||||||
"upload_attachments": {
|
|
||||||
"options": "Opcions",
|
|
||||||
"upload": "Puja"
|
|
||||||
},
|
|
||||||
"attribute_detail": {
|
|
||||||
"name": "Nom",
|
|
||||||
"value": "Valor",
|
|
||||||
"promoted": "Destacat",
|
|
||||||
"promoted_alias": "Àlies",
|
|
||||||
"multiplicity": "Multiplicitat",
|
|
||||||
"label_type": "Tipus",
|
|
||||||
"text": "Text",
|
|
||||||
"number": "Número",
|
|
||||||
"boolean": "Booleà",
|
|
||||||
"date": "Data",
|
|
||||||
"time": "Hora",
|
|
||||||
"url": "URL",
|
|
||||||
"precision": "Precisió",
|
|
||||||
"digits": "dígits",
|
|
||||||
"inheritable": "Heretable",
|
|
||||||
"delete": "Suprimeix",
|
|
||||||
"color_type": "Color"
|
|
||||||
},
|
|
||||||
"rename_label": {
|
|
||||||
"to": "Per"
|
|
||||||
},
|
|
||||||
"move_note": {
|
|
||||||
"to": "a"
|
|
||||||
},
|
|
||||||
"add_relation": {
|
|
||||||
"to": "a"
|
|
||||||
},
|
|
||||||
"rename_relation": {
|
|
||||||
"to": "Per"
|
|
||||||
},
|
|
||||||
"update_relation_target": {
|
|
||||||
"to": "a"
|
|
||||||
},
|
|
||||||
"attachments_actions": {
|
|
||||||
"download": "Descarrega"
|
|
||||||
},
|
|
||||||
"calendar": {
|
|
||||||
"mon": "Dl",
|
|
||||||
"tue": "Dt",
|
|
||||||
"wed": "dc",
|
|
||||||
"thu": "Dj",
|
|
||||||
"fri": "Dv",
|
|
||||||
"sat": "Ds",
|
|
||||||
"sun": "Dg",
|
|
||||||
"january": "Gener",
|
|
||||||
"febuary": "Febrer",
|
|
||||||
"march": "Març",
|
|
||||||
"april": "Abril",
|
|
||||||
"may": "Maig",
|
|
||||||
"june": "Juny",
|
|
||||||
"july": "Juliol",
|
|
||||||
"august": "Agost",
|
|
||||||
"september": "Setembre",
|
|
||||||
"october": "Octubre",
|
|
||||||
"november": "Novembre",
|
|
||||||
"december": "Desembre"
|
|
||||||
},
|
|
||||||
"global_menu": {
|
|
||||||
"menu": "Menú",
|
|
||||||
"options": "Opcions",
|
|
||||||
"zoom": "Zoom",
|
|
||||||
"advanced": "Avançat",
|
|
||||||
"logout": "Tanca la sessió"
|
|
||||||
},
|
|
||||||
"zpetne_odkazy": {
|
|
||||||
"relation": "relació"
|
|
||||||
},
|
|
||||||
"note_icon": {
|
|
||||||
"category": "Categoria:",
|
|
||||||
"search": "Cerca:"
|
|
||||||
},
|
|
||||||
"basic_properties": {
|
|
||||||
"editable": "Editable",
|
|
||||||
"language": "Llengua"
|
|
||||||
},
|
|
||||||
"book_properties": {
|
|
||||||
"grid": "Graella",
|
|
||||||
"list": "Llista",
|
|
||||||
"collapse": "Replega",
|
|
||||||
"expand": "Desplega",
|
|
||||||
"calendar": "Calendari",
|
|
||||||
"table": "Taula",
|
|
||||||
"board": "Tauler"
|
|
||||||
},
|
|
||||||
"edited_notes": {
|
|
||||||
"deleted": "(suprimit)"
|
|
||||||
},
|
|
||||||
"file_properties": {
|
|
||||||
"download": "Descarrega",
|
|
||||||
"open": "Obre",
|
|
||||||
"title": "Fitxer"
|
|
||||||
},
|
|
||||||
"image_properties": {
|
|
||||||
"download": "Descarrega",
|
|
||||||
"open": "Obre",
|
|
||||||
"title": "Imatge"
|
|
||||||
},
|
|
||||||
"note_info_widget": {
|
|
||||||
"created": "Creat",
|
|
||||||
"modified": "Modificat",
|
|
||||||
"type": "Tipus",
|
|
||||||
"calculate": "calcula"
|
|
||||||
},
|
|
||||||
"note_paths": {
|
|
||||||
"archived": "Arxivat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user