diff --git a/.env b/.env deleted file mode 100644 index ff859e622..000000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -NODE_OPTIONS=--max_old_space_size=4096 \ No newline at end of file diff --git a/.github/actions/build-electron/action.yml b/.github/actions/build-electron/action.yml index add8f3156..26d35b79f 100644 --- a/.github/actions/build-electron/action.yml +++ b/.github/actions/build-electron/action.yml @@ -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 nx --project=desktop electron-forge:make -- --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }} + run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }} # Add DMG signing step - name: Sign DMG diff --git a/.github/actions/build-server/action.yml b/.github/actions/build-server/action.yml index b0ab05212..cc7eb0e87 100644 --- a/.github/actions/build-server/action.yml +++ b/.github/actions/build-server/action.yml @@ -10,7 +10,7 @@ runs: steps: - uses: pnpm/action-setup@v4 - name: Set up node & dependencies - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 22 cache: "pnpm" @@ -23,7 +23,7 @@ runs: shell: bash run: | pnpm run chore:update-build-info - pnpm nx --project=server package + pnpm run --filter server package - name: Prepare artifacts shell: bash run: | diff --git a/.github/instructions/nx.instructions.md b/.github/instructions/nx.instructions.md deleted file mode 100644 index d5894c44d..000000000 --- a/.github/instructions/nx.instructions.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -applyTo: '**' ---- - -// This file is automatically generated by Nx Console - -You are in an nx workspace using Nx 21.3.9 and pnpm as the package manager. - -You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user: - -# General Guidelines -- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture -- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration -- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors -- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool - -# Generation Guidelines -If the user wants to generate something, use the following flow: - -- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable -- get the available generators using the 'nx_generators' tool -- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them -- get generator details using the 'nx_generator_schema' tool -- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure -- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic -- open the generator UI using the 'nx_open_generate_ui' tool -- wait for the user to finish the generator -- read the generator log file using the 'nx_read_generator_log' tool -- use the information provided in the log file to answer the user's question or continue with what they were doing - -# Running Tasks Guidelines -If the user wants help with tasks or commands (which include keywords like "test", "build", "lint", or other similar actions), use the following flow: -- Use the 'nx_current_running_tasks_details' tool to get the list of tasks (this can include tasks that were completed, stopped or failed). -- If there are any tasks, ask the user if they would like help with a specific task then use the 'nx_current_running_task_output' tool to get the terminal output for that task/command -- Use the terminal output from 'nx_current_running_task_output' to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary -- If the user would like to rerun the task or command, always use `nx run ` to rerun in the terminal. This will ensure that the task will run in the nx context and will be run the same way it originally executed -- If the task was marked as "continuous" do not offer to rerun the task. This task is already running and the user can see the output in the terminal. You can use 'nx_current_running_task_output' to get the output of the task to verify the output. - - - diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 000000000..6558788c8 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,183 @@ +# GitHub Actions workflow for deploying MkDocs documentation to Cloudflare Pages +# This workflow builds and deploys your MkDocs site when changes are pushed to main +name: Deploy MkDocs Documentation + +on: + # Trigger on push to main branch + push: + branches: + - main + - master # Also support master branch + # Only run when docs files change + paths: + - 'docs/**' + - 'README.md' # README is synced to docs/index.md + - 'mkdocs.yml' + - 'requirements-docs.txt' + - '.github/workflows/deploy-docs.yml' + - 'scripts/fix-mkdocs-structure.ts' + + # Allow manual triggering from Actions tab + workflow_dispatch: + + # Run on pull requests for preview deployments + pull_request: + branches: + - main + - master + paths: + - 'docs/**' + - 'README.md' # README is synced to docs/index.md + - 'mkdocs.yml' + - 'requirements-docs.txt' + - '.github/workflows/deploy-docs.yml' + - 'scripts/fix-mkdocs-structure.ts' + +jobs: + build-and-deploy: + name: Build and Deploy MkDocs + runs-on: ubuntu-latest + timeout-minutes: 10 + + # Required permissions for deployment + permissions: + contents: read + deployments: write + pull-requests: write # For PR preview comments + id-token: write # For OIDC authentication (if needed) + + steps: + - name: Checkout Repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 # Fetch all history for git info and mkdocs-git-revision-date plugin + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: '3.13' + cache: 'pip' + cache-dependency-path: 'requirements-docs.txt' + + - name: Install MkDocs and Dependencies + run: | + pip install --upgrade pip + pip install -r requirements-docs.txt + env: + PIP_DISABLE_PIP_VERSION_CHECK: 1 + + # Setup pnpm before fixing docs structure + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + # Setup Node.js with pnpm + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: '22' + cache: 'pnpm' + + # Install Node.js dependencies for the TypeScript script + - name: Install Dependencies + run: | + pnpm install --frozen-lockfile + + - name: Fix Documentation Structure + run: | + # Fix duplicate navigation entries by moving overview pages to index.md + pnpm run chore:fix-mkdocs-structure + + - name: Build MkDocs Site + run: | + # Build with strict mode but allow expected warnings + mkdocs build --verbose || { + EXIT_CODE=$? + # Check if the only issue is expected warnings + if mkdocs build 2>&1 | grep -E "WARNING.*(README|not found)" && \ + [ $(mkdocs build 2>&1 | grep -c "ERROR") -eq 0 ]; then + echo "āœ… Build succeeded with expected warnings" + mkdocs build --verbose + else + echo "āŒ Build failed with unexpected errors" + exit $EXIT_CODE + fi + } + + - name: Validate Built Site + run: | + # Basic validation that important files exist + test -f site/index.html || (echo "ERROR: site/index.html not found" && exit 1) + test -f site/sitemap.xml || (echo "ERROR: site/sitemap.xml not found" && exit 1) + test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1) + echo "āœ… Site validation passed" + + # Install wrangler globally to avoid workspace issues + - name: Install Wrangler + run: | + npm install -g wrangler + + # Deploy using Wrangler (use pre-installed wrangler) + - name: Deploy to Cloudflare Pages + id: deploy + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy site --project-name=trilium-docs --branch=${{ github.ref_name }} + wranglerVersion: '' # Use pre-installed version + + # Deploy preview for PRs + - name: Deploy Preview to Cloudflare Pages + id: preview-deployment + if: github.event_name == 'pull_request' + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy site --project-name=trilium-docs --branch=pr-${{ github.event.pull_request.number }} + wranglerVersion: '' # Use pre-installed version + + # Post deployment URL as PR comment + - name: Comment PR with Preview URL + if: github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prNumber = context.issue.number; + // Construct preview URL based on Cloudflare Pages pattern + const previewUrl = `https://pr-${prNumber}.trilium-docs.pages.dev`; + const mainUrl = 'https://docs.triliumnotes.org'; + + // Check if we already commented + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber + }); + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('Documentation preview is ready') + ); + + const commentBody = `šŸ“š Documentation preview is ready!\n\nšŸ”— Preview URL: ${previewUrl}\nšŸ“– Production URL: ${mainUrl}\n\nāœ… All checks passed\n\n_This preview will be updated automatically with new commits._`; + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body: commentBody + }); + } diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 443f2aa35..ceed464c8 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -19,45 +19,24 @@ 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@v4 + uses: actions/setup-node@v5 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 @@ -66,7 +45,6 @@ jobs: runs-on: ubuntu-latest needs: - test_dev - - check-affected steps: - uses: actions/checkout@v5 - uses: pnpm/action-setup@v4 @@ -75,7 +53,7 @@ jobs: - name: Update build info run: pnpm run chore:update-build-info - name: Trigger client build - run: pnpm nx run client:build + run: pnpm client:build - name: Send client bundle stats to RelativeCI if: false uses: relative-ci/agent-action@v3 @@ -83,7 +61,7 @@ jobs: webpackStatsFile: ./apps/client/dist/webpack-stats.json key: ${{ secrets.RELATIVE_CI_CLIENT_KEY }} - name: Trigger server build - run: pnpm nx run server:build + run: pnpm run server:build - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v6 with: @@ -95,7 +73,6 @@ jobs: runs-on: ubuntu-latest needs: - build_docker - - check-affected strategy: matrix: include: @@ -112,7 +89,7 @@ jobs: - name: Update build info run: pnpm run chore:update-build-info - name: Trigger build - run: pnpm nx run server:build + run: pnpm server:build - name: Set IMAGE_NAME to lowercase run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index c4c6ed551..a1b38782d 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -44,7 +44,7 @@ jobs: - uses: pnpm/action-setup@v4 - name: Set up node & dependencies - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 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 exec nx run server-e2e:e2e + run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpm --filter=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@v4 + uses: actions/setup-node@v5 with: node-version: 22 cache: 'pnpm' diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f20c5a2d4..ab913d402 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -51,13 +51,12 @@ jobs: - uses: actions/checkout@v5 - uses: pnpm/action-setup@v4 - name: Set up node & dependencies - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 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 @@ -79,7 +78,7 @@ jobs: GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }} - name: Publish release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@v2.3.3 if: ${{ github.event_name != 'pull_request' }} with: make_latest: false @@ -120,7 +119,7 @@ jobs: arch: ${{ matrix.arch }} - name: Publish release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@v2.3.3 if: ${{ github.event_name != 'pull_request' }} with: make_latest: false diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 25086af64..07ad94fd7 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -19,14 +19,8 @@ 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@v4 + - uses: actions/setup-node@v5 with: node-version: 22 cache: 'pnpm' @@ -34,10 +28,12 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - run: pnpm exec playwright install --with-deps - - uses: nrwl/nx-set-shas@v4 - # 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 + - 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20161630f..16dec7492 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,13 +35,12 @@ jobs: - uses: actions/checkout@v5 - uses: pnpm/action-setup@v4 - name: Set up node & dependencies - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 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: @@ -115,7 +114,7 @@ jobs: path: upload - name: Publish stable release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@v2.3.3 with: draft: false body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md diff --git a/.gitignore b/.gitignore index 09749c270..b2c4e3c46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files. +/.cache # compiled output dist @@ -32,14 +33,11 @@ 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 @@ -47,4 +45,7 @@ upload *.tsbuildinfo /result -.svelte-kit \ No newline at end of file +.svelte-kit + +# docs +site/ diff --git a/.nvmrc b/.nvmrc index 89b93fd74..3a6161c2a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.18.0 \ No newline at end of file +22.19.0 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e64c42352..4db7b7470 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,7 +5,6 @@ "lokalise.i18n-ally", "ms-azuretools.vscode-docker", "ms-playwright.playwright", - "nrwl.angular-console", "redhat.vscode-yaml", "tobermory.es6-string-html", "vitest.explorer", diff --git a/.vscode/mcp.json b/.vscode/mcp.json deleted file mode 100644 index 28994bb29..000000000 --- a/.vscode/mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "servers": { - "nx-mcp": { - "type": "http", - "url": "http://localhost:9461/mcp" - } - } -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ee21bb3c..9ee96f4c1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -35,6 +35,5 @@ "docs/**/*.png": true, "apps/server/src/assets/doc_notes/**": true, "apps/edit-docs/demo/**": true - }, - "nxConsole.generateAiAgentRules": true + } } \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 942ad06e3..5e7089895 100644 --- a/CLAUDE.md +++ b/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 NX, 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 pnpm, with multiple applications and shared packages. ## Development Commands @@ -14,12 +14,9 @@ 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 ` - 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 @@ -28,13 +25,8 @@ 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 ` - Run tests for specific project - `pnpm coverage` - Generate coverage reports -### Linting & Type Checking -- `pnpm nx run :lint` - Lint specific project -- `pnpm nx run :typecheck` - Type check specific project - ## Architecture Overview ### Monorepo Structure @@ -94,7 +86,6 @@ 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 @@ -154,7 +145,7 @@ Trilium provides powerful user scripting capabilities: - Update schema in `apps/server/src/assets/db/schema.sql` ## Build System Notes -- Uses NX for monorepo management with build caching +- Uses pnpm for monorepo management - Vite for fast development builds - ESBuild for production optimization - pnpm workspaces for dependency management diff --git a/README.md b/README.md index 0d6626f6e..d76005478 100644 --- a/README.md +++ b/README.md @@ -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 nx run edit-docs:edit-docs +pnpm 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 nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32 +pnpm run --filter 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). diff --git a/_regroup/package.json b/_regroup/package.json index 7d20e9fc4..b61ea536a 100644 --- a/_regroup/package.json +++ b/_regroup/package.json @@ -36,12 +36,12 @@ }, "devDependencies": { "@playwright/test": "1.55.0", - "@stylistic/eslint-plugin": "5.2.3", + "@stylistic/eslint-plugin": "5.3.1", "@types/express": "5.0.3", - "@types/node": "22.18.0", + "@types/node": "22.18.1", "@types/yargs": "17.0.33", "@vitest/coverage-v8": "3.2.4", - "eslint": "9.34.0", + "eslint": "9.35.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.11", + "typedoc": "0.28.12", "typedoc-plugin-missing-exports": "4.1.0" }, "optionalDependencies": { diff --git a/apps/client/package.json b/apps/client/package.json index 27811a06e..b2abba919 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -9,8 +9,13 @@ "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.34.0", + "@eslint/js": "9.35.0", "@excalidraw/excalidraw": "0.18.0", "@fullcalendar/core": "6.1.19", "@fullcalendar/daygrid": "6.1.19", @@ -19,7 +24,7 @@ "@fullcalendar/multimonth": "6.1.19", "@fullcalendar/timegrid": "6.1.19", "@maplibre/maplibre-gl-leaflet": "0.1.3", - "@mermaid-js/layout-elk": "0.1.9", + "@mermaid-js/layout-elk": "0.2.0", "@mind-elixir/node-menu": "5.0.0", "@popperjs/core": "2.11.8", "@triliumnext/ckeditor5": "workspace:*", @@ -30,13 +35,13 @@ "autocomplete.js": "0.38.1", "bootstrap": "5.3.8", "boxicons": "2.1.4", - "dayjs": "1.11.14", + "dayjs": "1.11.18", "dayjs-plugin-utc": "0.1.2", "debounce": "2.2.0", "draggabilly": "3.0.0", - "force-graph": "1.50.1", + "force-graph": "1.51.0", "globals": "16.3.0", - "i18next": "25.4.2", + "i18next": "25.5.2", "i18next-http-backend": "3.0.2", "jquery": "3.7.1", "jquery.fancytree": "2.38.5", @@ -47,12 +52,12 @@ "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", "marked": "16.2.1", - "mermaid": "11.10.1", - "mind-elixir": "5.0.6", + "mermaid": "11.11.0", + "mind-elixir": "5.1.1", "normalize.css": "8.0.1", "panzoom": "9.4.3", "preact": "10.27.1", - "react-i18next": "15.7.2", + "react-i18next": "15.7.3", "split.js": "1.6.5", "svg-pan-zoom": "3.6.2", "tabulator-tables": "6.3.1", @@ -64,25 +69,12 @@ "@types/bootstrap": "5.2.10", "@types/jquery": "3.5.33", "@types/leaflet": "1.9.20", - "@types/leaflet-gpx": "1.3.7", + "@types/leaflet-gpx": "1.3.8", "@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": [ - "^build" - ] - }, - "circular-deps": { - "command": "pnpx dpdm -T {projectRoot}/src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular" - } - } } } \ No newline at end of file diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 57ff4084e..cf644cd3b 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -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 "./stylesheets/bootstrap.scss"; +import "bootstrap/dist/css/bootstrap.min.css"; import "boxicons/css/boxicons.min.css"; import "autocomplete.js/index_jquery.js"; diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index b7eceffa2..a324152c9 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -22,6 +22,7 @@ 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 = ` @@ -131,30 +132,33 @@ export default class MobileLayout { .child(new FlexContainer("column").filling().id("mobile-sidebar-wrapper").child(new QuickSearchWidget()).child(new NoteTreeWidget().cssBlock(FANCYTREE_CSS))) ) .child( - new ScreenContainer("detail", "column") + new ScreenContainer("detail", "row") .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 FlexContainer("row") - .contentSized() - .css("font-size", "larger") - .css("align-items", "center") - .child() - .child() - .child() + new NoteWrapperWidget() + .child( + new FlexContainer("row") + .contentSized() + .css("font-size", "larger") + .css("align-items", "center") + .child() + .child() + .child() + ) + .child() + .child() + .child(new PromotedAttributesWidget()) + .child( + new ScrollingContainer() + .filling() + .contentSized() + .child(new NoteDetailWidget()) + .child(new NoteListWidget(false)) + .child() + ) + .child() ) - .child() - .child() - .child(new PromotedAttributesWidget()) - .child( - new ScrollingContainer() - .filling() - .contentSized() - .child(new NoteDetailWidget()) - .child(new NoteListWidget(false)) - .child() - ) - .child() ) ) .child( diff --git a/apps/client/src/login.ts b/apps/client/src/login.ts index 2ba27be02..351ed4c1e 100644 --- a/apps/client/src/login.ts +++ b/apps/client/src/login.ts @@ -1,4 +1,4 @@ -import "./stylesheets/bootstrap.scss"; +import "bootstrap/dist/css/bootstrap.min.css"; // @ts-ignore - module = undefined // Required for correct loading of scripts in Electron diff --git a/apps/client/src/mobile.ts b/apps/client/src/mobile.ts index 805ffe276..507b9ff5a 100644 --- a/apps/client/src/mobile.ts +++ b/apps/client/src/mobile.ts @@ -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 "./stylesheets/bootstrap.scss"; +import "bootstrap/dist/css/bootstrap.min.css"; import "boxicons/css/boxicons.min.css"; import "autocomplete.js/index_jquery.js"; diff --git a/apps/client/src/services/doc_renderer.ts b/apps/client/src/services/doc_renderer.ts index 1eca3d7c6..de3cfba6c 100644 --- a/apps/client/src/services/doc_renderer.ts +++ b/apps/client/src/services/doc_renderer.ts @@ -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 ? new URL(window.glob.assetPath).pathname : window.glob.assetPath; + const basePath = window.glob.isDev ? window.glob.assetPath + "/.." : window.glob.assetPath; return `${basePath}/doc_notes/${language}/${docNameValue}.html`; } diff --git a/apps/client/src/services/resizer.ts b/apps/client/src/services/resizer.ts index e0dc40995..54a11e801 100644 --- a/apps/client/src/services/resizer.ts +++ b/apps/client/src/services/resizer.ts @@ -10,6 +10,10 @@ let leftInstance: ReturnType | null; let rightPaneWidth: number; let rightInstance: ReturnType | null; +const noteSplitMap = new Map | undefined>(); // key: a group of ntxIds, value: the corresponding Split instance +const noteSplitRafMap = new Map(); +let splitNoteContainer: HTMLElement | undefined; + function setupLeftPaneResizer(leftPaneVisible: boolean) { if (leftInstance) { leftInstance.destroy(); @@ -83,7 +87,86 @@ function setupRightPaneResizer() { } } +function findKeyByNtxId(ntxId: string): string[] | undefined { + // Find the corresponding key in noteSplitMap based on ntxId + for (const key of noteSplitMap.keys()) { + if (key.includes(ntxId)) return key; + } + return undefined; +} + +function setupNoteSplitResizer(ntxIds: string[]) { + let targetNtxIds: string[] | undefined; + for (const ntxId of ntxIds) { + targetNtxIds = findKeyByNtxId(ntxId); + if (targetNtxIds) break; + } + + if (targetNtxIds) { + noteSplitMap.get(targetNtxIds)?.destroy(); + for (const id of ntxIds) { + if (!targetNtxIds.includes(id)) { + targetNtxIds.push(id) + }; + } + } else { + targetNtxIds = [...ntxIds]; + } + noteSplitMap.set(targetNtxIds, undefined); + createSplitInstance(targetNtxIds); +} + + +function delNoteSplitResizer(ntxIds: string[]) { + let targetNtxIds = findKeyByNtxId(ntxIds[0]); + if (!targetNtxIds) { + return; + } + + noteSplitMap.get(targetNtxIds)?.destroy(); + noteSplitMap.delete(targetNtxIds); + targetNtxIds = targetNtxIds.filter(id => !ntxIds.includes(id)); + + if (targetNtxIds.length >= 2) { + noteSplitMap.set(targetNtxIds, undefined); + createSplitInstance(targetNtxIds); + } +} + +function moveNoteSplitResizer(ntxId: string) { + const targetNtxIds = findKeyByNtxId(ntxId); + if (!targetNtxIds) { + return; + } + noteSplitMap.get(targetNtxIds)?.destroy(); + noteSplitMap.set(targetNtxIds, undefined); + createSplitInstance(targetNtxIds); +} + +function createSplitInstance(targetNtxIds: string[]) { + const prevRafId = noteSplitRafMap.get(targetNtxIds); + if (prevRafId) { + cancelAnimationFrame(prevRafId); + } + + const rafId = requestAnimationFrame(() => { + splitNoteContainer = splitNoteContainer ?? $("#center-pane").find(".split-note-container-widget")[0]; + const splitPanels = [...splitNoteContainer.querySelectorAll(':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 + setupRightPaneResizer, + setupNoteSplitResizer, + delNoteSplitResizer, + moveNoteSplitResizer }; diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 77fec1366..a8f4f567f 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -297,6 +297,54 @@ function isHtmlEmpty(html: string) { ); } +function formatHtml(html: string) { + let indent = "\n"; + const tab = "\t"; + let i = 0; + let pre: { indent: string; tag: string }[] = []; + + html = html + .replace(new RegExp("
([\\s\\S]+?)?
"), 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("") 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("
", "
\n").replace("
", pre[i].indent + "
")); + } + + return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html; +} + export async function clearBrowserCache() { if (isElectron()) { const win = dynamicRequire("@electron/remote").getCurrentWindow(); @@ -855,6 +903,7 @@ export default { getNoteTypeClass, getMimeTypeClass, isHtmlEmpty, + formatHtml, clearBrowserCache, copySelectionToClipboard, dynamicRequire, diff --git a/apps/client/src/set_password.ts b/apps/client/src/set_password.ts index 67dfee187..47e02f1d1 100644 --- a/apps/client/src/set_password.ts +++ b/apps/client/src/set_password.ts @@ -1,4 +1,4 @@ -import "./stylesheets/bootstrap.scss"; +import "bootstrap/dist/css/bootstrap.min.css"; import "./stylesheets/auth.css"; // @TriliumNextTODO: is this even needed anymore? diff --git a/apps/client/src/setup.ts b/apps/client/src/setup.ts index ba117aaf7..d5659645e 100644 --- a/apps/client/src/setup.ts +++ b/apps/client/src/setup.ts @@ -1,7 +1,7 @@ import "jquery"; import utils from "./services/utils.js"; import ko from "knockout"; -import "./stylesheets/bootstrap.scss"; +import "bootstrap/dist/css/bootstrap.min.css"; // TriliumNextTODO: properly make use of below types // type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | ""; diff --git a/apps/client/src/share.ts b/apps/client/src/share.ts index c97f18c49..b438f0c0c 100644 --- a/apps/client/src/share.ts +++ b/apps/client/src/share.ts @@ -1,6 +1,6 @@ import "normalize.css"; import "boxicons/css/boxicons.min.css"; -import "@triliumnext/ckeditor5/content.css"; +import "@triliumnext/ckeditor5/src/theme/ck-content.css"; import "@triliumnext/share-theme/styles/index.css"; import "@triliumnext/share-theme/scripts/index.js"; diff --git a/apps/client/src/stylesheets/bootstrap.scss b/apps/client/src/stylesheets/bootstrap.scss deleted file mode 100644 index b30b929fe..000000000 --- a/apps/client/src/stylesheets/bootstrap.scss +++ /dev/null @@ -1,2 +0,0 @@ -/* Import all of Bootstrap's CSS */ -@use "bootstrap/scss/bootstrap"; diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index a624fe686..d3f3f4690 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1134,6 +1134,7 @@ 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 { @@ -1242,6 +1243,10 @@ 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; @@ -1463,7 +1468,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { cursor: pointer; border: none; color: var(--launcher-pane-text-color); - background-color: var(--launcher-pane-background-color); + background: transparent; flex-shrink: 0; } @@ -1771,7 +1776,6 @@ 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; } @@ -2366,3 +2370,21 @@ 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; +} \ No newline at end of file diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index 69753b0db..9b1bafaa8 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -13,12 +13,13 @@ --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: #292929; + --dropdown-border-color: #404040; --dropdown-shadow-opacity: 0.6; --dropdown-item-icon-destructive-color: #de6e5b; --disabled-tooltip-icon-color: #7fd2ef; @@ -147,6 +148,7 @@ --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; @@ -155,6 +157,8 @@ --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; diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index cf6e7c6eb..6456f2797 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -13,6 +13,7 @@ --theme-style: light; --native-titlebar-background: #ffffff00; + --window-background-color-bgfx: transparent; /* When background effects enabled */ --main-background-color: white; --main-text-color: black; @@ -115,17 +116,17 @@ --quick-search-focus-border: #00000029; --quick-search-focus-background: #ffffff80; --quick-search-focus-color: #000; - --quick-search-result-content-background: #00000017; + --quick-search-result-content-background: #0000000f; --quick-search-result-highlight-color: #c65050; --left-pane-collapsed-border-color: #0000000d; --left-pane-background-color: #f2f2f2; --left-pane-text-color: #383838; - --left-pane-item-hover-background: #eaeaea; + --left-pane-item-hover-background: rgba(0, 0, 0, 0.032); --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: #d7d7d7; + --left-pane-item-action-button-background: rgba(0, 0, 0, 0.11); --left-pane-item-action-button-color: inherit; --left-pane-item-action-button-hover-background: white; --left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15); @@ -141,6 +142,7 @@ --launcher-pane-vert-button-hover-background: white; --launcher-pane-vert-button-hover-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; @@ -148,6 +150,8 @@ --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; diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index 524bb8140..a75ff689b 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -329,6 +329,8 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after { #toast-container .toast .toast-body { flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; } /* diff --git a/apps/client/src/stylesheets/theme-next/forms.css b/apps/client/src/stylesheets/theme-next/forms.css index f4465f905..86fad2682 100644 --- a/apps/client/src/stylesheets/theme-next/forms.css +++ b/apps/client/src/stylesheets/theme-next/forms.css @@ -5,7 +5,8 @@ button.btn.btn-primary, button.btn.btn-secondary, button.btn.btn-sm:not(.select-button), -button.btn.btn-success { +button.btn.btn-success, +button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text { display: inline-flex; align-items: center; justify-content: center; @@ -21,7 +22,8 @@ button.btn.btn-success { button.btn.btn-primary:hover, button.btn.btn-secondary:hover, button.btn.btn-sm:not(.select-button):hover, -button.btn.btn-success:hover { +button.btn.btn-success:hover, +button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):hover { background: var(--cmd-button-hover-background-color); color: var(--cmd-button-hover-text-color); } @@ -29,7 +31,8 @@ button.btn.btn-success:hover { button.btn.btn-primary:active, button.btn.btn-secondary:active, button.btn.btn-sm:not(.select-button):active, -button.btn.btn-success:active { +button.btn.btn-success:active, +button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):active { opacity: 0.85; box-shadow: unset; background: var(--cmd-button-background-color) !important; @@ -40,14 +43,16 @@ button.btn.btn-success:active { button.btn.btn-primary:disabled, button.btn.btn-secondary:disabled, button.btn.btn-sm:not(.select-button):disabled, -button.btn.btn-success:disabled { +button.btn.btn-success:disabled, +button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text.ck-disabled { opacity: var(--cmd-button-disabled-opacity); } 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.btn.btn-success:focus-visible, +button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):focus-visible { outline: 2px solid var(--input-focus-outline-color); } @@ -149,8 +154,11 @@ 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; @@ -167,8 +175,11 @@ 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); @@ -181,8 +192,11 @@ 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; diff --git a/apps/client/src/stylesheets/theme-next/notes/text.css b/apps/client/src/stylesheets/theme-next/notes/text.css index 404a47fec..6499fa0b4 100644 --- a/apps/client/src/stylesheets/theme-next/notes/text.css +++ b/apps/client/src/stylesheets/theme-next/notes/text.css @@ -4,6 +4,7 @@ :root { --ck-font-face: var(--main-font-family); + --ck-input-label-height: 1.5em; } /* @@ -307,6 +308,11 @@ 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 { @@ -363,19 +369,86 @@ color: var(--accent); } -/* Action buttons */ +/* Text snippet dropdown */ -: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 { + padding: 8px; } -:root .ck-link-actions button.ck-button:hover, -:root .ck-link-form button.ck-button:hover { - background: var(--hover-item-background-color); +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); } /* Mention list (the autocompletion list for emojis, labels and relations) */ @@ -392,6 +465,58 @@ 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 */ diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index a6e31de5d..c4c0b6d4c 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -36,32 +36,23 @@ body.mobile { /* #region Mica */ body.background-effects.platform-win32 { - --launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.15); - --launcher-pane-horiz-background-color: rgba(255, 255, 255, 0.7); - --launcher-pane-vert-background-color: rgba(255, 255, 255, 0.055); - --tab-background-color: transparent; - --new-tab-button-background: transparent; - --active-tab-background-color: var(--launcher-pane-horiz-background-color); --background-material: tabbed; -} - -@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); - } + --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); + --active-tab-background-color: var(--launcher-pane-horiz-background-color); } body.background-effects.platform-win32.layout-vertical { - --left-pane-background-color: transparent; - --left-pane-item-hover-background: rgba(127, 127, 127, 0.05); + --left-pane-background-color: var(--window-background-color-bgfx); --background-material: mica; } body.background-effects.platform-win32, -body.background-effects.platform-win32 #root-widget, -body.background-effects.platform-win32 #launcher-pane .launcher-button { - background: transparent !important; +body.background-effects.platform-win32 #root-widget { + background: var(--window-background-color-bgfx) !important; } body.background-effects.platform-win32.layout-horizontal #horizontal-main-container, @@ -90,7 +81,7 @@ body.background-effects.zen #root-widget { * Gutter */ - .gutter { +.gutter { background: var(--gutter-color) !important; transition: background 150ms ease-out; } @@ -575,31 +566,20 @@ 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; @@ -622,9 +602,8 @@ div.quick-search .search-button.show { font-weight: 600; } -/* Divider line */ -.quick-search .dropdown-item::after { - display: none; +.quick-search div.dropdown-divider { + margin: 8px 0; } /* @@ -899,6 +878,80 @@ 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); } @@ -1114,6 +1167,11 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging . /* will-change: opacity; -- causes some weird artifacts to the note menu in split view */ } +.split-note-container-widget > .gutter { + background: var(--root-background) !important; + transition: background 150ms ease-out; +} + /* * Ribbon & note header */ @@ -1122,10 +1180,6 @@ 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; diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index d76843a27..638501c45 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -263,6 +263,11 @@ "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?", @@ -1118,7 +1123,9 @@ "title": "Performance", "enable-motion": "Enable transitions and animations", "enable-shadows": "Enable shadows", - "enable-backdrop-effects": "Enable background effects for menus, popups and panels" + "enable-backdrop-effects": "Enable background effects for menus, popups and panels", + "enable-smooth-scroll": "Enable smooth scrolling", + "app-restart-required": "(a restart of the application is required for the change to take effect)" }, "ai_llm": { "not_started": "Not started", diff --git a/apps/client/src/translations/pl/translation.json b/apps/client/src/translations/pl/translation.json index 980ba7fa7..2d6e92e20 100644 --- a/apps/client/src/translations/pl/translation.json +++ b/apps/client/src/translations/pl/translation.json @@ -30,13 +30,16 @@ "search_note": "Wyszukaj notatkę po nazwie", "link_title_arbitrary": "Tytuł linku można dowolnie zmieniać", "link_title": "Tytuł linku", - "button_add_link": "Dodaj link" + "button_add_link": "Dodaj link", + "help_on_links": "Pomoc dotycząca linków", + "link_title_mirrors": "tytuł linku odzwierciedla tytuł obecnej notatki" }, "branch_prefix": { "save": "Zapisz", "edit_branch_prefix": "Edytuj prefiks gałęzi", "prefix": "Prefiks: ", - "branch_prefix_saved": "Zapisano prefiks gałęzi." + "branch_prefix_saved": "Zapisano prefiks gałęzi.", + "help_on_tree_prefix": "Pomoc dotycząca prefiksu drzewa" }, "bulk_actions": { "labels": "Etykiety", @@ -98,7 +101,8 @@ "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}}\"" + "note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\"", + "help_on_links": "Pomoc dotycząca linków" }, "help": { "title": "Ściągawka", diff --git a/apps/client/src/translations/ro/translation.json b/apps/client/src/translations/ro/translation.json index cc068831a..69dc904b9 100644 --- a/apps/client/src/translations/ro/translation.json +++ b/apps/client/src/translations/ro/translation.json @@ -2013,7 +2013,9 @@ "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-backdrop-effects": "Activează efectele de fundal pentru meniuri, popup-uri și panouri", + "enable-smooth-scroll": "Activează derularea lină", + "app-restart-required": "(este necesară repornirea aplicației pentru ca modificarea să aibă efect)" }, "settings": { "related_settings": "Setări similare" diff --git a/apps/client/src/translations/uk/translation.json b/apps/client/src/translations/uk/translation.json index 7b64595a4..65c3dcea0 100644 --- a/apps/client/src/translations/uk/translation.json +++ b/apps/client/src/translations/uk/translation.json @@ -844,7 +844,8 @@ "note_type": "Тип нотатки", "editable": "РеГагув.", "basic_properties": "ŠžŃŠ½Š¾Š²Š½Ń– Властивості", - "language": "Мова" + "language": "Мова", + "configure_code_notes": "ŠšŠ¾Š½Ń„Ń–Š³ŃƒŃ€Š°Ń†Ń–Ń нотатки Š· коГом..." }, "book_properties": { "view_type": "Тип ŠæŠµŃ€ŠµŠ³Š»ŃŠ“Ńƒ", @@ -1586,7 +1587,8 @@ "hoist-this-note-workspace": "Закріпити цю Š½Š¾Ń‚Š°Ń‚ŠŗŃƒ (робочий простір)", "refresh-saved-search-results": "ŠžŠ½Š¾Š²ŠøŃ‚Šø збережені Ń€ŠµŠ·ŃƒŠ»ŃŒŃ‚Š°Ń‚Šø пошуку", "create-child-note": "Дтворити Š“Š¾Ń‡Ń–Ń€Š½ŃŽ Š½Š¾Ń‚Š°Ń‚ŠŗŃƒ", - "unhoist": "ВіГкріпити" + "unhoist": "ВіГкріпити", + "toggle-sidebar": "ŠŸŠµŃ€ŠµŠ¼ŠøŠŗŠ°Š½Š½Ń бічної панелі" }, "title_bar_buttons": { "window-on-top": "Тримати вікно Š·Š²ŠµŃ€Ń…Ńƒ" @@ -1909,8 +1911,8 @@ "open-in-popup": "ШвиГке Ń€ŠµŠ“Š°Š³ŃƒŠ²Š°Š½Š½Ń" }, "shared_info": { - "shared_publicly": "Š¦Ń нотатка Š¾ŠæŃƒŠ±Š»Ń–кована на {{- link}}", - "shared_locally": "Š¦ŃŽ Š½Š¾Ń‚Š°Ń‚ŠŗŃƒ Š¾ŠæŃƒŠ±Š»Ń–ŠŗŠ¾Š²Š°Š½Š¾ локально на {{- link}}", + "shared_publicly": "Š¦Ń нотатка Š¾ŠæŃƒŠ±Š»Ń–кована на {{- link}}.", + "shared_locally": "Š¦ŃŽ Š½Š¾Ń‚Š°Ń‚ŠŗŃƒ Š¾ŠæŃƒŠ±Š»Ń–ŠŗŠ¾Š²Š°Š½Š¾ локально на {{- link}}.", "help_link": "Щоб отримати Гопомогу, віГвіГайте вікі." }, "note_types": { @@ -2018,5 +2020,11 @@ }, "units": { "percentage": "%" + }, + "ui-performance": { + "title": "ŠŸŃ€Š¾Š“ŃƒŠŗŃ‚ŠøŠ²Š½Ń–ŃŃ‚ŃŒ", + "enable-motion": "Š£Š²Ń–Š¼ŠŗŠ½ŃƒŃ‚Šø перехоГи та Š°Š½Ń–Š¼Š°Ń†Ń–ŃŽ", + "enable-shadows": "Š£Š²Ń–Š¼ŠŗŠ½ŃƒŃ‚Šø тіні", + "enable-backdrop-effects": "Š£Š²Ń–Š¼ŠŗŠ½ŃƒŃ‚Šø фонові ефекти Š“Š»Ń Š¼ŠµŠ½ŃŽ, ŃŠæŠ»ŠøŠ²Š°ŃŽŃ‡ŠøŃ… вікон та панелей" } } diff --git a/apps/client/src/widgets/containers/split_note_container.ts b/apps/client/src/widgets/containers/split_note_container.ts index 12d417973..8298d5989 100644 --- a/apps/client/src/widgets/containers/split_note_container.ts +++ b/apps/client/src/widgets/containers/split_note_container.ts @@ -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,6 +52,10 @@ export default class SplitNoteContainer extends FlexContainer { 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">) { @@ -95,9 +99,9 @@ export default class SplitNoteContainer extends FlexContainer { } } - closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) { + async closeThisNoteSplitCommand({ ntxId }: CommandListenerData<"closeThisNoteSplit">) { if (ntxId) { - appContext.tabManager.removeNoteContext(ntxId); + await appContext.tabManager.removeNoteContext(ntxId); } } @@ -137,6 +141,8 @@ export default class SplitNoteContainer extends FlexContainer { // activate context that now contains the original note await appContext.tabManager.activateNoteContext(isMovingLeft ? ntxIds[leftIndex + 1] : ntxIds[leftIndex]); + + splitService.moveNoteSplitResizer(ntxIds[leftIndex]); } activeContextChangedEvent() { @@ -157,6 +163,8 @@ export default class SplitNoteContainer extends FlexContainer { recursiveCleanup(widget); delete this.widgets[ntxId]; } + + splitService.delNoteSplitResizer(ntxIds); } contextsReopenedEvent({ ntxId, afterNtxId }: EventData<"contextsReopened">) { diff --git a/apps/client/src/widgets/dialogs/revisions.tsx b/apps/client/src/widgets/dialogs/revisions.tsx index 0fa4f956e..65c7dfd2c 100644 --- a/apps/client/src/widgets/dialogs/revisions.tsx +++ b/apps/client/src/widgets/dialogs/revisions.tsx @@ -7,6 +7,7 @@ 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"; @@ -18,12 +19,15 @@ 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(); + const [ noteContent, setNoteContent ] = useState(); const [ revisions, setRevisions ] = useState(); const [ currentRevision, setCurrentRevision ] = useState(); const [ shown, setShown ] = useState(false); + const [ showDiff, setShowDiff ] = useState(false); const [ refreshCounter, setRefreshCounter ] = useState(0); useTriliumEvent("showRevisions", async ({ noteId }) => { @@ -37,8 +41,10 @@ export default function RevisionsDialog() { useEffect(() => { if (note?.noteId) { server.get(`notes/${note.noteId}/revisions`).then(setRevisions); + note.getContent().then(setNoteContent); } else { setRevisions(undefined); + setNoteContent(undefined); } }, [ note?.noteId, refreshCounter ]); @@ -54,22 +60,42 @@ export default function RevisionsDialog() { helpPageId="vZWERwf8U3nx" bodyStyle={{ display: "flex", height: "80vh" }} header={ - (!!revisions?.length &&