mirror of
https://github.com/zadam/trilium.git
synced 2025-12-24 09:10:02 +01:00
Compare commits
15 Commits
fix/mkdocs
...
feature/dx
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41a8424b6c | ||
|
|
5bc16cfe38 | ||
|
|
e03fa2485c | ||
|
|
00c0860b32 | ||
|
|
e3b2e92d2d | ||
|
|
a000b8a28b | ||
|
|
ab27d36dd3 | ||
|
|
8fb078029c | ||
|
|
5a29c831a1 | ||
|
|
a2c49711bc | ||
|
|
509fce54f9 | ||
|
|
39ae7dda6c | ||
|
|
0dafb86012 | ||
|
|
20bdae4a84 | ||
|
|
a969a3c71c |
2
.github/actions/build-electron/action.yml
vendored
2
.github/actions/build-electron/action.yml
vendored
@@ -86,7 +86,7 @@ runs:
|
||||
APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }}
|
||||
WINDOWS_SIGN_EXECUTABLE: ${{ env.WINDOWS_SIGN_EXECUTABLE }}
|
||||
TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}
|
||||
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
|
||||
run: pnpm nx --project=desktop electron-forge:make -- --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
|
||||
|
||||
# Add DMG signing step
|
||||
- name: Sign DMG
|
||||
|
||||
4
.github/actions/build-server/action.yml
vendored
4
.github/actions/build-server/action.yml
vendored
@@ -10,7 +10,7 @@ runs:
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
@@ -23,7 +23,7 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm run chore:update-build-info
|
||||
pnpm run --filter server package
|
||||
pnpm nx --project=server package
|
||||
- name: Prepare artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
37
.github/workflows/dev.yml
vendored
37
.github/workflows/dev.yml
vendored
@@ -19,24 +19,45 @@ permissions:
|
||||
pull-requests: write # for PR comments
|
||||
|
||||
jobs:
|
||||
check-affected:
|
||||
name: Check affected jobs (NX)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # needed for https://github.com/marketplace/actions/nx-set-shas
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- uses: nrwl/nx-set-shas@v4
|
||||
- name: Check affected
|
||||
run: pnpm nx affected --verbose -t typecheck build rebuild-deps test-build
|
||||
|
||||
test_dev:
|
||||
name: Test development
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- check-affected
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: Run the unit tests
|
||||
run: pnpm run test:all
|
||||
|
||||
@@ -45,6 +66,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test_dev
|
||||
- check-affected
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
@@ -53,7 +75,7 @@ jobs:
|
||||
- name: Update build info
|
||||
run: pnpm run chore:update-build-info
|
||||
- name: Trigger client build
|
||||
run: pnpm client:build
|
||||
run: pnpm nx run client:build
|
||||
- name: Send client bundle stats to RelativeCI
|
||||
if: false
|
||||
uses: relative-ci/agent-action@v3
|
||||
@@ -61,7 +83,7 @@ jobs:
|
||||
webpackStatsFile: ./apps/client/dist/webpack-stats.json
|
||||
key: ${{ secrets.RELATIVE_CI_CLIENT_KEY }}
|
||||
- name: Trigger server build
|
||||
run: pnpm run server:build
|
||||
run: pnpm nx run server:build
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
@@ -73,6 +95,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build_docker
|
||||
- check-affected
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -89,7 +112,7 @@ jobs:
|
||||
- name: Update build info
|
||||
run: pnpm run chore:update-build-info
|
||||
- name: Trigger build
|
||||
run: pnpm server:build
|
||||
run: pnpm nx run server:build
|
||||
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
|
||||
6
.github/workflows/main-docker.yml
vendored
6
.github/workflows/main-docker.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
require-healthy: true
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpm --filter=server-e2e e2e
|
||||
run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpm exec nx run server-e2e:e2e
|
||||
|
||||
- name: Upload Playwright trace
|
||||
if: failure()
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
7
.github/workflows/nightly.yml
vendored
7
.github/workflows/nightly.yml
vendored
@@ -51,12 +51,13 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- uses: nrwl/nx-set-shas@v4
|
||||
- name: Update nightly version
|
||||
run: npm run chore:ci-update-nightly-version
|
||||
- name: Run the build
|
||||
@@ -78,7 +79,7 @@ jobs:
|
||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2.3.3
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
make_latest: false
|
||||
@@ -119,7 +120,7 @@ jobs:
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2.3.3
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
make_latest: false
|
||||
|
||||
22
.github/workflows/playwright.yml
vendored
22
.github/workflows/playwright.yml
vendored
@@ -19,8 +19,14 @@ jobs:
|
||||
filter: tree:0
|
||||
fetch-depth: 0
|
||||
|
||||
# This enables task distribution via Nx Cloud
|
||||
# Run this command as early as possible, before dependencies are installed
|
||||
# Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun
|
||||
# Connect your workspace by running "nx connect" and uncomment this line to enable task distribution
|
||||
# - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="e2e-ci"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
@@ -28,12 +34,10 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: pnpm exec playwright install --with-deps
|
||||
- uses: nrwl/nx-set-shas@v4
|
||||
|
||||
- run: pnpm --filter server-e2e e2e
|
||||
|
||||
- name: Upload test report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: e2e report
|
||||
path: apps/server-e2e/test-output
|
||||
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
|
||||
# - run: npx nx-cloud record -- echo Hello World
|
||||
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
|
||||
# When you enable task distribution, run the e2e-ci task instead of e2e
|
||||
- run: pnpm exec nx affected -t e2e --exclude desktop-e2e
|
||||
|
||||
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -35,12 +35,13 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- uses: nrwl/nx-set-shas@v4
|
||||
- name: Run the build
|
||||
uses: ./.github/actions/build-electron
|
||||
with:
|
||||
@@ -114,7 +115,7 @@ jobs:
|
||||
path: upload
|
||||
|
||||
- name: Publish stable release
|
||||
uses: softprops/action-gh-release@v2.3.3
|
||||
uses: softprops/action-gh-release@v2.3.2
|
||||
with:
|
||||
draft: false
|
||||
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md
|
||||
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
/.cache
|
||||
|
||||
.github/instructions/nx.instructions.md
|
||||
|
||||
# compiled output
|
||||
dist
|
||||
@@ -33,11 +34,14 @@ testem.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
.nx/cache
|
||||
.nx/workspace-data
|
||||
|
||||
vite.config.*.timestamp*
|
||||
vitest.config.*.timestamp*
|
||||
test-output
|
||||
|
||||
apps/*/data*
|
||||
apps/*/data
|
||||
apps/*/out
|
||||
upload
|
||||
|
||||
@@ -45,7 +49,4 @@ upload
|
||||
*.tsbuildinfo
|
||||
|
||||
/result
|
||||
.svelte-kit
|
||||
|
||||
# docs
|
||||
site/
|
||||
.svelte-kit
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -5,6 +5,7 @@
|
||||
"lokalise.i18n-ally",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-playwright.playwright",
|
||||
"nrwl.angular-console",
|
||||
"redhat.vscode-yaml",
|
||||
"tobermory.es6-string-html",
|
||||
"vitest.explorer",
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -35,5 +35,6 @@
|
||||
"docs/**/*.png": true,
|
||||
"apps/server/src/assets/doc_notes/**": true,
|
||||
"apps/edit-docs/demo/**": true
|
||||
}
|
||||
},
|
||||
"nxConsole.generateAiAgentRules": true
|
||||
}
|
||||
13
CLAUDE.md
13
CLAUDE.md
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Overview
|
||||
|
||||
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using pnpm, with multiple applications and shared packages.
|
||||
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using NX, with multiple applications and shared packages.
|
||||
|
||||
## Development Commands
|
||||
|
||||
@@ -14,9 +14,12 @@ Trilium Notes is a hierarchical note-taking application with advanced features l
|
||||
|
||||
### Running Applications
|
||||
- `pnpm run server:start` - Start development server (http://localhost:8080)
|
||||
- `pnpm nx run server:serve` - Alternative server start command
|
||||
- `pnpm nx run desktop:serve` - Run desktop Electron app
|
||||
- `pnpm run server:start-prod` - Run server in production mode
|
||||
|
||||
### Building
|
||||
- `pnpm nx build <project>` - Build specific project (server, client, desktop, etc.)
|
||||
- `pnpm run client:build` - Build client application
|
||||
- `pnpm run server:build` - Build server application
|
||||
- `pnpm run electron:build` - Build desktop application
|
||||
@@ -25,8 +28,13 @@ Trilium Notes is a hierarchical note-taking application with advanced features l
|
||||
- `pnpm test:all` - Run all tests (parallel + sequential)
|
||||
- `pnpm test:parallel` - Run tests that can run in parallel
|
||||
- `pnpm test:sequential` - Run tests that must run sequentially (server, ckeditor5-mermaid, ckeditor5-math)
|
||||
- `pnpm nx test <project>` - Run tests for specific project
|
||||
- `pnpm coverage` - Generate coverage reports
|
||||
|
||||
### Linting & Type Checking
|
||||
- `pnpm nx run <project>:lint` - Lint specific project
|
||||
- `pnpm nx run <project>:typecheck` - Type check specific project
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Monorepo Structure
|
||||
@@ -86,6 +94,7 @@ Frontend uses a widget system (`apps/client/src/widgets/`):
|
||||
- `apps/server/src/assets/db/schema.sql` - Core database structure
|
||||
|
||||
4. **Configuration**:
|
||||
- `nx.json` - NX workspace configuration
|
||||
- `package.json` - Project dependencies and scripts
|
||||
|
||||
## Note Types and Features
|
||||
@@ -145,7 +154,7 @@ Trilium provides powerful user scripting capabilities:
|
||||
- Update schema in `apps/server/src/assets/db/schema.sql`
|
||||
|
||||
## Build System Notes
|
||||
- Uses pnpm for monorepo management
|
||||
- Uses NX for monorepo management with build caching
|
||||
- Vite for fast development builds
|
||||
- ESBuild for production optimization
|
||||
- pnpm workspaces for dependency management
|
||||
|
||||
@@ -142,7 +142,7 @@ Download the repository, install dependencies using `pnpm` and then run the envi
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
pnpm edit-docs:edit-docs
|
||||
pnpm nx run edit-docs:edit-docs
|
||||
```
|
||||
|
||||
### Building the Executable
|
||||
@@ -151,7 +151,7 @@ Download the repository, install dependencies using `pnpm` and then build the de
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
|
||||
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
|
||||
```
|
||||
|
||||
For more details, see the [development docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.55.0",
|
||||
"@stylistic/eslint-plugin": "5.3.1",
|
||||
"@stylistic/eslint-plugin": "5.2.3",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/node": "22.18.1",
|
||||
"@types/node": "22.18.0",
|
||||
"@types/yargs": "17.0.33",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"eslint": "9.35.0",
|
||||
"eslint": "9.34.0",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"esm": "3.2.25",
|
||||
"jsdoc": "4.0.4",
|
||||
@@ -49,7 +49,7 @@
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"tslib": "2.8.1",
|
||||
"typedoc": "0.28.12",
|
||||
"typedoc": "0.28.11",
|
||||
"typedoc-plugin-missing-exports": "4.1.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -9,13 +9,8 @@
|
||||
"email": "contact@eliandoran.me",
|
||||
"url": "https://github.com/TriliumNext/Notes"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
|
||||
"test": "vitest",
|
||||
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "9.35.0",
|
||||
"@eslint/js": "9.34.0",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@fullcalendar/core": "6.1.19",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
@@ -24,7 +19,7 @@
|
||||
"@fullcalendar/multimonth": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
||||
"@mermaid-js/layout-elk": "0.2.0",
|
||||
"@mermaid-js/layout-elk": "0.1.9",
|
||||
"@mind-elixir/node-menu": "5.0.0",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
@@ -35,13 +30,13 @@
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
"dayjs": "1.11.18",
|
||||
"dayjs": "1.11.14",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "2.2.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
"force-graph": "1.50.1",
|
||||
"globals": "16.3.0",
|
||||
"i18next": "25.5.2",
|
||||
"i18next": "25.4.2",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
@@ -52,12 +47,12 @@
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "16.2.1",
|
||||
"mermaid": "11.11.0",
|
||||
"mind-elixir": "5.1.1",
|
||||
"mermaid": "11.10.1",
|
||||
"mind-elixir": "5.0.6",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.27.1",
|
||||
"react-i18next": "15.7.3",
|
||||
"react-i18next": "15.7.2",
|
||||
"split.js": "1.6.5",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
@@ -69,12 +64,23 @@
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/jquery": "3.5.33",
|
||||
"@types/leaflet": "1.9.20",
|
||||
"@types/leaflet-gpx": "1.3.8",
|
||||
"@types/leaflet-gpx": "1.3.7",
|
||||
"@types/mark.js": "8.11.12",
|
||||
"@types/tabulator-tables": "6.2.10",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"happy-dom": "18.0.1",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.2"
|
||||
},
|
||||
"nx": {
|
||||
"name": "client",
|
||||
"targets": {
|
||||
"serve": {
|
||||
"dependsOn": []
|
||||
},
|
||||
"circular-deps": {
|
||||
"command": "pnpx dpdm -T {projectRoot}/src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { t } from "./services/i18n.js";
|
||||
import options from "./services/options.js";
|
||||
import type ElectronRemote from "@electron/remote";
|
||||
import type Electron from "electron";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
import "boxicons/css/boxicons.min.css";
|
||||
import "autocomplete.js/index_jquery.js";
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ 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 = `
|
||||
@@ -132,33 +131,30 @@ export default class MobileLayout {
|
||||
.child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS)))
|
||||
)
|
||||
.child(
|
||||
new ScreenContainer("detail", "row")
|
||||
new ScreenContainer("detail", "column")
|
||||
.id("detail-container")
|
||||
.class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-9")
|
||||
.child(
|
||||
new NoteWrapperWidget()
|
||||
.child(
|
||||
new FlexContainer("row")
|
||||
.contentSized()
|
||||
.css("font-size", "larger")
|
||||
.css("align-items", "center")
|
||||
.child(<ToggleSidebarButton />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(<MobileDetailMenu />)
|
||||
)
|
||||
.child(<SharedInfoWidget />)
|
||||
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(
|
||||
new ScrollingContainer()
|
||||
.filling()
|
||||
.contentSized()
|
||||
.child(new NoteDetailWidget())
|
||||
.child(new NoteListWidget(false))
|
||||
.child(<FilePropertiesWrapper />)
|
||||
)
|
||||
.child(<MobileEditorToolbar />)
|
||||
new FlexContainer("row")
|
||||
.contentSized()
|
||||
.css("font-size", "larger")
|
||||
.css("align-items", "center")
|
||||
.child(<ToggleSidebarButton />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(<MobileDetailMenu />)
|
||||
)
|
||||
.child(<SharedInfoWidget />)
|
||||
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
|
||||
.child(new PromotedAttributesWidget())
|
||||
.child(
|
||||
new ScrollingContainer()
|
||||
.filling()
|
||||
.contentSized()
|
||||
.child(new NoteDetailWidget())
|
||||
.child(new NoteListWidget(false))
|
||||
.child(<FilePropertiesWrapper />)
|
||||
)
|
||||
.child(<MobileEditorToolbar />)
|
||||
)
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
|
||||
// @ts-ignore - module = undefined
|
||||
// Required for correct loading of scripts in Electron
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import appContext from "./components/app_context.js";
|
||||
import noteAutocompleteService from "./services/note_autocomplete.js";
|
||||
import glob from "./services/glob.js";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
import "boxicons/css/boxicons.min.css";
|
||||
import "autocomplete.js/index_jquery.js";
|
||||
|
||||
|
||||
@@ -48,6 +48,6 @@ function getUrl(docNameValue: string, language: string) {
|
||||
// Cannot have spaces in the URL due to how JQuery.load works.
|
||||
docNameValue = docNameValue.replaceAll(" ", "%20");
|
||||
|
||||
const basePath = window.glob.isDev ? window.glob.assetPath + "/.." : window.glob.assetPath;
|
||||
const basePath = window.glob.isDev ? new URL(window.glob.assetPath).pathname : window.glob.assetPath;
|
||||
return `${basePath}/doc_notes/${language}/${docNameValue}.html`;
|
||||
}
|
||||
|
||||
@@ -10,10 +10,6 @@ let leftInstance: ReturnType<typeof Split> | null;
|
||||
let rightPaneWidth: number;
|
||||
let rightInstance: ReturnType<typeof Split> | null;
|
||||
|
||||
const noteSplitMap = new Map<string[], ReturnType<typeof Split> | undefined>(); // key: a group of ntxIds, value: the corresponding Split instance
|
||||
const noteSplitRafMap = new Map<string[], number>();
|
||||
let splitNoteContainer: HTMLElement | undefined;
|
||||
|
||||
function setupLeftPaneResizer(leftPaneVisible: boolean) {
|
||||
if (leftInstance) {
|
||||
leftInstance.destroy();
|
||||
@@ -87,86 +83,7 @@ function setupRightPaneResizer() {
|
||||
}
|
||||
}
|
||||
|
||||
function findKeyByNtxId(ntxId: string): string[] | undefined {
|
||||
// Find the corresponding key in noteSplitMap based on ntxId
|
||||
for (const key of noteSplitMap.keys()) {
|
||||
if (key.includes(ntxId)) return key;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function setupNoteSplitResizer(ntxIds: string[]) {
|
||||
let targetNtxIds: string[] | undefined;
|
||||
for (const ntxId of ntxIds) {
|
||||
targetNtxIds = findKeyByNtxId(ntxId);
|
||||
if (targetNtxIds) break;
|
||||
}
|
||||
|
||||
if (targetNtxIds) {
|
||||
noteSplitMap.get(targetNtxIds)?.destroy();
|
||||
for (const id of ntxIds) {
|
||||
if (!targetNtxIds.includes(id)) {
|
||||
targetNtxIds.push(id)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
targetNtxIds = [...ntxIds];
|
||||
}
|
||||
noteSplitMap.set(targetNtxIds, undefined);
|
||||
createSplitInstance(targetNtxIds);
|
||||
}
|
||||
|
||||
|
||||
function delNoteSplitResizer(ntxIds: string[]) {
|
||||
let targetNtxIds = findKeyByNtxId(ntxIds[0]);
|
||||
if (!targetNtxIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
noteSplitMap.get(targetNtxIds)?.destroy();
|
||||
noteSplitMap.delete(targetNtxIds);
|
||||
targetNtxIds = targetNtxIds.filter(id => !ntxIds.includes(id));
|
||||
|
||||
if (targetNtxIds.length >= 2) {
|
||||
noteSplitMap.set(targetNtxIds, undefined);
|
||||
createSplitInstance(targetNtxIds);
|
||||
}
|
||||
}
|
||||
|
||||
function moveNoteSplitResizer(ntxId: string) {
|
||||
const targetNtxIds = findKeyByNtxId(ntxId);
|
||||
if (!targetNtxIds) {
|
||||
return;
|
||||
}
|
||||
noteSplitMap.get(targetNtxIds)?.destroy();
|
||||
noteSplitMap.set(targetNtxIds, undefined);
|
||||
createSplitInstance(targetNtxIds);
|
||||
}
|
||||
|
||||
function createSplitInstance(targetNtxIds: string[]) {
|
||||
const prevRafId = noteSplitRafMap.get(targetNtxIds);
|
||||
if (prevRafId) {
|
||||
cancelAnimationFrame(prevRafId);
|
||||
}
|
||||
|
||||
const rafId = requestAnimationFrame(() => {
|
||||
splitNoteContainer = splitNoteContainer ?? $("#center-pane").find(".split-note-container-widget")[0];
|
||||
const splitPanels = [...splitNoteContainer.querySelectorAll<HTMLElement>(':scope > .note-split')]
|
||||
.filter(el => targetNtxIds.includes(el.getAttribute('data-ntx-id') ?? ""));
|
||||
const splitInstance = Split(splitPanels, {
|
||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||
minSize: 150,
|
||||
});
|
||||
noteSplitMap.set(targetNtxIds, splitInstance);
|
||||
noteSplitRafMap.delete(targetNtxIds);
|
||||
});
|
||||
noteSplitRafMap.set(targetNtxIds, rafId);
|
||||
}
|
||||
|
||||
export default {
|
||||
setupLeftPaneResizer,
|
||||
setupRightPaneResizer,
|
||||
setupNoteSplitResizer,
|
||||
delNoteSplitResizer,
|
||||
moveNoteSplitResizer
|
||||
setupRightPaneResizer
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
|
||||
import shortcuts, { keyMatches, matchesShortcut } from "./shortcuts.js";
|
||||
|
||||
// Mock utils module
|
||||
vi.mock("./utils.js", () => ({
|
||||
@@ -320,36 +320,4 @@ describe("shortcuts", () => {
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isIMEComposing', () => {
|
||||
it('should return true when event.isComposing is true', () => {
|
||||
const event = { isComposing: true, keyCode: 65 } as KeyboardEvent;
|
||||
expect(isIMEComposing(event)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when keyCode is 229', () => {
|
||||
const event = { isComposing: false, keyCode: 229 } as KeyboardEvent;
|
||||
expect(isIMEComposing(event)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when both isComposing is true and keyCode is 229', () => {
|
||||
const event = { isComposing: true, keyCode: 229 } as KeyboardEvent;
|
||||
expect(isIMEComposing(event)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for normal keys', () => {
|
||||
const event = { isComposing: false, keyCode: 65 } as KeyboardEvent;
|
||||
expect(isIMEComposing(event)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when isComposing is undefined and keyCode is not 229', () => {
|
||||
const event = { keyCode: 13 } as KeyboardEvent;
|
||||
expect(isIMEComposing(event)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle null/undefined events gracefully', () => {
|
||||
expect(isIMEComposing(null as any)).toBe(false);
|
||||
expect(isIMEComposing(undefined as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,24 +40,6 @@ for (let i = 1; i <= 19; i++) {
|
||||
keyMap[`f${i}`] = [`F${i}`];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if IME (Input Method Editor) is composing
|
||||
* This is used to prevent keyboard shortcuts from firing during IME composition
|
||||
* @param e - The keyboard event to check
|
||||
* @returns true if IME is currently composing, false otherwise
|
||||
*/
|
||||
export function isIMEComposing(e: KeyboardEvent): boolean {
|
||||
// Handle null/undefined events gracefully
|
||||
if (!e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Standard check for composition state
|
||||
// e.isComposing is true when IME is actively composing
|
||||
// e.keyCode === 229 is a fallback for older browsers where 229 indicates IME processing
|
||||
return e.isComposing || e.keyCode === 229;
|
||||
}
|
||||
|
||||
function removeGlobalShortcut(namespace: string) {
|
||||
bindGlobalShortcut("", null, namespace);
|
||||
}
|
||||
@@ -86,13 +68,6 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
||||
}
|
||||
|
||||
const e = evt as KeyboardEvent;
|
||||
|
||||
// Skip processing if IME is composing to prevent shortcuts from
|
||||
// interfering with text input in CJK languages
|
||||
if (isIMEComposing(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchesShortcut(e, keyboardShortcut)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -297,54 +297,6 @@ function isHtmlEmpty(html: string) {
|
||||
);
|
||||
}
|
||||
|
||||
function formatHtml(html: string) {
|
||||
let indent = "\n";
|
||||
const tab = "\t";
|
||||
let i = 0;
|
||||
let pre: { indent: string; tag: string }[] = [];
|
||||
|
||||
html = html
|
||||
.replace(new RegExp("<pre>([\\s\\S]+?)?</pre>"), function (x) {
|
||||
pre.push({ indent: "", tag: x });
|
||||
return "<--TEMPPRE" + i++ + "/-->";
|
||||
})
|
||||
.replace(new RegExp("<[^<>]+>[^<]?", "g"), function (x) {
|
||||
let ret;
|
||||
const tagRegEx = /<\/?([^\s/>]+)/.exec(x);
|
||||
let tag = tagRegEx ? tagRegEx[1] : "";
|
||||
let p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x);
|
||||
|
||||
if (p) {
|
||||
const pInd = parseInt(p[1]);
|
||||
pre[pInd].indent = indent;
|
||||
}
|
||||
|
||||
if (["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"].indexOf(tag) >= 0) {
|
||||
// self closing tag
|
||||
ret = indent + x;
|
||||
} else {
|
||||
if (x.indexOf("</") < 0) {
|
||||
//open tag
|
||||
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length);
|
||||
else ret = indent + x;
|
||||
!p && (indent += tab);
|
||||
} else {
|
||||
//close tag
|
||||
indent = indent.substr(0, indent.length - 1);
|
||||
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length);
|
||||
else ret = indent + x;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
for (i = pre.length; i--;) {
|
||||
html = html.replace("<--TEMPPRE" + i + "/-->", pre[i].tag.replace("<pre>", "<pre>\n").replace("</pre>", pre[i].indent + "</pre>"));
|
||||
}
|
||||
|
||||
return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html;
|
||||
}
|
||||
|
||||
export async function clearBrowserCache() {
|
||||
if (isElectron()) {
|
||||
const win = dynamicRequire("@electron/remote").getCurrentWindow();
|
||||
@@ -903,7 +855,6 @@ export default {
|
||||
getNoteTypeClass,
|
||||
getMimeTypeClass,
|
||||
isHtmlEmpty,
|
||||
formatHtml,
|
||||
clearBrowserCache,
|
||||
copySelectionToClipboard,
|
||||
dynamicRequire,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
import "./stylesheets/auth.css";
|
||||
|
||||
// @TriliumNextTODO: is this even needed anymore?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "jquery";
|
||||
import utils from "./services/utils.js";
|
||||
import ko from "knockout";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
|
||||
// TriliumNextTODO: properly make use of below types
|
||||
// type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | "";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "normalize.css";
|
||||
import "boxicons/css/boxicons.min.css";
|
||||
import "@triliumnext/ckeditor5/src/theme/ck-content.css";
|
||||
import "@triliumnext/ckeditor5/content.css";
|
||||
import "@triliumnext/share-theme/styles/index.css";
|
||||
import "@triliumnext/share-theme/scripts/index.js";
|
||||
|
||||
|
||||
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";
|
||||
@@ -1134,7 +1134,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
|
||||
.toast-body {
|
||||
white-space: preserve-breaks;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ck-mentions .ck-button {
|
||||
@@ -1243,10 +1242,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
.hidden-ext.note-split + .gutter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#context-menu-cover.show {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -1468,7 +1463,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
color: var(--launcher-pane-text-color);
|
||||
background: transparent;
|
||||
background-color: var(--launcher-pane-background-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1776,6 +1771,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
}
|
||||
|
||||
.note-split {
|
||||
flex-basis: 0; /* so that each split has same width */
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
@@ -2370,21 +2366,3 @@ footer.webview-footer button {
|
||||
content: "\ec24";
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* CK Edito */
|
||||
|
||||
/* Insert text snippet: limit the width of the listed items to avoid overly long names */
|
||||
:root body.desktop div.ck-template-form li.ck-list__item .ck-template-form__text-part > span {
|
||||
max-width: 25vw;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.revision-diff-added {
|
||||
background: rgba(100, 200, 100, 0.5);
|
||||
}
|
||||
|
||||
.revision-diff-removed {
|
||||
background: rgba(255, 100, 100, 0.5);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
@@ -13,13 +13,12 @@
|
||||
|
||||
--theme-style: dark;
|
||||
--native-titlebar-background: #00000000;
|
||||
--window-background-color-bgfx: transparent; /* When background effects enabled */
|
||||
|
||||
--main-background-color: #272727;
|
||||
--main-text-color: #ccc;
|
||||
--main-border-color: #454545;
|
||||
--subtle-border-color: #313131;
|
||||
--dropdown-border-color: #404040;
|
||||
--dropdown-border-color: #292929;
|
||||
--dropdown-shadow-opacity: 0.6;
|
||||
--dropdown-item-icon-destructive-color: #de6e5b;
|
||||
--disabled-tooltip-icon-color: #7fd2ef;
|
||||
@@ -148,7 +147,6 @@
|
||||
--launcher-pane-vert-button-hover-background: #ffffff1c;
|
||||
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
||||
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
--launcher-pane-vert-background-color-bgfx: #00000026; /* When background effects enabled */
|
||||
|
||||
--launcher-pane-horiz-border-color: rgb(22, 22, 22);
|
||||
--launcher-pane-horiz-background-color: #282828;
|
||||
@@ -157,8 +155,6 @@
|
||||
--launcher-pane-horiz-button-hover-background: #ffffff1c;
|
||||
--launcher-pane-horiz-button-hover-shadow: unset;
|
||||
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
--launcher-pane-horiz-background-color-bgfx: #ffffff17; /* When background effects enabled */
|
||||
--launcher-pane-horiz-border-color-bgfx: #00000080; /* When background effects enabled */
|
||||
|
||||
--protected-session-active-icon-color: #8edd8e;
|
||||
--sync-status-error-pulse-color: #f47871;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
--theme-style: light;
|
||||
--native-titlebar-background: #ffffff00;
|
||||
--window-background-color-bgfx: transparent; /* When background effects enabled */
|
||||
|
||||
--main-background-color: white;
|
||||
--main-text-color: black;
|
||||
@@ -116,17 +115,17 @@
|
||||
--quick-search-focus-border: #00000029;
|
||||
--quick-search-focus-background: #ffffff80;
|
||||
--quick-search-focus-color: #000;
|
||||
--quick-search-result-content-background: #0000000f;
|
||||
--quick-search-result-content-background: #00000017;
|
||||
--quick-search-result-highlight-color: #c65050;
|
||||
|
||||
--left-pane-collapsed-border-color: #0000000d;
|
||||
--left-pane-background-color: #f2f2f2;
|
||||
--left-pane-text-color: #383838;
|
||||
--left-pane-item-hover-background: rgba(0, 0, 0, 0.032);
|
||||
--left-pane-item-hover-background: #eaeaea;
|
||||
--left-pane-item-selected-background: white;
|
||||
--left-pane-item-selected-color: black;
|
||||
--left-pane-item-selected-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
|
||||
--left-pane-item-action-button-background: rgba(0, 0, 0, 0.11);
|
||||
--left-pane-item-action-button-background: #d7d7d7;
|
||||
--left-pane-item-action-button-color: inherit;
|
||||
--left-pane-item-action-button-hover-background: white;
|
||||
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
|
||||
@@ -142,7 +141,6 @@
|
||||
--launcher-pane-vert-button-hover-background: white;
|
||||
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.075);
|
||||
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
--launcher-pane-vert-background-color-bgfx: #00000009; /* When background effects enabled */
|
||||
|
||||
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.1);
|
||||
--launcher-pane-horiz-background-color: #fafafa;
|
||||
@@ -150,8 +148,6 @@
|
||||
--launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background);
|
||||
--launcher-pane-horiz-button-hover-shadow: unset;
|
||||
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
--launcher-pane-horiz-background-color-bgfx: #ffffffb3; /* When background effects enabled */
|
||||
--launcher-pane-horiz-border-color-bgfx: #00000026; /* When background effects enabled */
|
||||
|
||||
--protected-session-active-icon-color: #16b516;
|
||||
--sync-status-error-pulse-color: #ff5528;
|
||||
|
||||
@@ -329,8 +329,6 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
|
||||
|
||||
#toast-container .toast .toast-body {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
button.btn.btn-primary,
|
||||
button.btn.btn-secondary,
|
||||
button.btn.btn-sm:not(.select-button),
|
||||
button.btn.btn-success,
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text {
|
||||
button.btn.btn-success {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -22,8 +21,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .c
|
||||
button.btn.btn-primary:hover,
|
||||
button.btn.btn-secondary:hover,
|
||||
button.btn.btn-sm:not(.select-button):hover,
|
||||
button.btn.btn-success:hover,
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):hover {
|
||||
button.btn.btn-success:hover {
|
||||
background: var(--cmd-button-hover-background-color);
|
||||
color: var(--cmd-button-hover-text-color);
|
||||
}
|
||||
@@ -31,8 +29,7 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .c
|
||||
button.btn.btn-primary:active,
|
||||
button.btn.btn-secondary:active,
|
||||
button.btn.btn-sm:not(.select-button):active,
|
||||
button.btn.btn-success:active,
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):active {
|
||||
button.btn.btn-success:active {
|
||||
opacity: 0.85;
|
||||
box-shadow: unset;
|
||||
background: var(--cmd-button-background-color) !important;
|
||||
@@ -43,16 +40,14 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .c
|
||||
button.btn.btn-primary:disabled,
|
||||
button.btn.btn-secondary:disabled,
|
||||
button.btn.btn-sm:not(.select-button):disabled,
|
||||
button.btn.btn-success:disabled,
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text.ck-disabled {
|
||||
button.btn.btn-success:disabled {
|
||||
opacity: var(--cmd-button-disabled-opacity);
|
||||
}
|
||||
|
||||
button.btn.btn-primary:focus-visible,
|
||||
button.btn.btn-secondary:focus-visible,
|
||||
button.btn.btn-sm:not(.select-button):focus-visible,
|
||||
button.btn.btn-success:focus-visible,
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):focus-visible {
|
||||
button.btn.btn-success:focus-visible {
|
||||
outline: 2px solid var(--input-focus-outline-color);
|
||||
}
|
||||
|
||||
@@ -154,11 +149,8 @@ input[type="password"],
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
:root input.ck.ck-input-text,
|
||||
:root input.ck.ck-input-number,
|
||||
textarea.form-control,
|
||||
textarea,
|
||||
:root textarea.ck.ck-textarea,
|
||||
.tn-input-field {
|
||||
outline: 3px solid transparent;
|
||||
outline-offset: 6px;
|
||||
@@ -175,11 +167,8 @@ input[type="password"]:hover,
|
||||
input[type="date"]:hover,
|
||||
input[type="time"]:hover,
|
||||
input[type="datetime-local"]:hover,
|
||||
:root input.ck.ck-input-text:not([readonly="true"]):hover,
|
||||
:root input.ck.ck-input-number:not([readonly="true"]):hover,
|
||||
textarea.form-control:hover,
|
||||
textarea:hover,
|
||||
:root textarea.ck.ck-textarea:hover,
|
||||
.tn-input-field:hover {
|
||||
background: var(--input-hover-background);
|
||||
color: var(--input-hover-color);
|
||||
@@ -192,11 +181,8 @@ input[type="password"]:focus,
|
||||
input[type="date"]:focus,
|
||||
input[type="time"]:focus,
|
||||
input[type="datetime-local"]:focus,
|
||||
:root input.ck.ck-input-text:focus,
|
||||
:root input.ck.ck-input-number:focus,
|
||||
textarea.form-control:focus,
|
||||
textarea:focus,
|
||||
:root textarea.ck.ck-textarea:focus,
|
||||
.tn-input-field:focus,
|
||||
.tn-input-field:focus-within {
|
||||
box-shadow: unset;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
:root {
|
||||
--ck-font-face: var(--main-font-family);
|
||||
--ck-input-label-height: 1.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -308,11 +307,6 @@
|
||||
fill: black !important;
|
||||
}
|
||||
|
||||
/* Hex color input box prefix */
|
||||
:root .ck.ck-color-selector .ck-color-picker__hash-view {
|
||||
margin-top: var(--ck-input-label-height);
|
||||
}
|
||||
|
||||
/* Numbered list */
|
||||
|
||||
:root .ck.ck-list-properties_with-numbered-properties .ck.ck-list-styles-list {
|
||||
@@ -369,86 +363,19 @@
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Text snippet dropdown */
|
||||
/* Action buttons */
|
||||
|
||||
div.ck-template-form {
|
||||
padding: 8px;
|
||||
:root .ck-link-actions button.ck-button,
|
||||
:root .ck-link-form button.ck-button {
|
||||
--ck-border-radius: 6px;
|
||||
|
||||
background: transparent;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
div.ck-template-form .ck-labeled-field-view {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Template item */
|
||||
|
||||
:root div.ck-template-form li.ck-list__item button.ck-template-button {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* Template icon */
|
||||
:root .ck-template-form .ck-button__icon {
|
||||
--ck-spacing-medium: 2px;
|
||||
}
|
||||
|
||||
:root div.ck-template-form .note-icon {
|
||||
color: var(--menu-item-icon-color);
|
||||
}
|
||||
|
||||
/* Template name */
|
||||
div.ck-template-form .ck-template-form__text-part {
|
||||
color: var(--hover-item-text-color);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
div.ck-template-form .ck-template-form__text-part mark {
|
||||
background: unset;
|
||||
color: var(--quick-search-result-highlight-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Template description */
|
||||
:root div.ck-template-form .ck-template-form__description {
|
||||
opacity: .5;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
div.ck-template-form .ck-search__info > span {
|
||||
line-height: initial;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
div.ck-template-form .ck-search__info span:nth-child(2) {
|
||||
display: block;
|
||||
opacity: .5;
|
||||
margin-top: 8px;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
/* Link dropdown */
|
||||
|
||||
:root .ck.ck-form.ck-link-form ul.ck-link-form__providers-list {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* Math popup */
|
||||
|
||||
.ck-math-form .ck-labeled-field-view {
|
||||
--ck-input-label-height: 0;
|
||||
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
/* Emoji dropdown */
|
||||
|
||||
.ck-emoji-picker-form .ck-emoji__search .ck-button_with-text:not(.ck-list-item-button) {
|
||||
margin-top: var(--ck-input-label-height);
|
||||
}
|
||||
|
||||
/* Find and replace dialog */
|
||||
|
||||
.ck-find-and-replace-form .ck-find-and-replace-form__inputs button {
|
||||
margin-top: var(--ck-input-label-height);
|
||||
:root .ck-link-actions button.ck-button:hover,
|
||||
:root .ck-link-form button.ck-button:hover {
|
||||
background: var(--hover-item-background-color);
|
||||
}
|
||||
|
||||
/* Mention list (the autocompletion list for emojis, labels and relations) */
|
||||
@@ -465,58 +392,6 @@ div.ck-template-form .ck-search__info span:nth-child(2) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
* FORMS
|
||||
*/
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
|
||||
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck-button_with-text {
|
||||
--ck-color-text: var(--cmd-button-text-color);
|
||||
|
||||
min-width: 60px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/*
|
||||
* Text boxes
|
||||
*/
|
||||
|
||||
.ck.ck-labeled-field-view {
|
||||
padding-top: var(--ck-input-label-height) !important; /* Create space for the label */
|
||||
}
|
||||
|
||||
.ck.ck-labeled-field-view > .ck.ck-labeled-field-view__input-wrapper > label.ck.ck-label {
|
||||
/* Move the label above the text box regardless of the text box state */
|
||||
transform: translate(0, calc(-.2em - var(--ck-input-label-height))) !important;
|
||||
|
||||
padding-left: 0 !important;
|
||||
background: transparent;
|
||||
font-size: .85em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:root input.ck.ck-input-text[readonly="true"] {
|
||||
cursor: not-allowed;
|
||||
background: var(--input-background-color);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
|
||||
:root .ck.ck-form__row.ck-form__row_with-submit > :not(:first-child) {
|
||||
margin-inline-start: 16px;
|
||||
}
|
||||
|
||||
.ck.ck-form__row_with-submit button {
|
||||
margin-top: var(--ck-input-label-height);
|
||||
}
|
||||
|
||||
.ck.ck-form__header {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* EDITOR'S CONTENT
|
||||
*/
|
||||
|
||||
@@ -36,23 +36,32 @@ body.mobile {
|
||||
|
||||
/* #region Mica */
|
||||
body.background-effects.platform-win32 {
|
||||
--background-material: tabbed;
|
||||
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
|
||||
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
|
||||
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
|
||||
--tab-background-color: var(--window-background-color-bgfx);
|
||||
--new-tab-button-background: var(--window-background-color-bgfx);
|
||||
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.15);
|
||||
--launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.7);
|
||||
--launcher-pane-vert-background-color: rgba(255, 255, 255, 0.055);
|
||||
--tab-background-color: transparent;
|
||||
--new-tab-button-background: transparent;
|
||||
--active-tab-background-color: var(--launcher-pane-horiz-background-color);
|
||||
--background-material: tabbed;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body.background-effects.platform-win32 {
|
||||
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.5);
|
||||
--launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.09);
|
||||
}
|
||||
}
|
||||
|
||||
body.background-effects.platform-win32.layout-vertical {
|
||||
--left-pane-background-color: var(--window-background-color-bgfx);
|
||||
--left-pane-background-color: transparent;
|
||||
--left-pane-item-hover-background: rgba(127, 127, 127, 0.05);
|
||||
--background-material: mica;
|
||||
}
|
||||
|
||||
body.background-effects.platform-win32,
|
||||
body.background-effects.platform-win32 #root-widget {
|
||||
background: var(--window-background-color-bgfx) !important;
|
||||
body.background-effects.platform-win32 #root-widget,
|
||||
body.background-effects.platform-win32 #launcher-pane .launcher-button {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container,
|
||||
@@ -81,7 +90,7 @@ body.background-effects.zen #root-widget {
|
||||
* Gutter
|
||||
*/
|
||||
|
||||
.gutter {
|
||||
.gutter {
|
||||
background: var(--gutter-color) !important;
|
||||
transition: background 150ms ease-out;
|
||||
}
|
||||
@@ -566,20 +575,31 @@ div.quick-search .search-button.show {
|
||||
* Quick search results
|
||||
*/
|
||||
|
||||
div.quick-search .dropdown-menu {
|
||||
--quick-search-item-delimiter-color: transparent;
|
||||
--menu-item-icon-vert-offset: -.065em;
|
||||
}
|
||||
|
||||
/* Item */
|
||||
.quick-search .dropdown-menu *.dropdown-item {
|
||||
padding: 8px 12px !important;
|
||||
}
|
||||
|
||||
/* Note icon */
|
||||
.quick-search .dropdown-menu .dropdown-item > .bx {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.quick-search .quick-search-item-icon {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
.quick-search .dropdown-menu small {
|
||||
display: block;
|
||||
@@ -602,8 +622,9 @@ div.quick-search .dropdown-menu {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quick-search div.dropdown-divider {
|
||||
margin: 8px 0;
|
||||
/* Divider line */
|
||||
.quick-search .dropdown-item::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -878,80 +899,6 @@ body.layout-horizontal .tab-row-container {
|
||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container {
|
||||
border-bottom: unset !important;
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .note-tab-wrapper {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .toggle-button {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .toggle-button:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -10px;
|
||||
right: -10px;
|
||||
top: 29px;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-left,
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-right {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-left:after,
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .tab-scroll-button-right:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .note-tab[active]:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -32768px;
|
||||
top: var(--tab-height);
|
||||
right: calc(100% - 1px);
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .note-tab[active]:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 100%;
|
||||
top: var(--tab-height);
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
||||
}
|
||||
|
||||
body.electron.background-effects.layout-horizontal .tab-row-container .note-new-tab:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -4px;
|
||||
top: calc(var(--tab-height), -1);
|
||||
right: 0;
|
||||
width: 100vw;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
|
||||
}
|
||||
|
||||
body.layout-vertical.electron.platform-darwin .tab-row-container {
|
||||
border-bottom: 1px solid var(--subtle-border-color);
|
||||
}
|
||||
@@ -1167,11 +1114,6 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
|
||||
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
|
||||
}
|
||||
|
||||
.split-note-container-widget > .gutter {
|
||||
background: var(--root-background) !important;
|
||||
transition: background 150ms ease-out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ribbon & note header
|
||||
*/
|
||||
@@ -1180,6 +1122,10 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.note-split:not(.hidden-ext) + .note-split:not(.hidden-ext) {
|
||||
border-left: 4px solid var(--root-background);
|
||||
}
|
||||
|
||||
@keyframes note-entrance {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1404,8 +1404,8 @@
|
||||
"open-in-popup": "Schnellbearbeitung"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
|
||||
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
|
||||
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}",
|
||||
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}",
|
||||
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||
},
|
||||
"note_types": {
|
||||
@@ -1484,8 +1484,7 @@
|
||||
"hoist-this-note-workspace": "Diese Notiz fokussieren (Arbeitsbereich)",
|
||||
"refresh-saved-search-results": "Gespeicherte Suchergebnisse aktualisieren",
|
||||
"create-child-note": "Unternotiz anlegen",
|
||||
"unhoist": "Fokus verlassen",
|
||||
"toggle-sidebar": "Seitenleiste ein-/ausblenden"
|
||||
"unhoist": "Fokus verlassen"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Dieses Fenster immer oben halten"
|
||||
|
||||
@@ -263,11 +263,6 @@
|
||||
"confirm_delete_all": "Do you want to delete all revisions of this note?",
|
||||
"no_revisions": "No revisions for this note yet...",
|
||||
"restore_button": "Restore",
|
||||
"diff_on": "Show diff",
|
||||
"diff_off": "Show content",
|
||||
"diff_on_hint": "Click to show note source diff",
|
||||
"diff_off_hint": "Click to show note content",
|
||||
"diff_not_available": "Diff isn't available.",
|
||||
"confirm_restore": "Do you want to restore this revision? This will overwrite the current title and content of the note with this revision.",
|
||||
"delete_button": "Delete",
|
||||
"confirm_delete": "Do you want to delete this revision?",
|
||||
@@ -1123,9 +1118,7 @@
|
||||
"title": "Performance",
|
||||
"enable-motion": "Enable transitions and animations",
|
||||
"enable-shadows": "Enable shadows",
|
||||
"enable-backdrop-effects": "Enable background effects for menus, popups and panels",
|
||||
"enable-smooth-scroll": "Enable smooth scrolling",
|
||||
"app-restart-required": "(a restart of the application is required for the change to take effect)"
|
||||
"enable-backdrop-effects": "Enable background effects for menus, popups and panels"
|
||||
},
|
||||
"ai_llm": {
|
||||
"not_started": "Not started",
|
||||
|
||||
@@ -13,131 +13,29 @@
|
||||
"critical-error": {
|
||||
"title": "Błąd krytyczny",
|
||||
"message": "Wystąpił krytyczny błąd uniemożliwiający uruchomienie aplikacji:\n\n{{message}}\n\nJest to spowodowane najprawdopodobniej niespodziewanym błędem skryptu. Spróbuj uruchomić aplikację ponownie w trybie bezpiecznym i zaadresuj problem."
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "Nie udało się zainicjować widżetu",
|
||||
"message-custom": "Niestandardowy widżet z notatki o identyfikatorze \"{{id}}\", i tytule \"{{title}}\" nie mógł zostać zainicjowany z powodu:\n\n{{message}}",
|
||||
"message-unknown": "Nieznany widżet nie mógł być zainicjowany z powodu:\n\n{{message}}"
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Nie udało się załadować niestandardowego skryptu",
|
||||
"message": "Skrypt z notatki o identyfikatorze \"{{id}}\", tytule \"{{title}}: nie został uruchomiony z powodu:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Dodaj link",
|
||||
"note": "Notatka",
|
||||
"search_note": "Wyszukaj notatkę po nazwie",
|
||||
"link_title_arbitrary": "Tytuł linku można dowolnie zmieniać",
|
||||
"link_title": "Tytuł linku",
|
||||
"button_add_link": "Dodaj link",
|
||||
"help_on_links": "Pomoc dotycząca linków",
|
||||
"link_title_mirrors": "tytuł linku odzwierciedla tytuł obecnej notatki"
|
||||
"add_link": "Dodaj link"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"save": "Zapisz",
|
||||
"edit_branch_prefix": "Edytuj prefiks gałęzi",
|
||||
"prefix": "Prefiks: ",
|
||||
"branch_prefix_saved": "Zapisano prefiks gałęzi.",
|
||||
"help_on_tree_prefix": "Pomoc dotycząca prefiksu drzewa"
|
||||
"save": "Zapisz"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"labels": "Etykiety",
|
||||
"notes": "Notatki",
|
||||
"other": "Inne",
|
||||
"relations": "Powiązania",
|
||||
"bulk_actions": "Działania zbiorcze",
|
||||
"include_descendants": "Uwzględnia rozwinięcia wybranych notatek",
|
||||
"available_actions": "Dostępne działania",
|
||||
"chosen_actions": "Wybrane działania",
|
||||
"execute_bulk_actions": "Wykonaj zbiór działań",
|
||||
"bulk_actions_executed": "Zbiór działań został wykonany prawidłowo.",
|
||||
"none_yet": "Brak zaznaczonych działań... dodaj działanie poprzez kliknięcie jednej z dostępnych opcji powyżej."
|
||||
"relations": "Powiązania"
|
||||
},
|
||||
"confirm": {
|
||||
"ok": "OK",
|
||||
"cancel": "Anuluj",
|
||||
"confirmation": "Potwierdzenie",
|
||||
"are_you_sure_remove_note": "Czy napewno chcesz usunąć notatkę \"{{title}}\" z mapy powiązań? ",
|
||||
"if_you_dont_check": "Jeśli nie zaznaczysz tej opcji, notatka zostanie usunięta jedynie z mapy powiązań.",
|
||||
"also_delete_note": "Usuń dodatkowo notatkę"
|
||||
"cancel": "Anuluj"
|
||||
},
|
||||
"delete_notes": {
|
||||
"cancel": "Anuluj",
|
||||
"close": "Zamknij",
|
||||
"delete_notes_preview": "Usuń podgląd notatek",
|
||||
"delete_all_clones_description": "Usuń również wszystkie sklonowania (działanie może zostać cofnięte w ostatnich zmianach)",
|
||||
"erase_notes_description": "Normalne (miękkie) usuwanie zaznacza jedynie notatki jako usunięte i można je przywrócić (w oknie dialogowym ostatnich zmian) przez wyznaczony okres czasu. Zaznaczenie tej opcji spowoduje natychmiastowe usunięcie notatek, bez możliwości ich przywrócenia.",
|
||||
"erase_notes_warning": "Usuń notatki permanentnie (bez opcji ich przywrócenia), włączając wszystkie kopie. Działanie to wymaga ponownego uruchomienia aplikacji.",
|
||||
"notes_to_be_deleted": "Następujące notatki zostaną usunięte ({{notesCount}})",
|
||||
"no_note_to_delete": "Żadne notatki nie zostaną usunięte (jedynie kopie).",
|
||||
"broken_relations_to_be_deleted": "Następujące powiązania zostaną uszkodzone i usunięte ({{ relationCount}})",
|
||||
"ok": "OK",
|
||||
"deleted_relation_text": "Notatka {{- note}} (do usunięcia) jest powiązana przez relację {{- relation}} pochodzącą z {{- source}}."
|
||||
"close": "Zamknij"
|
||||
},
|
||||
"export": {
|
||||
"close": "Zamknij",
|
||||
"export_note_title": "Eksportuj notatkę",
|
||||
"export_type_subtree": "Ta notatka oraz wszystkie podrzędne",
|
||||
"format_html": "HTML - rekomendowany jako zachowujący całość formatowania",
|
||||
"format_html_zip": "HTML w archiwum ZIP - rekomendowany jako zachowujący całość formatowania.",
|
||||
"format_markdown": "Markdown - zachowuje większość formatowania.",
|
||||
"format_opml": "OPML - format wymiany danych dla outlinerów zawierający tylko tekst. Formatowanie, obrazy i pliki nie są uwzględnione.",
|
||||
"opml_version_1": "OPML v1.0 - tylko zwykły tekst",
|
||||
"opml_version_2": "OPML v2.0 - umożliwia również HTML",
|
||||
"export_type_single": "Tylko ta notatka, bez elementów podrzędnych",
|
||||
"export": "Eksportuj",
|
||||
"choose_export_type": "Wybierz najpierw rodzaj pliku do eksportu",
|
||||
"export_status": "Status eksportu",
|
||||
"export_in_progress": "Postęp eksportowania: {{progressCount}}",
|
||||
"export_finished_successfully": "Eksportowanie zakończone.",
|
||||
"format_pdf": "PDF - w celu drukowania lub udostępniania."
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Sklonuj notatki do...",
|
||||
"notes_to_clone": "Notatki do sklonowania",
|
||||
"search_for_note_by_its_name": "Wyszukaj notatkę po jej nazwie",
|
||||
"cloned_note_prefix_title": "Sklonowana notatka zostanie wyświetlona w drzewie notatki z podanym prefiksem",
|
||||
"prefix_optional": "Prefiks (opcjonalne)",
|
||||
"clone_to_selected_note": "Sklonuj do wybranej notatki",
|
||||
"no_path_to_clone_to": "Brak ścieżki do sklonowania.",
|
||||
"note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\"",
|
||||
"help_on_links": "Pomoc dotycząca linków"
|
||||
},
|
||||
"help": {
|
||||
"title": "Ściągawka",
|
||||
"noteNavigation": "Nawigacja po notatkach",
|
||||
"goUpDown": "przewijanie w górę/w dół w liście notatek",
|
||||
"collapseExpand": "zwiń/rozwiń zbiór",
|
||||
"notSet": "niezdefiniowany",
|
||||
"goBackForwards": "przewijaj do tyłu/do przodu w historii",
|
||||
"showJumpToNoteDialog": "pokaż <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"przejdź do dialogu</a>",
|
||||
"scrollToActiveNote": "przewiń do aktywnej notatki",
|
||||
"jumpToParentNote": "przejdź do głównej notatki",
|
||||
"collapseWholeTree": "zwiń całe drzewko notatki",
|
||||
"collapseSubTree": "zwiń gałąź notatki",
|
||||
"tabShortcuts": "Skóry kart",
|
||||
"newTabNoteLink": "link notatki otwiera notatkę w nowej karcie",
|
||||
"newTabWithActivationNoteLink": "link notatki otwiera i aktywuje notatkę w nowej karcie",
|
||||
"onlyInDesktop": "Tylko na komputerze stacjonarnym (wersja Electron)",
|
||||
"openEmptyTab": "Otwórz pustą kartę",
|
||||
"closeActiveTab": "zamknij aktywną kartę",
|
||||
"activateNextTab": "aktywuj następną kartę",
|
||||
"activatePreviousTab": "aktywuj poprzednią kartę",
|
||||
"creatingNotes": "Tworzenie notatek",
|
||||
"createNoteAfter": "Utwórz nową notatkę obok obecnie aktywnej",
|
||||
"createNoteInto": "Utwórz nową podnotatkę w obecnie otwartej",
|
||||
"editBranchPrefix": "edytuj <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefiks</a> aktywnej kopii notatki",
|
||||
"movingCloningNotes": "Przenoszenie / kopiowanie notatek",
|
||||
"moveNoteUpDown": "Przenieś notatkę w górę/w dół na liście notatek",
|
||||
"moveNoteUpHierarchy": "Przenieś notatkę w górę w hierarchii",
|
||||
"multiSelectNote": "Zaznacz wiele notatek powyżej/poniżej",
|
||||
"selectAllNotes": "Wybierz wszystkie notatki na obecnym poziomie",
|
||||
"selectNote": "Wybierz notatkę",
|
||||
"copyNotes": "skopiuj obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">klonowania</a>)",
|
||||
"cutNotes": "przytnij obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla przenoszenia notatek)",
|
||||
"pasteNotes": "wklej notatkę jako podnotatka w obecnej notatce (rozumiane jako przenieś lub skopiuj, w zależności czy notatka była skopiowana czy wycięta)",
|
||||
"deleteNotes": "usuń notatkę / gałąź",
|
||||
"editingNotes": "Edytowanie notatek"
|
||||
"close": "Zamknij"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -994,7 +994,7 @@
|
||||
"invalid_view_type": "Tipo de visualização inválido '{{type}}'",
|
||||
"calendar": "Calendário",
|
||||
"table": "Tabela",
|
||||
"geo-map": "Mapa geográfico",
|
||||
"geo-map": "Geo Map",
|
||||
"board": "Quadro"
|
||||
},
|
||||
"edited_notes": {
|
||||
@@ -1278,7 +1278,7 @@
|
||||
"handwriting-system-fonts": "Fontes de escrita à mão de sistema",
|
||||
"serif": "Serifa",
|
||||
"sans-serif": "Sem Serifa",
|
||||
"monospace": "Monoespaçado",
|
||||
"monospace": "Monospace",
|
||||
"system-default": "Padrão do Sistema",
|
||||
"note_tree_and_detail_font_sizing": "Note que o tamanho da fonte da árvore e dos detalhes é relativo à configuração principal do tamanho de fonte."
|
||||
},
|
||||
@@ -1518,7 +1518,7 @@
|
||||
"doc": "Documento",
|
||||
"widget": "Widget",
|
||||
"confirm-change": "Não é recomentado alterar o tipo da nota quando o conteúdo da nota não está vazio. Quer continuar assim mesmo?",
|
||||
"geo-map": "Mapa geográfico",
|
||||
"geo-map": "Geo Map",
|
||||
"beta-feature": "Beta",
|
||||
"ai-chat": "Chat IA",
|
||||
"task-list": "Lista de Tarefas",
|
||||
@@ -1579,8 +1579,7 @@
|
||||
"hoist-this-note-workspace": "Fixar esta nota (workspace)",
|
||||
"refresh-saved-search-results": "Atualizar resultados de pesquisa salvos",
|
||||
"create-child-note": "Criar nota filha",
|
||||
"unhoist": "Desafixar",
|
||||
"toggle-sidebar": "Alternar barra lateral"
|
||||
"unhoist": "Desafixar"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Manter Janela no Topo"
|
||||
@@ -2023,8 +2022,6 @@
|
||||
"handshake_failed": "Falha no handshake com o servidor de sincronização, erro: {{message}}"
|
||||
},
|
||||
"shared_info": {
|
||||
"help_link": "Para ajuda, visite a <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>.",
|
||||
"shared_publicly": "Esta nota é compartilhada publicamente em {{- link}}.",
|
||||
"shared_locally": "Esta nota é compartilhada localmente em {{- link}}."
|
||||
"help_link": "Para ajuda, visite a <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2013,9 +2013,7 @@
|
||||
"title": "Setări de performanță",
|
||||
"enable-motion": "Activează tranzițiile și animațiile",
|
||||
"enable-shadows": "Activează umbrirea elementelor",
|
||||
"enable-backdrop-effects": "Activează efectele de fundal pentru meniuri, popup-uri și panouri",
|
||||
"enable-smooth-scroll": "Activează derularea lină",
|
||||
"app-restart-required": "(este necesară repornirea aplicației pentru ca modificarea să aibă efect)"
|
||||
"enable-backdrop-effects": "Activează efectele de fundal pentru meniuri, popup-uri și panouri"
|
||||
},
|
||||
"settings": {
|
||||
"related_settings": "Setări similare"
|
||||
|
||||
@@ -742,8 +742,7 @@
|
||||
"save-changes": "Сохранить и применить изменения",
|
||||
"saved-search-note-refreshed": "Сохраненная поисковая заметка обновлена.",
|
||||
"refresh-saved-search-results": "Обновить сохраненные результаты поиска",
|
||||
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве.",
|
||||
"toggle-sidebar": "Переключить боковую панель"
|
||||
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве."
|
||||
},
|
||||
"quick-search": {
|
||||
"no-results": "Результаты не найдены",
|
||||
@@ -1975,8 +1974,8 @@
|
||||
},
|
||||
"shared_info": {
|
||||
"help_link": "Для получения справки посетите <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">вики</a>.",
|
||||
"shared_locally": "Заметка общедоступна локально в {{- link}}.",
|
||||
"shared_publicly": "Заметка общедоступна публично в {{- link}}."
|
||||
"shared_locally": "Заметка общедоступна локально в {{- link}}",
|
||||
"shared_publicly": "Заметка общедоступна публично в {{- link}}"
|
||||
},
|
||||
"note_create": {
|
||||
"duplicated": "Создан дубль заметки \"{{title}}\"."
|
||||
|
||||
@@ -844,8 +844,7 @@
|
||||
"note_type": "Тип нотатки",
|
||||
"editable": "Редагув.",
|
||||
"basic_properties": "Основні Властивості",
|
||||
"language": "Мова",
|
||||
"configure_code_notes": "Конфігурація нотатки з кодом..."
|
||||
"language": "Мова"
|
||||
},
|
||||
"book_properties": {
|
||||
"view_type": "Тип перегляду",
|
||||
@@ -1587,8 +1586,7 @@
|
||||
"hoist-this-note-workspace": "Закріпити цю нотатку (робочий простір)",
|
||||
"refresh-saved-search-results": "Оновити збережені результати пошуку",
|
||||
"create-child-note": "Створити дочірню нотатку",
|
||||
"unhoist": "Відкріпити",
|
||||
"toggle-sidebar": "Перемикання бічної панелі"
|
||||
"unhoist": "Відкріпити"
|
||||
},
|
||||
"title_bar_buttons": {
|
||||
"window-on-top": "Тримати вікно зверху"
|
||||
@@ -1911,8 +1909,8 @@
|
||||
"open-in-popup": "Швидке редагування"
|
||||
},
|
||||
"shared_info": {
|
||||
"shared_publicly": "Ця нотатка опублікована на {{- link}}.",
|
||||
"shared_locally": "Цю нотатку опубліковано локально на {{- link}}.",
|
||||
"shared_publicly": "Ця нотатка опублікована на {{- link}}",
|
||||
"shared_locally": "Цю нотатку опубліковано локально на {{- link}}",
|
||||
"help_link": "Щоб отримати допомогу, відвідайте <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">вікі</a>."
|
||||
},
|
||||
"note_types": {
|
||||
@@ -2020,11 +2018,5 @@
|
||||
},
|
||||
"units": {
|
||||
"percentage": "%"
|
||||
},
|
||||
"ui-performance": {
|
||||
"title": "Продуктивність",
|
||||
"enable-motion": "Увімкнути переходи та анімацію",
|
||||
"enable-shadows": "Увімкнути тіні",
|
||||
"enable-backdrop-effects": "Увімкнути фонові ефекти для меню, спливаючих вікон та панелей"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"about": {
|
||||
"homepage": "Trang chủ:",
|
||||
"title": "Về Trilium Notes",
|
||||
"app_version": "Phiên bản:",
|
||||
"db_version": "Phiên bản DB:",
|
||||
"sync_version": "Phiên bản liên kết:"
|
||||
"title": "Về Trilium Notes"
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Thêm liên kết",
|
||||
@@ -29,8 +26,7 @@
|
||||
"close": "Đóng"
|
||||
},
|
||||
"help": {
|
||||
"other": "Khác",
|
||||
"notSet": "chưa được đặt"
|
||||
"other": "Khác"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
@@ -73,16 +69,12 @@
|
||||
"add_label": {
|
||||
"add_label": "Thêm nhãn",
|
||||
"label_name_placeholder": "tên nhãn",
|
||||
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn",
|
||||
"new_value_placeholder": "giá trị mới"
|
||||
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn"
|
||||
},
|
||||
"rename_label": {
|
||||
"rename_label": "Đặt lại tên nhãn"
|
||||
},
|
||||
"call_to_action": {
|
||||
"dismiss": "Bỏ qua"
|
||||
},
|
||||
"abstract_search_option": {
|
||||
"remove_this_search_option": "Xoá lựa chọn tìm kiếm này"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import utils from "../../services/utils.js";
|
||||
import contextMenu, { MenuCommandItem } from "../../menus/context_menu.js";
|
||||
import contextMenu from "../../menus/context_menu.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import ButtonFromNoteWidget from "./button_from_note.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { CommandNames } from "../../components/app_context.js";
|
||||
import type { WebContents } from "electron";
|
||||
import link from "../../services/link.js";
|
||||
|
||||
interface ContextMenuItem {
|
||||
title: string;
|
||||
idx: string;
|
||||
uiIcon: string;
|
||||
}
|
||||
|
||||
export default class HistoryNavigationButton extends ButtonFromNoteWidget {
|
||||
private webContents?: WebContents;
|
||||
@@ -42,20 +47,24 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
let items: MenuCommandItem<string>[] = [];
|
||||
let items: ContextMenuItem[] = [];
|
||||
|
||||
const history = this.webContents.navigationHistory.getAllEntries();
|
||||
const activeIndex = this.webContents.navigationHistory.getActiveIndex();
|
||||
const history = this.webContents.navigationHistory;
|
||||
const activeIndex = history.getActiveIndex();
|
||||
|
||||
for (const idx in history) {
|
||||
const { notePath } = link.parseNavigationStateFromUrl(history[idx].url);
|
||||
if (!notePath) continue;
|
||||
const url = history[idx];
|
||||
const parts = url.split("#");
|
||||
if (parts.length < 2) continue;
|
||||
|
||||
const notePathWithTab = parts[1];
|
||||
const notePath = notePathWithTab.split("-")[0];
|
||||
|
||||
const title = await treeService.getNotePathTitle(notePath);
|
||||
|
||||
items.push({
|
||||
title,
|
||||
command: idx,
|
||||
idx,
|
||||
uiIcon:
|
||||
parseInt(idx) === activeIndex
|
||||
? "bx bx-radio-circle-marked" // compare with type coercion!
|
||||
@@ -75,10 +84,9 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items,
|
||||
selectMenuItemHandler: (item: MenuCommandItem<string>) => {
|
||||
if (item && item.command && this.webContents) {
|
||||
const idx = parseInt(item.command, 10);
|
||||
this.webContents.navigationHistory.goToIndex(idx);
|
||||
selectMenuItemHandler: (item: any) => {
|
||||
if (item && item.idx && this.webContents) {
|
||||
this.webContents.goToIndex(item.idx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import appContext, { type CommandData, type CommandListenerData, type EventData,
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
import type NoteContext from "../../components/note_context.js";
|
||||
import Component from "../../components/component.js";
|
||||
import splitService from "../../services/resizer.js";
|
||||
|
||||
interface NoteContextEvent {
|
||||
noteContext: NoteContext;
|
||||
}
|
||||
@@ -52,10 +52,6 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
await widget.handleEvent("setNoteContext", { noteContext });
|
||||
|
||||
this.child(widget);
|
||||
|
||||
if (noteContext.mainNtxId && noteContext.ntxId) {
|
||||
splitService.setupNoteSplitResizer([noteContext.mainNtxId,noteContext.ntxId]);
|
||||
}
|
||||
}
|
||||
|
||||
async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: EventData<"openNewNoteSplit">) {
|
||||
@@ -99,9 +95,9 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
async closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) {
|
||||
closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) {
|
||||
if (ntxId) {
|
||||
await appContext.tabManager.removeNoteContext(ntxId);
|
||||
appContext.tabManager.removeNoteContext(ntxId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,8 +137,6 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
|
||||
// activate context that now contains the original note
|
||||
await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]);
|
||||
|
||||
splitService.moveNoteSplitResizer(ntxIds[leftIndex]);
|
||||
}
|
||||
|
||||
activeContextChangedEvent() {
|
||||
@@ -163,8 +157,6 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
recursiveCleanup(widget);
|
||||
delete this.widgets[ntxId];
|
||||
}
|
||||
|
||||
splitService.delNoteSplitResizer(ntxIds);
|
||||
}
|
||||
|
||||
contextsReopenedEvent({ ntxId, afterNtxId }: EventData<"contextsReopened">) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { t } from "../../services/i18n";
|
||||
import server from "../../services/server";
|
||||
import toast from "../../services/toast";
|
||||
import Button from "../react/Button";
|
||||
import FormToggle from "../react/FormToggle";
|
||||
import Modal from "../react/Modal";
|
||||
import FormList, { FormListItem } from "../react/FormList";
|
||||
import utils from "../../services/utils";
|
||||
@@ -19,15 +18,12 @@ import open from "../../services/open";
|
||||
import ActionButton from "../react/ActionButton";
|
||||
import options from "../../services/options";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import { diffWords } from "diff";
|
||||
|
||||
export default function RevisionsDialog() {
|
||||
const [ note, setNote ] = useState<FNote>();
|
||||
const [ noteContent, setNoteContent ] = useState<string>();
|
||||
const [ revisions, setRevisions ] = useState<RevisionItem[]>();
|
||||
const [ currentRevision, setCurrentRevision ] = useState<RevisionItem>();
|
||||
const [ shown, setShown ] = useState(false);
|
||||
const [ showDiff, setShowDiff ] = useState(false);
|
||||
const [ refreshCounter, setRefreshCounter ] = useState(0);
|
||||
|
||||
useTriliumEvent("showRevisions", async ({ noteId }) => {
|
||||
@@ -41,10 +37,8 @@ export default function RevisionsDialog() {
|
||||
useEffect(() => {
|
||||
if (note?.noteId) {
|
||||
server.get<RevisionItem[]>(`notes/${note.noteId}/revisions`).then(setRevisions);
|
||||
note.getContent().then(setNoteContent);
|
||||
} else {
|
||||
setRevisions(undefined);
|
||||
setNoteContent(undefined);
|
||||
}
|
||||
}, [ note?.noteId, refreshCounter ]);
|
||||
|
||||
@@ -60,42 +54,22 @@ export default function RevisionsDialog() {
|
||||
helpPageId="vZWERwf8U3nx"
|
||||
bodyStyle={{ display: "flex", height: "80vh" }}
|
||||
header={
|
||||
!!revisions?.length && (
|
||||
<>
|
||||
{["text", "code", "mermaid"].includes(currentRevision?.type ?? "") && (
|
||||
<FormToggle
|
||||
currentValue={showDiff}
|
||||
onChange={(newValue) => setShowDiff(newValue)}
|
||||
switchOnName={t("revisions.diff_on")}
|
||||
switchOffName={t("revisions.diff_off")}
|
||||
switchOnTooltip={t("revisions.diff_on_hint")}
|
||||
switchOffTooltip={t("revisions.diff_off_hint")}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
text={t("revisions.delete_all_revisions")}
|
||||
size="small"
|
||||
style={{ padding: "0 10px" }}
|
||||
onClick={async () => {
|
||||
const text = t("revisions.confirm_delete_all");
|
||||
(!!revisions?.length && <Button text={t("revisions.delete_all_revisions")} size="small" style={{ padding: "0 10px" }}
|
||||
onClick={async () => {
|
||||
const text = t("revisions.confirm_delete_all");
|
||||
|
||||
if (note && await dialog.confirm(text)) {
|
||||
await server.remove(`notes/${note.noteId}/revisions`);
|
||||
setRevisions([]);
|
||||
setCurrentRevision(undefined);
|
||||
toast.showMessage(t("revisions.revisions_deleted"));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
if (note && await dialog.confirm(text)) {
|
||||
await server.remove(`notes/${note.noteId}/revisions`);
|
||||
setRevisions([]);
|
||||
setCurrentRevision(undefined);
|
||||
toast.showMessage(t("revisions.revisions_deleted"));
|
||||
}
|
||||
}}/>)
|
||||
}
|
||||
footer={<RevisionFooter note={note} />}
|
||||
footerStyle={{ paddingTop: 0, paddingBottom: 0 }}
|
||||
onHidden={() => {
|
||||
setShown(false);
|
||||
setShowDiff(false);
|
||||
setNote(undefined);
|
||||
setCurrentRevision(undefined);
|
||||
setRevisions(undefined);
|
||||
@@ -118,13 +92,10 @@ export default function RevisionsDialog() {
|
||||
marginLeft: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
maxWidth: "calc(100% - 150px)",
|
||||
minWidth: 0
|
||||
}}>
|
||||
<RevisionPreview
|
||||
noteContent={noteContent}
|
||||
revisionItem={currentRevision}
|
||||
showDiff={showDiff}
|
||||
revisionItem={currentRevision}
|
||||
setShown={setShown}
|
||||
onRevisionDeleted={() => {
|
||||
setRefreshCounter(c => c + 1);
|
||||
@@ -150,10 +121,8 @@ function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: Re
|
||||
</FormList>);
|
||||
}
|
||||
|
||||
function RevisionPreview({noteContent, revisionItem, showDiff, setShown, onRevisionDeleted }: {
|
||||
noteContent?: string,
|
||||
revisionItem?: RevisionItem,
|
||||
showDiff: boolean,
|
||||
function RevisionPreview({ revisionItem, setShown, onRevisionDeleted }: {
|
||||
revisionItem?: RevisionItem,
|
||||
setShown: Dispatch<StateUpdater<boolean>>,
|
||||
onRevisionDeleted?: () => void
|
||||
}) {
|
||||
@@ -210,7 +179,7 @@ function RevisionPreview({noteContent, revisionItem, showDiff, setShown, onRevis
|
||||
</div>)}
|
||||
</div>
|
||||
<div className="revision-content use-tn-links" style={{ overflow: "auto", wordBreak: "break-word" }}>
|
||||
<RevisionContent noteContent={noteContent} revisionItem={revisionItem} fullRevision={fullRevision} showDiff={showDiff}/>
|
||||
<RevisionContent revisionItem={revisionItem} fullRevision={fullRevision} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -228,15 +197,12 @@ const CODE_STYLE: CSSProperties = {
|
||||
whiteSpace: "pre-wrap"
|
||||
};
|
||||
|
||||
function RevisionContent({ noteContent, revisionItem, fullRevision, showDiff }: { noteContent?:string, revisionItem?: RevisionItem, fullRevision?: RevisionPojo, showDiff: boolean}) {
|
||||
function RevisionContent({ revisionItem, fullRevision }: { revisionItem?: RevisionItem, fullRevision?: RevisionPojo }) {
|
||||
const content = fullRevision?.content;
|
||||
if (!revisionItem || !content) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (showDiff) {
|
||||
return <RevisionContentDiff noteContent={noteContent} itemContent={content} itemType={revisionItem.type}/>
|
||||
}
|
||||
switch (revisionItem.type) {
|
||||
case "text":
|
||||
return <RevisionContentText content={content} />
|
||||
@@ -301,48 +267,6 @@ function RevisionContentText({ content }: { content: string | Buffer<ArrayBuffer
|
||||
return <div ref={contentRef} className="ck-content" dangerouslySetInnerHTML={{ __html: content as string }}></div>
|
||||
}
|
||||
|
||||
function RevisionContentDiff({ noteContent, itemContent, itemType }: {
|
||||
noteContent?: string,
|
||||
itemContent: string | Buffer<ArrayBufferLike> | undefined,
|
||||
itemType: string
|
||||
}) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!noteContent || typeof itemContent !== "string") {
|
||||
if (contentRef.current) {
|
||||
contentRef.current.textContent = t("revisions.diff_not_available");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let processedNoteContent = noteContent;
|
||||
let processedItemContent = itemContent;
|
||||
|
||||
if (itemType === "text") {
|
||||
processedNoteContent = utils.formatHtml(noteContent);
|
||||
processedItemContent = utils.formatHtml(itemContent);
|
||||
}
|
||||
|
||||
const diff = diffWords(processedNoteContent, processedItemContent);
|
||||
const diffHtml = diff.map(part => {
|
||||
if (part.added) {
|
||||
return `<span class="revision-diff-added">${utils.escapeHtml(part.value)}</span>`;
|
||||
} else if (part.removed) {
|
||||
return `<span class="revision-diff-removed">${utils.escapeHtml(part.value)}</span>`;
|
||||
} else {
|
||||
return utils.escapeHtml(part.value);
|
||||
}
|
||||
}).join("");
|
||||
|
||||
if (contentRef.current) {
|
||||
contentRef.current.innerHTML = diffHtml;
|
||||
}
|
||||
}, [noteContent, itemContent, itemType]);
|
||||
|
||||
return <div ref={contentRef} className="ck-content" style={{ whiteSpace: "pre-wrap" }}></div>;
|
||||
}
|
||||
|
||||
function RevisionFooter({ note }: { note?: FNote }) {
|
||||
if (!note) {
|
||||
return <></>;
|
||||
|
||||
@@ -8,7 +8,6 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import FindInText from "./find_in_text.js";
|
||||
import FindInCode from "./find_in_code.js";
|
||||
import { isIMEComposing } from "../services/shortcuts.js";
|
||||
import FindInHtml from "./find_in_html.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
|
||||
@@ -163,11 +162,6 @@ export default class FindWidget extends NoteContextAwareWidget {
|
||||
this.$replaceButton.on("click", () => this.replace());
|
||||
|
||||
this.$input.on("keydown", async (e) => {
|
||||
// Skip processing during IME composition
|
||||
if (isIMEComposing(e.originalEvent as KeyboardEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((e.metaKey || e.ctrlKey) && (e.key === "F" || e.key === "f")) {
|
||||
// If ctrl+f is pressed when the findbox is shown, select the
|
||||
// whole input to find
|
||||
|
||||
@@ -8,7 +8,6 @@ import "./note_title.css";
|
||||
import { isLaunchBarConfig } from "../services/utils";
|
||||
import appContext from "../components/app_context";
|
||||
import branches from "../services/branches";
|
||||
import { isIMEComposing } from "../services/shortcuts";
|
||||
|
||||
export default function NoteTitleWidget() {
|
||||
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
|
||||
@@ -79,12 +78,6 @@ export default function NoteTitleWidget() {
|
||||
spacedUpdate.scheduleUpdate();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// Skip processing if IME is composing to prevent interference
|
||||
// with text input in CJK languages
|
||||
if (isIMEComposing(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus on the note content when pressing enter.
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -219,22 +219,21 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
this.$tree = this.$widget.find(".tree");
|
||||
this.$treeActions = this.$widget.find(".tree-actions");
|
||||
|
||||
this.$tree.on("mousedown", (e: JQuery.MouseDownEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (e.button !== 0) return;
|
||||
this.$tree.on("mousedown", ".unhoist-button", () => hoistedNoteService.unhoist());
|
||||
this.$tree.on("mousedown", ".refresh-search-button", (e) => this.refreshSearch(e));
|
||||
this.$tree.on("mousedown", ".add-note-button", (e) => {
|
||||
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||
const parentNotePath = treeService.getNotePath(node);
|
||||
|
||||
if (target.classList.contains("unhoist-button")) {
|
||||
hoistedNoteService.unhoist();
|
||||
} else if (target.classList.contains("refresh-search-button")) {
|
||||
this.refreshSearch(e);
|
||||
} else if (target.classList.contains("add-note-button")) {
|
||||
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||
const parentNotePath = treeService.getNotePath(node);
|
||||
noteCreateService.createNote(parentNotePath, { isProtected: node.data.isProtected });
|
||||
} else if (target.classList.contains("enter-workspace-button")) {
|
||||
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||
this.triggerCommand("hoistNote", { noteId: node.data.noteId });
|
||||
}
|
||||
noteCreateService.createNote(parentNotePath, {
|
||||
isProtected: node.data.isProtected
|
||||
});
|
||||
});
|
||||
|
||||
this.$tree.on("mousedown", ".enter-workspace-button", (e) => {
|
||||
const node = $.ui.fancytree.getNode(e as unknown as Event);
|
||||
|
||||
this.triggerCommand("hoistNote", { noteId: node.data.noteId });
|
||||
});
|
||||
|
||||
// fancytree doesn't support middle click, so this is a way to support it
|
||||
|
||||
@@ -23,21 +23,15 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
noteSwitchedAndActivatedEvent({ noteContext }: EventData<"setNoteContext">) {
|
||||
this.noteContext = noteContext;
|
||||
|
||||
noteSwitchedAndActivatedEvent() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
noteSwitchedEvent({ noteContext }: EventData<"setNoteContext">) {
|
||||
this.noteContext = noteContext;
|
||||
|
||||
noteSwitchedEvent() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
activeContextChangedEvent({ noteContext }: EventData<"setNoteContext">) {
|
||||
this.noteContext = noteContext;
|
||||
|
||||
activeContextChangedEvent() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import linkService from "../services/link.js";
|
||||
import froca from "../services/froca.js";
|
||||
import utils from "../services/utils.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import shortcutService, { isIMEComposing } from "../services/shortcuts.js";
|
||||
import shortcutService from "../services/shortcuts.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import { Dropdown, Tooltip } from "bootstrap";
|
||||
|
||||
@@ -15,15 +15,13 @@ const TPL = /*html*/`
|
||||
padding: 10px 10px 10px 0px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
.quick-search button, .quick-search input {
|
||||
border: 0;
|
||||
font-size: 100% !important;
|
||||
}
|
||||
|
||||
.quick-search .dropdown-menu {
|
||||
--quick-search-item-delimiter-color: var(--dropdown-border-color);
|
||||
|
||||
.quick-search .dropdown-menu {
|
||||
max-height: 80vh;
|
||||
min-width: 400px;
|
||||
max-width: 720px;
|
||||
@@ -40,14 +38,14 @@ const TPL = /*html*/`
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quick-search .dropdown-item + .dropdown-item::after {
|
||||
.quick-search .dropdown-item:not(:last-child)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid var(--quick-search-item-delimiter-color);
|
||||
background: var(--dropdown-border-color);
|
||||
}
|
||||
|
||||
.quick-search .dropdown-item:last-child::after {
|
||||
@@ -94,8 +92,6 @@ const TPL = /*html*/`
|
||||
background-color: var(--accented-background-color);
|
||||
color: var(--main-text-color);
|
||||
font-size: .85em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Search result highlighting */
|
||||
@@ -110,10 +106,6 @@ const TPL = /*html*/`
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.quick-search .bx-loader {
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="input-group-prepend">
|
||||
@@ -180,14 +172,6 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
|
||||
if (utils.isMobile()) {
|
||||
this.$searchString.keydown((e) => {
|
||||
// Skip processing if IME is composing to prevent interference
|
||||
// with text input in CJK languages
|
||||
// Note: jQuery wraps the native event, so we access originalEvent
|
||||
const originalEvent = e.originalEvent as KeyboardEvent;
|
||||
if (originalEvent && isIMEComposing(originalEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.which === 13) {
|
||||
if (this.$dropdownMenu.is(":visible")) {
|
||||
this.search(); // just update already visible dropdown
|
||||
@@ -236,11 +220,7 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
this.isLoadingMore = false;
|
||||
|
||||
this.$dropdownMenu.empty();
|
||||
this.$dropdownMenu.append(`
|
||||
<span class="dropdown-item disabled">
|
||||
<span class="bx bx-loader bx-spin"></span>
|
||||
${t("quick-search.searching")}
|
||||
</span>`);
|
||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`);
|
||||
|
||||
const { searchResultNoteIds, searchResults, error } = await server.get<QuickSearchResponse>(`quick-search/${encodeURIComponent(searchString)}`);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import attribute_parser, { Attribute } from "../../../services/attribute_parser"
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import { escapeQuotes, getErrorMessage } from "../../../services/utils";
|
||||
import link from "../../../services/link";
|
||||
import { isIMEComposing } from "../../../services/shortcuts";
|
||||
import froca from "../../../services/froca";
|
||||
import contextMenu from "../../../menus/context_menu";
|
||||
import type { CommandData, FilteredCommandNames } from "../../../components/app_context";
|
||||
@@ -288,11 +287,6 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
|
||||
ref={wrapperRef}
|
||||
style="position: relative; padding-top: 10px; padding-bottom: 10px"
|
||||
onKeyDown={(e) => {
|
||||
// Skip processing during IME composition
|
||||
if (isIMEComposing(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "Enter") {
|
||||
// allow autocomplete to fill the result textarea
|
||||
setTimeout(() => save(), 100);
|
||||
|
||||
@@ -388,7 +388,7 @@ async function setupFonts() {
|
||||
if (!glob.isDev) {
|
||||
path = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`;
|
||||
} else {
|
||||
path = (await import("../../../../../node_modules/@excalidraw/excalidraw/dist/prod/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2?url")).default;
|
||||
path = (await import("../../../node_modules/@excalidraw/excalidraw/dist/prod/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2?url")).default;
|
||||
let pathComponents = path.split("/");
|
||||
path = pathComponents.slice(0, pathComponents.length - 2).join("/");
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { buildExtraCommands, type EditorConfig, PREMIUM_PLUGINS } from "@trilium
|
||||
import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
|
||||
import options from "../../../services/options.js";
|
||||
import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
|
||||
import emojiDefinitionsUrl from "@triliumnext/ckeditor5/src/emoji_definitions/en.json?url";
|
||||
import emojiDefinitionsUrl from "@triliumnext/ckeditor5/emoji_definitions/en.json?url";
|
||||
import { copyTextWithToast } from "../../../services/clipboard_ext.js";
|
||||
import getTemplates from "./snippets.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import utils from "../../../services/utils.js";
|
||||
import options from "../../../services/options.js";
|
||||
import IconAlignCenter from "@ckeditor/ckeditor5-icons/theme/icons/align-center.svg?raw";
|
||||
|
||||
const TEXT_FORMATTING_GROUP = {
|
||||
label: "Text formatting",
|
||||
@@ -78,7 +77,7 @@ export function buildClassicToolbar(multilineToolbar: boolean) {
|
||||
items: ["imageUpload", "|", "link", "bookmark", "internallink", "includeNote", "|", "specialCharacters", "emoji", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
|
||||
},
|
||||
"|",
|
||||
buildAlignmentToolbar(),
|
||||
"alignment",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
@@ -135,7 +134,7 @@ export function buildFloatingToolbar() {
|
||||
items: ["link", "bookmark", "internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
|
||||
},
|
||||
"|",
|
||||
buildAlignmentToolbar(),
|
||||
"alignment",
|
||||
"outdent",
|
||||
"indent",
|
||||
"|",
|
||||
@@ -148,11 +147,3 @@ export function buildFloatingToolbar() {
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function buildAlignmentToolbar() {
|
||||
return {
|
||||
label: "Alignment",
|
||||
icon: IconAlignCenter,
|
||||
items: ["alignment:left", "alignment:center", "alignment:right", "|", "alignment:justify"]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { buildSelectedBackgroundColor } from "../../components/touch_bar.js";
|
||||
import { buildConfig, BuildEditorOptions, OPEN_SOURCE_LICENSE_KEY } from "./ckeditor/config.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig, EditorConfig } from "@triliumnext/ckeditor5";
|
||||
import "@triliumnext/ckeditor5/index.css";
|
||||
import { updateTemplateCache } from "./ckeditor/snippets.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
|
||||
@@ -266,20 +266,9 @@ function Performance() {
|
||||
label={t("ui-performance.enable-backdrop-effects")}
|
||||
currentValue={backdropEffectsEnabled} onChange={setBackdropEffectsEnabled}
|
||||
/>
|
||||
|
||||
{isElectron() && <SmoothScrollEnabledOption />}
|
||||
|
||||
</OptionsSection>
|
||||
}
|
||||
|
||||
function SmoothScrollEnabledOption() {
|
||||
const [ smoothScrollEnabled, setSmoothScrollEnabled ] = useTriliumOptionBool("smoothScrollEnabled");
|
||||
|
||||
return <FormCheckbox
|
||||
label={`${t("ui-performance.enable-smooth-scroll")} ${t("ui-performance.app-restart-required")}`}
|
||||
currentValue={smoothScrollEnabled} onChange={setSmoothScrollEnabled}
|
||||
/>
|
||||
}
|
||||
|
||||
function MaxContentWidth() {
|
||||
const [ maxContentWidth, setMaxContentWidth ] = useTriliumOption("maxContentWidth");
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import AbstractCodeTypeWidget from "./abstract_code_type_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="note-detail-readonly-code note-detail-printable">
|
||||
@@ -34,7 +33,7 @@ export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
if (!blob) return;
|
||||
|
||||
const isFormattable = note.type === "text" && this.noteContext?.viewScope?.viewMode === "source";
|
||||
const content = isFormattable ? utils.formatHtml(blob.content) : blob.content;
|
||||
const content = isFormattable ? this.format(blob.content) : blob.content;
|
||||
|
||||
this._update(note, content);
|
||||
this.show();
|
||||
@@ -55,4 +54,52 @@ export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
|
||||
|
||||
resolve(this.$editor);
|
||||
}
|
||||
|
||||
format(html: string) {
|
||||
let indent = "\n";
|
||||
const tab = "\t";
|
||||
let i = 0;
|
||||
let pre: { indent: string; tag: string }[] = [];
|
||||
|
||||
html = html
|
||||
.replace(new RegExp("<pre>((.|\\t|\\n|\\r)+)?</pre>"), function (x) {
|
||||
pre.push({ indent: "", tag: x });
|
||||
return "<--TEMPPRE" + i++ + "/-->";
|
||||
})
|
||||
.replace(new RegExp("<[^<>]+>[^<]?", "g"), function (x) {
|
||||
let ret;
|
||||
const tagRegEx = /<\/?([^\s/>]+)/.exec(x);
|
||||
let tag = tagRegEx ? tagRegEx[1] : "";
|
||||
let p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x);
|
||||
|
||||
if (p) {
|
||||
const pInd = parseInt(p[1]);
|
||||
pre[pInd].indent = indent;
|
||||
}
|
||||
|
||||
if (["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"].indexOf(tag) >= 0) {
|
||||
// self closing tag
|
||||
ret = indent + x;
|
||||
} else {
|
||||
if (x.indexOf("</") < 0) {
|
||||
//open tag
|
||||
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length);
|
||||
else ret = indent + x;
|
||||
!p && (indent += tab);
|
||||
} else {
|
||||
//close tag
|
||||
indent = indent.substr(0, indent.length - 1);
|
||||
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length);
|
||||
else ret = indent + x;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
for (i = pre.length; i--;) {
|
||||
html = html.replace("<--TEMPPRE" + i + "/-->", pre[i].tag.replace("<pre>", "<pre>\n").replace("</pre>", pre[i].indent + "</pre>"));
|
||||
}
|
||||
|
||||
return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,26 @@
|
||||
import { join, resolve } from 'path';
|
||||
import { defineConfig, type Plugin } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||
import asset_path from './src/asset_path';
|
||||
import webpackStatsPlugin from 'rollup-plugin-webpack-stats';
|
||||
import preact from "@preact/preset-vite";
|
||||
|
||||
const assets = [ "assets", "stylesheets", "fonts", "translations" ];
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
let plugins: any = [
|
||||
preact({
|
||||
babel: {
|
||||
compact: !isDev
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
if (!isDev) {
|
||||
plugins = [
|
||||
...plugins,
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/apps/client',
|
||||
base: process.env.NODE_ENV === "production" ? "" : asset_path,
|
||||
server: {
|
||||
port: 4200,
|
||||
host: 'localhost',
|
||||
},
|
||||
preview: {
|
||||
port: 4300,
|
||||
host: 'localhost',
|
||||
},
|
||||
plugins: [
|
||||
preact(),
|
||||
viteStaticCopy({
|
||||
targets: assets.map((asset) => ({
|
||||
src: `src/${asset}/*`,
|
||||
@@ -29,20 +32,13 @@ if (!isDev) {
|
||||
structured: true,
|
||||
targets: [
|
||||
{
|
||||
src: "../../node_modules/@excalidraw/excalidraw/dist/prod/fonts/*",
|
||||
src: "node_modules/@excalidraw/excalidraw/dist/prod/fonts/*",
|
||||
dest: "",
|
||||
}
|
||||
]
|
||||
}),
|
||||
webpackStatsPlugin()
|
||||
]
|
||||
}
|
||||
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/apps/client',
|
||||
base: "",
|
||||
plugins,
|
||||
] as Plugin[],
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
@@ -62,6 +58,10 @@ export default defineConfig(() => ({
|
||||
"preact/hooks"
|
||||
]
|
||||
},
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
build: {
|
||||
target: "esnext",
|
||||
outDir: './dist',
|
||||
@@ -100,6 +100,18 @@ export default defineConfig(() => ({
|
||||
"./src/test/setup.ts"
|
||||
]
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
"@triliumnext/highlightjs"
|
||||
]
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
quietDeps: true
|
||||
}
|
||||
}
|
||||
},
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
To build and run manually:
|
||||
|
||||
```sh
|
||||
pnpm build db-compare
|
||||
nx build db-compare
|
||||
node ./apps/db-compare/dist/compare.js
|
||||
```
|
||||
|
||||
To serve development build with arguments:
|
||||
|
||||
```sh
|
||||
pnpm dev --args "apps/server/spec/db/document_v214.db" --args "apps/server/spec/db/document_v214_migrated.db"
|
||||
nx serve db-compare --args "apps/server/spec/db/document_v214.db" --args "apps/server/spec/db/document_v214_migrated.db"
|
||||
```
|
||||
@@ -9,8 +9,63 @@
|
||||
"sqlite": "5.1.1",
|
||||
"sqlite3": "5.1.7"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsx src/compare.ts",
|
||||
"build": "esbuild --platform=node --format=cjs --outdir=dist src/compare.ts"
|
||||
"nx": {
|
||||
"name": "db-compare",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"platform": "node",
|
||||
"outputPath": "apps/db-compare/dist",
|
||||
"format": [
|
||||
"cjs"
|
||||
],
|
||||
"bundle": false,
|
||||
"main": "apps/db-compare/src/compare.ts",
|
||||
"tsConfig": "apps/db-compare/tsconfig.app.json",
|
||||
"assets": [],
|
||||
"esbuildOptions": {
|
||||
"sourcemap": true,
|
||||
"outExtension": {
|
||||
".js": ".js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"development": {},
|
||||
"production": {
|
||||
"esbuildOptions": {
|
||||
"sourcemap": false,
|
||||
"outExtension": {
|
||||
".js": ".js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nx/js:node",
|
||||
"defaultConfiguration": "development",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"options": {
|
||||
"buildTarget": "db-compare:build",
|
||||
"runBuildTargetDependencies": false
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "db-compare:build:development"
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "db-compare:build:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
apps/desktop-e2e/.env
Normal file
3
apps/desktop-e2e/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
TRILIUM_INTEGRATION_TEST=memory-no-store
|
||||
TRILIUM_PORT=8082
|
||||
TRILIUM_DATA_DIR=data
|
||||
15
apps/desktop-e2e/eslint.config.mjs
Normal file
15
apps/desktop-e2e/eslint.config.mjs
Normal file
@@ -0,0 +1,15 @@
|
||||
import playwright from "eslint-plugin-playwright";
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
playwright.configs["flat/recommended"],
|
||||
...baseConfig,
|
||||
{
|
||||
files: [
|
||||
"**/*.ts",
|
||||
"**/*.js"
|
||||
],
|
||||
// Override or add rules here
|
||||
rules: {}
|
||||
}
|
||||
];
|
||||
24
apps/desktop-e2e/package.json
Normal file
24
apps/desktop-e2e/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@triliumnext/desktop-e2e",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"nx": {
|
||||
"name": "desktop-e2e",
|
||||
"implicitDependencies": [
|
||||
"client",
|
||||
"desktop"
|
||||
],
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"dependsOn": [
|
||||
"desktop:build",
|
||||
"desktop:rebuild-deps"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "17.2.1",
|
||||
"electron": "37.4.0"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
require('dotenv').config({
|
||||
path: __dirname + "/" + ".env"
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
@@ -9,8 +14,6 @@ export default defineConfig({
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
testDir: "e2e",
|
||||
outputDir: "test-output",
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
@@ -60,7 +60,7 @@ test('First setup', async () => {
|
||||
// Verify the shared link is valid
|
||||
const requestContext = await request.newContext();
|
||||
const response = await requestContext.get(linkUrl!);
|
||||
await expect(response).toBeOK();
|
||||
expect(response).toBeOK();
|
||||
|
||||
await mainWindow.waitForTimeout(5000);
|
||||
});
|
||||
25
apps/desktop-e2e/tsconfig.json
Normal file
25
apps/desktop-e2e/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"outDir": "out-tsc/playwright",
|
||||
"sourceMap": false
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"playwright.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"out-tsc",
|
||||
"test-output",
|
||||
"eslint.config.js",
|
||||
"eslint.config.mjs",
|
||||
"eslint.config.cjs"
|
||||
]
|
||||
}
|
||||
1
apps/desktop/.npmrc
Normal file
1
apps/desktop/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
node-linker = hoisted
|
||||
1
apps/desktop/.serve-nodir.env
Normal file
1
apps/desktop/.serve-nodir.env
Normal file
@@ -0,0 +1 @@
|
||||
TRILIUM_PORT=37743
|
||||
3
apps/desktop/.serve.env
Normal file
3
apps/desktop/.serve.env
Normal file
@@ -0,0 +1,3 @@
|
||||
TRILIUM_PORT=37741
|
||||
TRILIUM_DATA_DIR=../data
|
||||
NODE_OPTIONS=--enable-source-maps
|
||||
@@ -3,23 +3,7 @@
|
||||
"version": "0.98.1",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"private": true,
|
||||
"main": "src/main.ts",
|
||||
"license": "AGPL-3.0-only",
|
||||
"author": {
|
||||
"name": "Trilium Notes Team",
|
||||
"email": "contact@eliandoran.me",
|
||||
"url": "https://github.com/TriliumNext/Notes"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data tsx ../../scripts/electron-start.mts src/main.ts",
|
||||
"start-no-dir": "cross-env TRILIUM_PORT=37743 tsx ../../scripts/electron-start.mts src/main.ts",
|
||||
"build": "tsx scripts/build.ts",
|
||||
"start-prod": "pnpm build && cross-env TRILIUM_DATA_DIR=data TRILIUM_PORT=37841 ELECTRON_IS_DEV=0 electron dist",
|
||||
"electron-forge:make": "pnpm build && cross-env electron-forge make dist",
|
||||
"electron-forge:package": "pnpm build && electron-forge package dist",
|
||||
"electron-forge:start": "pnpm build && electron-forge start dist",
|
||||
"e2e": "pnpm build && cross-env TRILIUM_INTEGRATION_TEST=memory-no-store TRILIUM_PORT=8082 TRILIUM_DATA_DIR=data-e2e ELECTRON_IS_DEV=0 playwright test"
|
||||
},
|
||||
"main": "main.cjs",
|
||||
"dependencies": {
|
||||
"@electron/remote": "2.1.3",
|
||||
"better-sqlite3": "^12.0.0",
|
||||
@@ -31,7 +15,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"electron": "37.4.0",
|
||||
@@ -44,5 +27,174 @@
|
||||
"@electron-forge/maker-zip": "7.8.3",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.8.3",
|
||||
"prebuild-install": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"forge": "./electron-forge/forge.config.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"start-prod": "nx build desktop && cross-env TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=dist TRILIUM_PORT=37841 electron dist/main.js"
|
||||
},
|
||||
"license": "AGPL-3.0-only",
|
||||
"author": {
|
||||
"name": "Trilium Notes Team",
|
||||
"email": "contact@eliandoran.me",
|
||||
"url": "https://github.com/TriliumNext/Notes"
|
||||
},
|
||||
"nx": {
|
||||
"name": "desktop",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"defaultConfiguration": "production",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"minify": true,
|
||||
"sourcemap": false
|
||||
},
|
||||
"development": {
|
||||
"minify": false,
|
||||
"sourcemap": true
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"main": "apps/desktop/src/electron-main.ts",
|
||||
"outputPath": "apps/desktop/dist",
|
||||
"outputFileName": "main.js",
|
||||
"tsConfig": "apps/desktop/tsconfig.app.json",
|
||||
"platform": "node",
|
||||
"external": [
|
||||
"electron",
|
||||
"@electron/remote",
|
||||
"better-sqlite3",
|
||||
"./xhr-sync-worker.js"
|
||||
],
|
||||
"format": [
|
||||
"cjs"
|
||||
],
|
||||
"thirdParty": true,
|
||||
"declaration": false,
|
||||
"esbuildOptions": {
|
||||
"splitting": false,
|
||||
"loader": {
|
||||
".css": "text"
|
||||
}
|
||||
},
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/dist/node_modules",
|
||||
"output": "node_modules"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/desktop/node_modules/@electron/remote",
|
||||
"output": "node_modules/@electron/remote"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/dist/assets",
|
||||
"output": "assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "packages/share-theme/src/templates",
|
||||
"output": "share-theme/templates"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/desktop/src/assets",
|
||||
"output": "assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/dist/public",
|
||||
"output": "public"
|
||||
},
|
||||
{
|
||||
"glob": "xhr-sync-worker.js",
|
||||
"input": "apps/server/node_modules/jsdom/lib/jsdom/living/xhr",
|
||||
"output": ""
|
||||
}
|
||||
],
|
||||
"declarationRootDir": "apps/desktop/src"
|
||||
}
|
||||
},
|
||||
"rebuild-deps": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"cache": false,
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist $(nix-shell -p electron_35 --run \"electron --version\")"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"rebuild-deps"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "electron main.cjs",
|
||||
"cwd": "{projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "nix-shell -p electron_35 --run \"electron {projectRoot}/dist/main.cjs\"",
|
||||
"cwd": ".",
|
||||
"forwardAllArgs": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-nodir": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"rebuild-deps"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "electron main.cjs",
|
||||
"cwd": "{projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "nix-shell -p electron_35 --run \"electron {projectRoot}/dist/main.cjs\"",
|
||||
"cwd": ".",
|
||||
"forwardAllArgs": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"electron-forge:make": {
|
||||
"dependsOn": [
|
||||
"build",
|
||||
"rebuild-deps"
|
||||
],
|
||||
"command": "pnpm -C apps/desktop exec cross-env NODE_INSTALLER=npm electron-forge make dist"
|
||||
},
|
||||
"electron-forge:package": {
|
||||
"dependsOn": [
|
||||
"build",
|
||||
"rebuild-deps"
|
||||
],
|
||||
"command": "pnpm -C apps/desktop exec cross-env NODE_INSTALLER=npm electron-forge package dist"
|
||||
},
|
||||
"electron-forge:start": {
|
||||
"dependsOn": [
|
||||
"build",
|
||||
"rebuild-deps"
|
||||
],
|
||||
"command": "pnpm -C apps/desktop exec cross-env NODE_INSTALLER=npm TRILIUM_DATA_DIR=./data electron-forge start dist"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { join } from "path";
|
||||
import BuildHelper from "../../../scripts/build-utils";
|
||||
import originalPackageJson from "../package.json" with { type: "json" };
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
const build = new BuildHelper("apps/desktop");
|
||||
|
||||
async function main() {
|
||||
await build.buildBackend([ "src/main.ts"]);
|
||||
|
||||
// Copy assets.
|
||||
build.copy("src/assets", "assets/");
|
||||
build.copy("/apps/server/src/assets", "assets/");
|
||||
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
|
||||
|
||||
// Copy node modules dependencies
|
||||
build.copyNodeModules([ "better-sqlite3", "bindings", "file-uri-to-path", "@electron/remote" ]);
|
||||
build.copy("/node_modules/jsdom/lib/jsdom/living/xhr/xhr-sync-worker.js", "xhr-sync-worker.js");
|
||||
|
||||
// Integrate the client.
|
||||
build.triggerBuildAndCopyTo("apps/client", "public/");
|
||||
build.deleteFromOutput("public/webpack-stats.json");
|
||||
|
||||
generatePackageJson();
|
||||
}
|
||||
|
||||
function generatePackageJson() {
|
||||
const { version, author, license, description, dependencies, devDependencies } = originalPackageJson;
|
||||
const packageJson = {
|
||||
name: "trilium",
|
||||
main: "main.cjs",
|
||||
version, author, license, description,
|
||||
dependencies: {
|
||||
"better-sqlite3": dependencies["better-sqlite3"],
|
||||
},
|
||||
devDependencies: {
|
||||
electron: devDependencies.electron
|
||||
},
|
||||
config: {
|
||||
forge: "../electron-forge/forge.config.ts"
|
||||
}
|
||||
};
|
||||
writeFileSync(join(build.outDir, "package.json"), JSON.stringify(packageJson, null, "\t"), "utf-8");
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -25,12 +25,6 @@ async function main() {
|
||||
// needed for excalidraw export https://github.com/zadam/trilium/issues/4271
|
||||
electron.app.commandLine.appendSwitch("enable-experimental-web-platform-features");
|
||||
electron.app.commandLine.appendSwitch("lang", options.getOptionOrNull("formattingLocale") ?? "en");
|
||||
|
||||
// Disable smooth scroll if the option is set
|
||||
const smoothScrollEnabled = options.getOptionOrNull("smoothScrollEnabled");
|
||||
if (smoothScrollEnabled === "false") {
|
||||
electron.app.commandLine.appendSwitch("disable-smooth-scrolling");
|
||||
}
|
||||
|
||||
// Electron 36 crashes with "Using GTK 2/3 and GTK 4 in the same process is not supported" on some distributions.
|
||||
// See https://github.com/electron/electron/issues/46538 for more info.
|
||||
@@ -15,8 +15,65 @@
|
||||
"@types/mime-types": "^3.0.0",
|
||||
"@types/yargs": "^17.0.33"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsx src/main.ts",
|
||||
"build": "esbuild --platform=node --format=cjs --outdir=dist src/main.ts"
|
||||
"nx": {
|
||||
"name": "dump-db",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"platform": "node",
|
||||
"outputPath": "apps/dump-db/dist",
|
||||
"format": [
|
||||
"cjs"
|
||||
],
|
||||
"bundle": false,
|
||||
"main": "apps/dump-db/src/main.ts",
|
||||
"tsConfig": "apps/dump-db/tsconfig.app.json",
|
||||
"assets": [
|
||||
"apps/dump-db/src/assets"
|
||||
],
|
||||
"esbuildOptions": {
|
||||
"sourcemap": true,
|
||||
"outExtension": {
|
||||
".js": ".js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"development": {},
|
||||
"production": {
|
||||
"esbuildOptions": {
|
||||
"sourcemap": false,
|
||||
"outExtension": {
|
||||
".js": ".js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nx/js:node",
|
||||
"defaultConfiguration": "development",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"options": {
|
||||
"buildTarget": "dump-db:build",
|
||||
"runBuildTargetDependencies": false
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "dump-db:build:development"
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "dump-db:build:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
apps/edit-docs/.env
Normal file
7
apps/edit-docs/.env
Normal file
@@ -0,0 +1,7 @@
|
||||
TRILIUM_DATA_DIR=../data
|
||||
TRILIUM_INTEGRATION_TEST=memory-no-store
|
||||
TRILIUM_PORT=37741
|
||||
|
||||
# Paths are relative to dist root
|
||||
DOCS_ROOT=../../../docs
|
||||
USER_GUIDE_ROOT=../../../apps/server/src/assets/doc_notes/en/User Guide
|
||||
@@ -15,8 +15,120 @@
|
||||
"electron": "37.4.0",
|
||||
"fs-extra": "11.3.1"
|
||||
},
|
||||
"scripts": {
|
||||
"edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-docs.ts",
|
||||
"edit-demo": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-demo.ts"
|
||||
}
|
||||
"nx": {
|
||||
"name": "edit-docs",
|
||||
"implicitDependencies": [
|
||||
"server"
|
||||
],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"main": "apps/edit-docs/src/edit-docs.ts",
|
||||
"outputPath": "apps/edit-docs/dist",
|
||||
"tsConfig": "apps/edit-docs/tsconfig.app.json",
|
||||
"platform": "node",
|
||||
"additionalEntryPoints": [
|
||||
"apps/edit-docs/src/edit-demo.ts"
|
||||
],
|
||||
"external": [
|
||||
"electron",
|
||||
"@electron/remote",
|
||||
"better-sqlite3",
|
||||
"./xhr-sync-worker.js"
|
||||
],
|
||||
"format": [
|
||||
"cjs"
|
||||
],
|
||||
"minify": false,
|
||||
"thirdParty": true,
|
||||
"declaration": false,
|
||||
"esbuildOptions": {
|
||||
"splitting": false,
|
||||
"loader": {
|
||||
".css": "text"
|
||||
}
|
||||
},
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/dist/node_modules",
|
||||
"output": "node_modules"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/dist/assets",
|
||||
"output": "assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/dist/public",
|
||||
"output": "public"
|
||||
},
|
||||
{
|
||||
"glob": "xhr-sync-worker.js",
|
||||
"input": "apps/server/node_modules/jsdom/lib/jsdom/living/xhr",
|
||||
"output": ""
|
||||
}
|
||||
],
|
||||
"declarationRootDir": "apps/edit-docs/src"
|
||||
}
|
||||
},
|
||||
"rebuild-deps": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"cache": true,
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "cross-env DEBUG=* tsx scripts/electron-rebuild.mts {projectRoot}/dist $(nix-shell -p electron_35 --run \"electron --version\")"
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-docs": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"rebuild-deps"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "electron edit-docs.cjs",
|
||||
"cwd": "{projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "nix-shell -p electron_35 --run \"electron {projectRoot}/dist/edit-docs.cjs\"",
|
||||
"cwd": ".",
|
||||
"forwardAllArgs": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit-demo": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"rebuild-deps"
|
||||
],
|
||||
"defaultConfiguration": "default",
|
||||
"configurations": {
|
||||
"default": {
|
||||
"command": "electron edit-demo.cjs",
|
||||
"cwd": "{projectRoot}/dist"
|
||||
},
|
||||
"nixos": {
|
||||
"command": "nix-shell -p electron_35 --run \"electron {projectRoot}/dist/edit-demo.cjs\"",
|
||||
"cwd": ".",
|
||||
"forwardAllArgs": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
apps/server-e2e/.env
Normal file
3
apps/server-e2e/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
TRILIUM_INTEGRATION_TEST=memory
|
||||
TRILIUM_PORT=8082
|
||||
TRILIUM_DATA_DIR=apps/server/spec/db
|
||||
@@ -2,10 +2,21 @@
|
||||
"name": "@triliumnext/server-e2e",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"e2e": "playwright test"
|
||||
"nx": {
|
||||
"name": "server-e2e",
|
||||
"implicitDependencies": [
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"dependsOn": [
|
||||
"server:build"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "17.2.2"
|
||||
"dotenv": "17.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,68 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import { join } from 'path';
|
||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
require('dotenv').config({
|
||||
path: __dirname + "/" + ".env"
|
||||
});
|
||||
|
||||
// For CI, you may want to set BASE_URL to the deployed application.
|
||||
const port = process.env['TRILIUM_PORT'] ?? "8082";
|
||||
const port = process.env['TRILIUM_PORT'];
|
||||
const baseURL = process.env['BASE_URL'] || `http://127.0.0.1:${port}`;
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "src",
|
||||
reporter: [["list"], ["html", { outputFolder: "test-output" }]],
|
||||
outputDir: "test-output",
|
||||
retries: 3,
|
||||
|
||||
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
baseURL,
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: !process.env.TRILIUM_DOCKER ? {
|
||||
command: 'pnpm start-prod-no-dir',
|
||||
command: 'pnpm server:start-prod',
|
||||
url: baseURL,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: join(__dirname, "../server"),
|
||||
env: {
|
||||
TRILIUM_DATA_DIR: "spec/db",
|
||||
TRILIUM_PORT: port,
|
||||
TRILIUM_INTEGRATION_TEST: "memory"
|
||||
},
|
||||
cwd: workspaceRoot,
|
||||
timeout: 5 * 60 * 1000
|
||||
} : undefined,
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// {
|
||||
// name: "firefox",
|
||||
// use: { ...devices["Desktop Firefox"] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: "webkit",
|
||||
// use: { ...devices["Desktop Safari"] },
|
||||
// },
|
||||
|
||||
// Uncomment for mobile browsers support
|
||||
/* {
|
||||
name: 'Mobile Chrome',
|
||||
use: { ...devices['Pixel 5'] },
|
||||
},
|
||||
{
|
||||
name: 'Mobile Safari',
|
||||
use: { ...devices['iPhone 12'] },
|
||||
}, */
|
||||
|
||||
// Uncomment for branded browsers
|
||||
/* {
|
||||
name: 'Microsoft Edge',
|
||||
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
},
|
||||
{
|
||||
name: 'Google Chrome',
|
||||
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
} */
|
||||
],
|
||||
});
|
||||
|
||||
@@ -79,7 +79,7 @@ test("Tabs are restored in right order", async ({ page, context }) => {
|
||||
|
||||
// Refresh the page and check the order.
|
||||
await app.goto( { preserveTabs: true });
|
||||
await expect(app.getTab(0)).toContainText("Code notes", { timeout: 15_000 });
|
||||
await expect(app.getTab(0)).toContainText("Code notes");
|
||||
await expect(app.getTab(1)).toContainText("Text notes");
|
||||
await expect(app.getTab(2)).toContainText("Mermaid");
|
||||
|
||||
|
||||
6
apps/server/.edit-integration-db.env
Normal file
6
apps/server/.edit-integration-db.env
Normal file
@@ -0,0 +1,6 @@
|
||||
TRILIUM_ENV=dev
|
||||
TRILIUM_DATA_DIR=./apps/server/spec/db
|
||||
TRILIUM_RESOURCE_DIR=./apps/server/dist
|
||||
TRILIUM_PUBLIC_SERVER=http://localhost:4200
|
||||
TRILIUM_PORT=8086
|
||||
TRILIUM_INTEGRATION_TEST=edit
|
||||
3
apps/server/.serve-nodir.env
Normal file
3
apps/server/.serve-nodir.env
Normal file
@@ -0,0 +1,3 @@
|
||||
TRILIUM_ENV=dev
|
||||
TRILIUM_RESOURCE_DIR=./apps/server/dist
|
||||
TRILIUM_PUBLIC_SERVER=http://localhost:4200
|
||||
4
apps/server/.serve.env
Normal file
4
apps/server/.serve.env
Normal file
@@ -0,0 +1,4 @@
|
||||
TRILIUM_ENV=dev
|
||||
TRILIUM_DATA_DIR=./apps/server/data
|
||||
TRILIUM_RESOURCE_DIR=./apps/server/src
|
||||
TRILIUM_PUBLIC_SERVER=http://localhost:4200
|
||||
3
apps/server/.start-prod.env
Normal file
3
apps/server/.start-prod.env
Normal file
@@ -0,0 +1,3 @@
|
||||
TRILIUM_ENV=production
|
||||
TRILIUM_DATA_DIR=./apps/server/data
|
||||
TRILIUM_PORT=8082
|
||||
4
apps/server/.test.env
Normal file
4
apps/server/.test.env
Normal file
@@ -0,0 +1,4 @@
|
||||
TRILIUM_ENV=dev
|
||||
TRILIUM_DATA_DIR=./spec/db
|
||||
TRILIUM_PUBLIC_SERVER=http://localhost:4200
|
||||
TRILIUM_INTEGRATION_TEST=memory
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.19.0-bullseye-slim AS builder
|
||||
FROM node:22.18.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.19.0-bullseye-slim
|
||||
FROM node:22.18.0-bullseye-slim
|
||||
# Install only runtime dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.19.0-alpine AS builder
|
||||
FROM node:22.18.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.19.0-alpine
|
||||
FROM node:22.18.0-alpine
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache su-exec shadow
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.19.0-alpine AS builder
|
||||
FROM node:22.18.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.19.0-alpine
|
||||
FROM node:22.18.0-alpine
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.19.0-bullseye-slim AS builder
|
||||
FROM node:22.18.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.19.0-bullseye-slim
|
||||
FROM node:22.18.0-bullseye-slim
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -3,38 +3,11 @@
|
||||
"version": "0.98.1",
|
||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||
"private": true,
|
||||
"main": "./src/main.ts",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development TRILIUM_ENV=dev TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=src tsx watch --ignore '../client/node_modules/.vite-temp' ./src/main.ts",
|
||||
"start-no-dir": "cross-env NODE_ENV=development TRILIUM_ENV=dev TRILIUM_RESOURCE_DIR=src tsx watch --ignore '../client/node_modules/.vite-temp' ./src/main.ts",
|
||||
"edit-integration-db": "cross-env NODE_ENV=development TRILIUM_PORT=8086 TRILIUM_ENV=dev TRILIUM_DATA_DIR=spec/db TRILIUM_INTEGRATION_TEST=edit TRILIUM_RESOURCE_DIR=src tsx watch --ignore '../client/node_modules/.vite-temp' ./src/main.ts",
|
||||
"build": "tsx scripts/build.ts",
|
||||
"package": "pnpm build && bash scripts/build-server.sh",
|
||||
"test": "vitest",
|
||||
"test-build": "vitest --config vitest.build.config.mts",
|
||||
"start-prod": "cross-env TRILIUM_DATA_DIR=data pnpm start-prod-no-dir",
|
||||
"start-prod-no-dir": "pnpm build && cross-env TRILIUM_ENV=production TRILIUM_PORT=8082 node dist/main.cjs",
|
||||
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular",
|
||||
"docker-build-debian": "pnpm build && docker build . -t triliumnext-debian -f Dockerfile",
|
||||
"docker-build-alpine": "pnpm build && docker build . -t triliumnext-alpine -f Dockerfile.alpine",
|
||||
"docker-build-rootless-debian": "pnpm build && docker build . -t triliumnext-rootless-debian -f Dockerfile.rootless",
|
||||
"docker-build-rootless-alpine": "pnpm build && docker build . -t triliumnext-rootless-alpine -f Dockerfile.alpine.rootless",
|
||||
"docker-start-debian": "pnpm docker-build-debian && docker run -p 8081:8080 triliumnext-debian",
|
||||
"docker-start-alpine": "pnpm docker-build-alpine && docker run -p 8081:8080 triliumnext-alpine",
|
||||
"docker-start-rootless-debian": "pnpm docker-build-rootless-debian && docker run -p 8081:8080 triliumnext-rootless-debian",
|
||||
"docker-start-rootless-alpine": "pnpm docker-build-rootless-alpine && docker run -p 8081:8080 triliumnext-rootless-alpine"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "0.61.0",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@electron/remote": "2.1.3",
|
||||
"@preact/preset-vite": "2.10.2",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/express-partial-content": "workspace:*",
|
||||
"@triliumnext/turndown-plugin-gfm": "workspace:*",
|
||||
"@types/archiver": "6.0.3",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/cls-hooked": "4.3.9",
|
||||
@@ -65,11 +38,16 @@
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/xml2js": "0.4.14",
|
||||
"express-http-proxy": "2.1.1",
|
||||
"@anthropic-ai/sdk": "0.60.0",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/express-partial-content": "workspace:*",
|
||||
"@triliumnext/turndown-plugin-gfm": "workspace:*",
|
||||
"archiver": "7.0.1",
|
||||
"async-mutex": "0.5.0",
|
||||
"axios": "1.11.0",
|
||||
"bindings": "1.5.0",
|
||||
"bootstrap": "5.3.8",
|
||||
"chardet": "2.1.0",
|
||||
"cheerio": "1.1.2",
|
||||
"chokidar": "4.0.3",
|
||||
@@ -77,7 +55,7 @@
|
||||
"compression": "1.8.1",
|
||||
"cookie-parser": "1.4.7",
|
||||
"csrf-csrf": "3.2.2",
|
||||
"dayjs": "1.11.18",
|
||||
"dayjs": "1.11.14",
|
||||
"debounce": "2.2.0",
|
||||
"debug": "4.4.1",
|
||||
"ejs": "3.1.10",
|
||||
@@ -86,9 +64,8 @@
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"express": "5.1.0",
|
||||
"express-http-proxy": "2.1.1",
|
||||
"express-openid-connect": "^2.17.1",
|
||||
"express-rate-limit": "8.1.0",
|
||||
"express-rate-limit": "8.0.1",
|
||||
"express-session": "1.18.2",
|
||||
"file-uri-to-path": "2.0.0",
|
||||
"fs-extra": "11.3.1",
|
||||
@@ -97,7 +74,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "25.5.2",
|
||||
"i18next": "25.4.2",
|
||||
"i18next-fs-backend": "2.6.0",
|
||||
"image-type": "6.0.0",
|
||||
"ini": "5.0.0",
|
||||
@@ -128,9 +105,270 @@
|
||||
"tmp": "0.2.5",
|
||||
"turndown": "7.2.1",
|
||||
"unescape": "1.0.1",
|
||||
"vite": "^7.1.3",
|
||||
"ws": "8.18.3",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "3.2.0"
|
||||
}
|
||||
},
|
||||
"nx": {
|
||||
"name": "server",
|
||||
"implicitDependencies": [
|
||||
"share-theme"
|
||||
],
|
||||
"targets": {
|
||||
"serve": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"client"
|
||||
],
|
||||
"target": "serve"
|
||||
}
|
||||
],
|
||||
"continuous": true,
|
||||
"options": {
|
||||
"command": "tsx watch --conditions development {projectRoot}/src/main.ts"
|
||||
}
|
||||
},
|
||||
"serve-nodir": {
|
||||
"executor": "@nx/js:node",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"client"
|
||||
],
|
||||
"target": "serve"
|
||||
},
|
||||
"build-without-client"
|
||||
],
|
||||
"continuous": true,
|
||||
"options": {
|
||||
"buildTarget": "server:build-without-client:development",
|
||||
"runBuildTargetDependencies": false
|
||||
}
|
||||
},
|
||||
"edit-integration-db": {
|
||||
"executor": "@nx/js:node",
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"client"
|
||||
],
|
||||
"target": "serve"
|
||||
},
|
||||
"build-without-client"
|
||||
],
|
||||
"continuous": true,
|
||||
"options": {
|
||||
"buildTarget": "server:build-without-client:development",
|
||||
"runBuildTargetDependencies": false
|
||||
}
|
||||
},
|
||||
"package": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"command": "bash apps/server/scripts/build-server.sh"
|
||||
},
|
||||
"start-prod": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"command": "node apps/server/dist/main.cjs"
|
||||
},
|
||||
"docker-build": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "{projectRoot}"
|
||||
},
|
||||
"executor": "nx:run-commands",
|
||||
"defaultConfiguration": "alpine",
|
||||
"configurations": {
|
||||
"debian": {
|
||||
"command": "docker build . -t triliumnext-debian -f Dockerfile"
|
||||
},
|
||||
"alpine": {
|
||||
"command": "docker build . -t triliumnext-alpine -f Dockerfile.alpine"
|
||||
},
|
||||
"rootless-debian": {
|
||||
"command": "docker build . -t triliumnext-rootless-debian -f Dockerfile.rootless"
|
||||
},
|
||||
"rootless-alpine": {
|
||||
"command": "docker build . -t triliumnext-rootless-alpine -f Dockerfile.alpine.rootless"
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker-start": {
|
||||
"dependsOn": [
|
||||
"docker-build"
|
||||
],
|
||||
"executor": "nx:run-commands",
|
||||
"defaultConfiguration": "alpine",
|
||||
"configurations": {
|
||||
"debian": {
|
||||
"command": "docker run -p 8081:8080 triliumnext-debian"
|
||||
},
|
||||
"alpine": {
|
||||
"command": "docker run -p 8081:8080 triliumnext-alpine"
|
||||
},
|
||||
"rootless-debian": {
|
||||
"command": "docker run -p 8081:8080 triliumnext-rootless-debian"
|
||||
},
|
||||
"rootless-alpine": {
|
||||
"command": "docker run -p 8081:8080 triliumnext-rootless-alpine"
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-without-client": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"main": "apps/server/src/main.ts",
|
||||
"outputPath": "apps/server/dist",
|
||||
"outputFileName": "main.js",
|
||||
"tsConfig": "apps/server/tsconfig.app.json",
|
||||
"platform": "node",
|
||||
"format": [
|
||||
"cjs"
|
||||
],
|
||||
"esbuildOptions": {
|
||||
"loader": {
|
||||
".css": "text",
|
||||
".ejs": "text"
|
||||
}
|
||||
},
|
||||
"declarationRootDir": "apps/server/src",
|
||||
"minify": false,
|
||||
"sourcemap": true,
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/src/assets",
|
||||
"output": "assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "packages/share-theme/src/templates",
|
||||
"output": "share-theme/templates"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"client:build"
|
||||
],
|
||||
"defaultConfiguration": "production",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"minify": true,
|
||||
"sourcemap": false
|
||||
},
|
||||
"development": {
|
||||
"minify": false,
|
||||
"sourcemap": true
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"main": "apps/server/src/main.ts",
|
||||
"outputPath": "apps/server/dist",
|
||||
"tsConfig": "apps/server/tsconfig.app.json",
|
||||
"platform": "node",
|
||||
"external": [
|
||||
"electron",
|
||||
"@electron/remote",
|
||||
"better-sqlite3",
|
||||
"./xhr-sync-worker.js"
|
||||
],
|
||||
"format": [
|
||||
"cjs"
|
||||
],
|
||||
"declarationRootDir": "apps/server/src",
|
||||
"thirdParty": true,
|
||||
"declaration": false,
|
||||
"esbuildOptions": {
|
||||
"splitting": false,
|
||||
"loader": {
|
||||
".css": "text",
|
||||
".ejs": "text"
|
||||
}
|
||||
},
|
||||
"additionalEntryPoints": [
|
||||
"apps/server/src/docker_healthcheck.ts"
|
||||
],
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/src/assets",
|
||||
"output": "assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "packages/share-theme/src/templates",
|
||||
"output": "share-theme/templates"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/client/dist",
|
||||
"output": "public",
|
||||
"ignore": [
|
||||
"webpack-stats.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/node_modules/better-sqlite3",
|
||||
"output": "node_modules/better-sqlite3"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/node_modules/bindings",
|
||||
"output": "node_modules/bindings"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/server/node_modules/file-uri-to-path",
|
||||
"output": "node_modules/file-uri-to-path"
|
||||
},
|
||||
{
|
||||
"glob": "xhr-sync-worker.js",
|
||||
"input": "apps/server/node_modules/jsdom/lib/jsdom/living/xhr",
|
||||
"output": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"test-build": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"command": "vitest --config {projectRoot}/vitest.build.config.mts"
|
||||
},
|
||||
"circular-deps": {
|
||||
"command": "pnpx dpdm -T {projectRoot}/src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
"./src/*": "./src/*",
|
||||
".": {
|
||||
"development": "./src/main.ts",
|
||||
"types": "./dist/main.d.ts",
|
||||
"import": "./dist/main.js",
|
||||
"default": "./dist/main.js"
|
||||
}
|
||||
},
|
||||
"types": "./dist/main.d.ts",
|
||||
"module": "./dist/main.js",
|
||||
"main": "./dist/main.js"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import BuildHelper from "../../../scripts/build-utils";
|
||||
|
||||
const build = new BuildHelper("apps/server");
|
||||
|
||||
async function main() {
|
||||
await build.buildBackend([ "src/main.ts", "src/docker_healthcheck.ts" ])
|
||||
|
||||
// Copy assets
|
||||
build.copy("src/assets", "assets/");
|
||||
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
|
||||
|
||||
// Copy node modules dependencies
|
||||
build.copyNodeModules([ "better-sqlite3", "bindings", "file-uri-to-path" ]);
|
||||
build.copy("/node_modules/jsdom/lib/jsdom/living/xhr/xhr-sync-worker.js", "xhr-sync-worker.js");
|
||||
build.copy("/node_modules/ckeditor5/dist/ckeditor5-content.css", "ckeditor5-content.css");
|
||||
|
||||
// Integrate the client.
|
||||
build.triggerBuildAndCopyTo("apps/client", "public/");
|
||||
build.deleteFromOutput("public/webpack-stats.json");
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -30,5 +30,5 @@ describe("etapi/import", () => {
|
||||
.expect(201);
|
||||
expect(response.body.note.title).toStrictEqual("Journal");
|
||||
expect(response.body.branch.parentNoteId).toStrictEqual("root");
|
||||
}, 10_000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,10 +74,7 @@
|
||||
"zoom-out": "Pomniejsz",
|
||||
"zoom-in": "Powiększ",
|
||||
"print-active-note": "Drukuj aktywną notatkę",
|
||||
"toggle-full-screen": "Przełącz pełny ekran",
|
||||
"cut-into-note": "Wycina zaznaczony tekst i tworzy podrzędną notatkę z tym tekstem",
|
||||
"edit-readonly-note": "Edytuj notatkę tylko do odczytu",
|
||||
"other": "Inne"
|
||||
"toggle-full-screen": "Przełącz pełny ekran"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"zoom-in": "Powiększ",
|
||||
|
||||
@@ -65,7 +65,6 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
|
||||
"monthlyBackupEnabled",
|
||||
"motionEnabled",
|
||||
"shadowsEnabled",
|
||||
"smoothScrollEnabled",
|
||||
"backdropEffectsEnabled",
|
||||
"maxContentWidth",
|
||||
"compressImages",
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "path";
|
||||
import express from "express";
|
||||
import { getResourceDir, isDev } from "../services/utils.js";
|
||||
import type serveStatic from "serve-static";
|
||||
import proxy from "express-http-proxy";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions<express.Response<unknown, Record<string, unknown>>>) => {
|
||||
@@ -16,22 +17,17 @@ const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOp
|
||||
};
|
||||
|
||||
async function register(app: express.Application) {
|
||||
const srcRoot = path.join(__dirname, "..", "..");
|
||||
const srcRoot = path.join(__dirname, "..");
|
||||
const resourceDir = getResourceDir();
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const { createServer: createViteServer } = await import("vite");
|
||||
const vite = await createViteServer({
|
||||
server: { middlewareMode: true },
|
||||
appType: "custom",
|
||||
cacheDir: path.join(srcRoot, "../../.cache/vite"),
|
||||
base: `/${assetUrlFragment}/`,
|
||||
root: path.join(srcRoot, "../client")
|
||||
});
|
||||
app.use(`/${assetUrlFragment}/`, (req, res, next) => {
|
||||
req.url = `/${assetUrlFragment}` + req.url;
|
||||
vite.middlewares(req, res, next);
|
||||
});
|
||||
if (isDev) {
|
||||
const publicUrl = process.env.TRILIUM_PUBLIC_SERVER;
|
||||
if (!publicUrl) {
|
||||
throw new Error("Missing TRILIUM_PUBLIC_SERVER");
|
||||
}
|
||||
app.use("/" + assetUrlFragment + `/@fs`, proxy(publicUrl, {
|
||||
proxyReqPathResolver: (req) => "/" + assetUrlFragment + `/@fs` + req.url
|
||||
}));
|
||||
} else {
|
||||
const publicDir = path.join(resourceDir, "public");
|
||||
if (!existsSync(publicDir)) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import attributeService from "../services/attributes.js";
|
||||
import config from "../services/config.js";
|
||||
import optionService from "../services/options.js";
|
||||
import log from "../services/log.js";
|
||||
import { isDev, isElectron, isWindows11 } from "../services/utils.js";
|
||||
import { isDev, isElectron } from "../services/utils.js";
|
||||
import protectedSessionService from "../services/protected_session.js";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
import assetPath from "../services/asset_path.js";
|
||||
@@ -42,7 +42,7 @@ function index(req: Request, res: Response) {
|
||||
platform: process.platform,
|
||||
isElectron,
|
||||
hasNativeTitleBar: isElectron && options.nativeTitleBarVisible === "true",
|
||||
hasBackgroundEffects: isElectron && isWindows11 && options.backgroundEffects === "true",
|
||||
hasBackgroundEffects: isElectron && options.backgroundEffects === "true",
|
||||
mainFontSize: parseInt(options.mainFontSize),
|
||||
treeFontSize: parseInt(options.treeFontSize),
|
||||
detailFontSize: parseInt(options.detailFontSize),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import assetPath from "./asset_path.js";
|
||||
import { isDev } from "./utils.js";
|
||||
|
||||
export default isDev ? assetPath : assetPath + "/src";
|
||||
export default assetPath + "/src";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user