Merge remote-tracking branch 'origin/develop' into style/next/restyle-ckeditor

This commit is contained in:
Elian Doran 2025-02-25 17:00:32 +02:00
commit 29df170590
No known key found for this signature in database
393 changed files with 14261 additions and 3265 deletions

View File

@ -1,3 +1,6 @@
name: "Build Electron App"
description: "Builds and packages the Electron app for different platforms"
inputs:
os:
description: "One of the supported platforms: macos, linux, windows"
@ -6,34 +9,205 @@ inputs:
description: "The architecture to build for: x64, arm64"
required: true
extension:
description: "Platform specific extension to build: dmg, deb, exe"
description: "Platform specific extensions to copy in the output: dmg, deb, rpm, exe, zip"
required: true
runs:
using: composite
steps:
- name: Set up Python for appdmg to be installed
# Certificate setup
- name: Import Apple certificates
if: inputs.os == 'macos'
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ env.APPLE_APP_CERTIFICATE_BASE64 }}
p12-password: ${{ env.APPLE_APP_CERTIFICATE_PASSWORD }}
keychain: build
keychain-password: ${{ github.run_id }}
- name: Install Installer certificate
if: inputs.os == 'macos'
uses: apple-actions/import-codesign-certs@v2
with:
p12-file-base64: ${{ env.APPLE_INSTALLER_CERTIFICATE_BASE64 }}
p12-password: ${{ env.APPLE_INSTALLER_CERTIFICATE_PASSWORD }}
keychain: build
keychain-password: ${{ github.run_id }}
# We don't need to create a keychain here because we're using the build keychain that was created in the previous step
create-keychain: false
- name: Verify certificates
if: inputs.os == 'macos'
shell: bash
run: |
echo "Available signing identities:"
security find-identity -v -p codesigning build.keychain
- name: Set up Python and other macOS dependencies
if: ${{ inputs.os == 'macos' }}
shell: bash
run: brew install python-setuptools
- name: Install rpm on Ubuntu for RPM package building
run: |
brew install python-setuptools
brew install create-dmg
- name: Install dependencies for RPM and Flatpak package building
if: ${{ inputs.os == 'linux' }}
shell: bash
run: sudo apt install rpm
run: |
sudo apt-get update && sudo apt-get install rpm flatpak-builder elfutils
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
FLATPAK_ARCH=$(if [[ ${{ inputs.arch }} = 'arm64' ]]; then echo 'aarch64'; else echo 'x86_64'; fi)
FLATPAK_VERSION='24.08'
flatpak install --user --no-deps --arch $FLATPAK_ARCH --assumeyes runtime/org.freedesktop.Platform/$FLATPAK_ARCH/$FLATPAK_VERSION runtime/org.freedesktop.Sdk/$FLATPAK_ARCH/$FLATPAK_VERSION org.electronjs.Electron2.BaseApp/$FLATPAK_ARCH/$FLATPAK_VERSION
# Build setup
- name: Install dependencies
shell: bash
run: npm ci
- name: Update build info
shell: bash
run: npm run update-build-info
- name: Run electron-forge
run: npm run chore:update-build-info
# Critical debugging configuration
- name: Run electron-forge build with enhanced logging
shell: bash
run: npm run make-electron -- --arch=${{ inputs.arch }}
env:
# Pass through required environment variables for signing and notarization
APPLE_TEAM_ID: ${{ env.APPLE_TEAM_ID }}
APPLE_ID: ${{ env.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }}
run: |
# Map OS names to Electron Forge platform names
if [ "${{ inputs.os }}" = "macos" ]; then
PLATFORM="darwin"
elif [ "${{ inputs.os }}" = "windows" ]; then
PLATFORM="win32"
else
PLATFORM="${{ inputs.os }}"
fi
npm run electron-forge:make -- \
--arch=${{ inputs.arch }} \
--platform=$PLATFORM
# Add DMG signing step
- name: Sign DMG
if: inputs.os == 'macos'
shell: bash
run: |
echo "Signing DMG file..."
dmg_file=$(find out -name "*.dmg" -print -quit)
if [ -n "$dmg_file" ]; then
echo "Found DMG: $dmg_file"
# Get the first valid signing identity from the keychain
SIGNING_IDENTITY=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
if [ -z "$SIGNING_IDENTITY" ]; then
echo "Error: No valid Developer ID Application certificate found in keychain"
exit 1
fi
echo "Using signing identity: $SIGNING_IDENTITY"
# Sign the DMG
codesign --force --sign "$SIGNING_IDENTITY" --options runtime --timestamp "$dmg_file"
# Notarize the DMG
xcrun notarytool submit "$dmg_file" --apple-id "$APPLE_ID" --password "$APPLE_ID_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait
# Staple the notarization ticket
xcrun stapler staple "$dmg_file"
else
echo "No DMG found to sign"
fi
- name: Verify code signing
if: inputs.os == 'macos'
shell: bash
run: |
echo "Verifying code signing for all artifacts..."
# First check the .app bundle
echo "Looking for .app bundle..."
app_bundle=$(find out -name "*.app" -print -quit)
if [ -n "$app_bundle" ]; then
echo "Found app bundle: $app_bundle"
echo "Verifying app bundle signing..."
codesign --verify --deep --strict --verbose=2 "$app_bundle"
echo "Displaying app bundle signing info..."
codesign --display --verbose=2 "$app_bundle"
echo "Checking entitlements..."
codesign --display --entitlements :- "$app_bundle"
echo "Checking notarization status..."
xcrun stapler validate "$app_bundle" || echo "Warning: App bundle not notarized yet"
else
echo "No .app bundle found to verify"
fi
# Then check DMG if it exists
echo "Looking for DMG..."
dmg_file=$(find out -name "*.dmg" -print -quit)
if [ -n "$dmg_file" ]; then
echo "Found DMG: $dmg_file"
echo "Verifying DMG signing..."
codesign --verify --deep --strict --verbose=2 "$dmg_file"
echo "Displaying DMG signing info..."
codesign --display --verbose=2 "$dmg_file"
echo "Checking DMG notarization..."
xcrun stapler validate "$dmg_file" || echo "Warning: DMG not notarized yet"
else
echo "No DMG found to verify"
fi
# Finally check ZIP if it exists
echo "Looking for ZIP..."
zip_file=$(find out -name "*.zip" -print -quit)
if [ -n "$zip_file" ]; then
echo "Found ZIP: $zip_file"
echo "Note: ZIP files are not code signed, but their contents should be"
fi
- name: Prepare artifacts
shell: bash
run: |
mkdir -p upload;
for ext in ${{ join(inputs.extension, ' ') }};
do
file=$(find out/make -name "*.$ext" -print -quit);
cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}.$ext";
done
mkdir -p upload
if [ "${{ inputs.os }}" = "macos" ]; then
# For macOS, we need to look in specific directories based on the maker
echo "Collecting macOS artifacts..."
# Look for DMG files recursively
echo "Looking for DMG files..."
dmg_file=$(find out -name "*.dmg" -print -quit)
if [ -n "$dmg_file" ]; then
echo "Found DMG: $dmg_file"
cp "$dmg_file" "upload/TriliumNextNotes-${{ github.ref_name }}-darwin-${{ inputs.arch }}.dmg"
else
echo "Warning: No DMG file found"
fi
# Look for ZIP files recursively
echo "Looking for ZIP files..."
zip_file=$(find out -name "*.zip" -print -quit)
if [ -n "$zip_file" ]; then
echo "Found ZIP: $zip_file"
cp "$zip_file" "upload/TriliumNextNotes-${{ github.ref_name }}-darwin-${{ inputs.arch }}.zip"
else
echo "Warning: No ZIP file found"
fi
else
# For other platforms, use the existing logic but with better error handling
echo "Collecting artifacts for ${{ inputs.os }}..."
for ext in ${{ inputs.extension }}; do
echo "Looking for .$ext files..."
file=$(find out -name "*.$ext" -print -quit)
if [ -n "$file" ]; then
echo "Found $file for extension $ext"
cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}.$ext"
else
echo "Warning: No file found with extension .$ext"
fi
done
fi
echo "Final contents of upload directory:"
ls -la upload/

View File

@ -1,4 +1,7 @@
inputs:
os:
description: "One of the supported platforms: windows"
required: true
arch:
description: "The architecture to build for: x64, arm64"
required: true
@ -18,11 +21,11 @@ runs:
MATRIX_ARCH: ${{ inputs.arch }}
shell: bash
run: |
npm run update-build-info
npm run chore:update-build-info
./bin/build-server.sh
- name: Prepare artifacts
shell: bash
run: |
mkdir -p upload
file=$(find dist -name '*.tar.xz' -print -quit)
cp "$file" "upload/TriliumNextNotes-linux-${{ inputs.arch }}-${{ github.ref_name }}.tar.xz"
cp "$file" "upload/TriliumNextNotes-Server-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}.tar.xz"

View File

@ -33,7 +33,7 @@ jobs:
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
@ -47,16 +47,16 @@ jobs:
with:
node-version: 20
cache: "npm"
- name: Install npm dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
@ -69,12 +69,12 @@ jobs:
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Validate container run output
run: |
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }})
echo "Container ID: $CONTAINER_ID"
- name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v3
with:
@ -82,7 +82,7 @@ jobs:
wait-time: 50
require-status: running
require-healthy: true
- name: Run Playwright tests
run: TRILIUM_DOCKER=1 npx playwright test
- uses: actions/upload-artifact@v4
@ -100,7 +100,20 @@ jobs:
build:
name: Build Docker images
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
image: ubuntu-latest
- dockerfile: Dockerfile
platform: linux/arm64
image: ubuntu-24.04-arm
- dockerfile: Dockerfile
platform: linux/arm/v7
image: ubuntu-24.04-arm
runs-on: ${{ matrix.image }}
needs:
- test_docker
permissions:
@ -108,16 +121,6 @@ jobs:
packages: write
attestations: write
id-token: write
strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
- dockerfile: Dockerfile
platform: linux/arm64
- dockerfile: Dockerfile
platform: linux/arm/v7
steps:
- name: Prepare
run: |
@ -144,13 +147,13 @@ jobs:
type=sha
flavor: |
latest=false
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
@ -169,14 +172,14 @@ jobs:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
@ -186,13 +189,13 @@ jobs:
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
@ -220,7 +223,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
@ -237,14 +240,14 @@ jobs:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
@ -255,7 +258,7 @@ jobs:
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
@ -267,25 +270,25 @@ jobs:
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Small delay to ensure stable tag is fully propagated
sleep 5
# Now update latest tags
docker buildx imagetools create \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
fi
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

View File

@ -23,16 +23,46 @@ jobs:
os:
- name: macos
image: macos-latest
extension: dmg
extension: [dmg, zip]
- name: linux
image: ubuntu-latest
extension: deb
extension: [deb, rpm, zip, flatpak]
- name: windows
image: windows-latest
extension: exe
extension: [exe, zip]
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v4
# Set up certificates and keychain for macOS
- name: Install Apple Certificates
if: matrix.os.name == 'macos'
env:
APP_CERTIFICATE_BASE64: ${{ secrets.APPLE_APP_CERTIFICATE_BASE64 }}
APP_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APP_CERTIFICATE_PASSWORD }}
INSTALLER_CERTIFICATE_BASE64: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_BASE64 }}
INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ github.run_id }}
run: |
# Create keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain
# Import application certificate
echo "$APP_CERTIFICATE_BASE64" | base64 --decode > application.p12
security import application.p12 -k build.keychain -P "$APP_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
rm application.p12
# Import installer certificate
echo "$INSTALLER_CERTIFICATE_BASE64" | base64 --decode > installer.p12
security import installer.p12 -k build.keychain -P "$INSTALLER_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
rm installer.p12
# Update keychain settings
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
@ -43,6 +73,17 @@ jobs:
os: ${{ matrix.os.name }}
arch: ${{ matrix.arch }}
extension: ${{ matrix.os.extension }}
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
# Clean up keychain after build
- name: Clean up keychain
if: matrix.os.name == 'macos' && always()
run: |
security delete-keychain build.keychain
- name: Publish artifacts
uses: actions/upload-artifact@v4
with:
@ -53,6 +94,7 @@ jobs:
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}.${{matrix.os.extension}}
path: upload/*.${{ matrix.os.extension }}
build_linux_server:
name: Build Linux Server
strategy:

View File

@ -20,13 +20,13 @@ jobs:
os:
- name: macos
image: macos-latest
extension: dmg
extension: [dmg, zip]
- name: linux
image: ubuntu-latest
extension: deb
extension: [deb, rpm, zip, flatpak]
- name: windows
image: windows-latest
extension: exe
extension: [exe, zip]
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v4
@ -38,32 +38,25 @@ jobs:
shell: bash
run: npm ci
- name: Update nightly version
run: npm run ci-update-nightly-version
run: npm run chore:ci-update-nightly-version
- name: Run the build
uses: ./.github/actions/build-electron
with:
os: ${{ matrix.os.name }}
arch: ${{ matrix.arch }}
extension: ${{ matrix.os.extension }}
- run: find output
extension: ${{ join(matrix.os.extension, ' ') }}
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.2.0
- name: Publish release
uses: softprops/action-gh-release@v2
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.zip # path to archive to upload
asset_name: TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
make_latest: false
prerelease: true
draft: false
fail_on_unmatched_files: true
files: upload/*.*
tag_name: nightly
name: Nightly Build
- name: Deploy installer release
uses: WebFreak001/deploy-nightly@v3.2.0
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }} # path to archive to upload
asset_name: TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.${{ matrix.os.extension }} # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
nightly-server:
name: Deploy server nightly
strategy:
@ -82,13 +75,16 @@ jobs:
- name: Run the build
uses: ./.github/actions/build-server
with:
os: linux
arch: ${{ matrix.arch }}
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.2.0
- name: Publish release
uses: softprops/action-gh-release@v2
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-linux-${{ matrix.arch }}-${{ github.ref_name }}.tar.xz # path to archive to upload
asset_name: TriliumNextNotes-linux-${{ matrix.arch }}-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
make_latest: false
prerelease: true
draft: false
fail_on_unmatched_files: true
files: upload/*.*
tag_name: nightly
name: Nightly Build

View File

@ -23,10 +23,10 @@ jobs:
extension: [dmg, zip]
- name: linux
image: ubuntu-latest
extension: [deb, rpm, zip]
extension: [deb, rpm, zip, flatpak]
- name: windows
image: windows-latest
extension: exe
extension: [exe, zip]
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v4
@ -39,13 +39,23 @@ jobs:
with:
os: ${{ matrix.os.name }}
arch: ${{ matrix.arch }}
extension: ${{ matrix.os.extension }}
extension: ${{ join(matrix.os.extension, ' ') }}
env:
APPLE_APP_CERTIFICATE_BASE64: ${{ secrets.APPLE_APP_CERTIFICATE_BASE64 }}
APPLE_APP_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_APP_CERTIFICATE_PASSWORD }}
APPLE_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_BASE64 }}
APPLE_INSTALLER_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_INSTALLER_CERTIFICATE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Publish release
uses: softprops/action-gh-release@v2
with:
draft: true
fail_on_unmatched_files: true
files: upload/*.*
build_linux_server-x64:
name: Build Linux Server
strategy:
@ -64,6 +74,7 @@ jobs:
- name: Run the build
uses: ./.github/actions/build-server
with:
os: linux
arch: ${{ matrix.arch }}
- name: Publish release

2
.gitignore vendored
View File

@ -1,3 +1,4 @@
.cache
.DS_Store
node_modules/
dist/
@ -7,6 +8,7 @@ src/public/app-dist/
npm-debug.log
yarn-error.log
po-*/
.flatpak-builder/
*.db
!integration-tests/db/document.db

View File

@ -4,7 +4,7 @@ image:
tasks:
- before: nvm install 20.15.1 && nvm use 20.15.1
init: npm install
command: npm run start-server
command: npm run server:start
ports:
- port: 8080

4
.vscode/launch.json vendored
View File

@ -5,8 +5,8 @@
{
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"name": "nodemon start-server",
"program": "${workspaceFolder}/src/www",
"name": "nodemon server:start",
"program": "${workspaceFolder}/src/main",
"request": "launch",
"restart": true,
"runtimeExecutable": "nodemon",

View File

@ -18,5 +18,6 @@
"github-actions.workflows.pinned.workflows": [".github/workflows/nightly.yml"],
"[css]": {
"editor.defaultFormatter": "vscode.css-language-features"
}
},
"npm.exclude": ["**/build", "**/dist", "**/out/**"]
}

View File

@ -1,5 +1,5 @@
# Build stage
FROM node:22.13.1-bullseye-slim AS builder
FROM node:22.14.0-bullseye-slim AS builder
# Configure build dependencies in a single layer
RUN apt-get update && apt-get install -y --no-install-recommends \
@ -25,7 +25,7 @@ RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm docker_healthcheck.ts && \
npm install && \
npm run webpack && \
npm run build:webpack && \
npm prune --omit=dev && \
npm cache clean --force && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \
@ -36,7 +36,7 @@ RUN cp -R build/src/* src/. && \
rm -r build
# Runtime stage
FROM node:22.13.1-bullseye-slim
FROM node:22.14.0-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \

View File

@ -1,5 +1,5 @@
# Build stage
FROM node:22.13.1-alpine AS builder
FROM node:22.14.0-alpine AS builder
# Configure build dependencies
RUN apk add --no-cache --virtual .build-dependencies \
@ -24,7 +24,7 @@ RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm docker_healthcheck.ts && \
npm install && \
npm run webpack && \
npm run build:webpack && \
npm prune --omit=dev && \
npm cache clean --force && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \
@ -35,7 +35,7 @@ RUN cp -R build/src/* src/. && \
rm -r build
# Runtime stage
FROM node:22.13.1-alpine
FROM node:22.14.0-alpine
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow

View File

@ -78,7 +78,7 @@ Trilium 也提供 Flatpak
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 致谢

View File

@ -86,7 +86,7 @@ Clone localmente y ejecute
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 Reconocimientos

View File

@ -73,7 +73,7 @@ Clona localmente ed esegui
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 Riconoscimenti

View File

@ -54,7 +54,7 @@ Trilium は Flatpak としても提供されます:
```shell
npm install
npm run start-server
npm run server:start
```
## 📢 シャウトアウト

View File

@ -102,7 +102,7 @@ You can also read [Patterns of personal knowledge base](https://triliumnext.gith
git clone https://github.com/TriliumNext/Notes.git
cd Notes
npm install
npm run start-server
npm run server:start
```
### Documentation
@ -118,8 +118,10 @@ Head on over to our [Docs repo](https://github.com/TriliumNext/Docs)
## 🤝 Support
You can support the original Trilium developer using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
Support for the TriliumNext organization will be possible in the near future.
Support for the TriliumNext organization will be possible in the near future. For now, you can:
- Support continued development on TriliumNext by supporting our developers: [eliandoran](https://github.com/sponsors/eliandoran) (See the [repository insights]([developers]([url](https://github.com/TriliumNext/Notes/graphs/contributors))) for a full list)
- Show a token of gratitude to the original Trilium developer ([zadam](https://github.com/sponsors/zadam)) via [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
## 🔑 License

View File

@ -44,7 +44,7 @@ Trilium предоставляется в виде десктопного при
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 Благодарности

View File

@ -7,9 +7,9 @@ const DEST_DIR_NODE_MODULES = path.join(DEST_DIR, "node_modules");
const VERBOSE = process.env.VERBOSE;
function log(...args) {
function log(...args: any[]) {
if (VERBOSE) {
console.log(args);
console.log(...args);
}
}
@ -29,7 +29,12 @@ const copy = async () => {
fs.copySync(path.join("build", srcFile), destFile, { recursive: true });
}
const filesToCopy = ["config-sample.ini", "tsconfig.webpack.json"];
const filesToCopy = [
"config-sample.ini",
"tsconfig.webpack.json",
"./src/etapi/etapi.openapi.yaml",
"./src/routes/api/openapi.json"
];
for (const file of filesToCopy) {
log(`Copying ${file}`);
await fs.copy(file, path.join(DEST_DIR, file));
@ -90,7 +95,6 @@ const copy = async () => {
"node_modules/mark.js/dist/",
"node_modules/normalize.css/",
"node_modules/jquery.fancytree/dist/",
"node_modules/bootstrap/dist/",
"node_modules/autocomplete.js/dist/",
"node_modules/codemirror/lib/",
"node_modules/codemirror/addon/",

View File

@ -23,7 +23,7 @@ rm -rf "$DIR"
mkdir -pv "$DIR"
echo Webpack start
npm run webpack
npm run build:webpack
echo Webpack finish
echo "Copying Trilium to build directory $DIR"

189
bin/generate-openapi.ts Normal file
View File

@ -0,0 +1,189 @@
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import swaggerJsdoc from 'swagger-jsdoc';
import fs from "fs";
/*
* Usage: npm run generate-openapi | tail -n1 > x.json
*
* Inspect generated file by opening it in https://editor-next.swagger.io/
*
*/
const options = {
definition: {
openapi: '3.1.1',
info: {
title: 'Trilium Notes - Sync server API',
version: '0.96.6',
description: "This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).",
contact: {
name: "TriliumNext issue tracker",
url: "https://github.com/TriliumNext/Notes/issues",
},
license: {
name: "GNU Free Documentation License 1.3 (or later)",
url: "https://www.gnu.org/licenses/fdl-1.3",
},
},
},
apis: [
// Put individual files here to have them ordered first.
'./src/routes/api/setup.ts',
// all other files
'./src/routes/api/*.ts', './bin/generate-openapi.js'
],
};
const openapiSpecification = swaggerJsdoc(options);
const scriptDir = dirname(fileURLToPath(import.meta.url));
const outputPath = join(scriptDir, "..", "src", "routes", "api", "openapi.json");
fs.writeFileSync(outputPath, JSON.stringify(openapiSpecification));
console.log("Saved to ", outputPath);
/**
* @swagger
* tags:
* - name: auth
* description: Authentication
* - name: sync
* description: Synchronization
* - name: data
*/
/**
* @swagger
* components:
* schemas:
* Attribute:
* type: object
* properties:
* attributeId:
* type: string
* example: "4G1DPrI58PAb"
* noteId:
* $ref: "#/components/schemas/NoteId"
* type:
* type: string
* enum: ["attribute", "relation"]
* name:
* type: string
* example: "internalLink"
* value:
* type: string
* example: "hA8aHSpTRdZ6"
* description: "If type = \"relation\", a note ID. Otherwise, the attribute content."
* position:
* type: integer
* example: 20
* isInheritable:
* type: boolean
* Blob:
* type: object
* properties:
* blobId:
* type: string
* example: "8iqMIB8eiY1tPYmElfjm"
* content:
* type:
* - string
* - 'null'
* description: "`null` if not text."
* contentLength:
* type: integer
* dateModified:
* $ref: "#/components/schemas/DateTime"
* utcDateModified:
* $ref: "#/components/schemas/UtcDateTime"
* Branch:
* type: object
* properties:
* branchId:
* $ref: "#/components/schemas/BranchId"
* noteId:
* $ref: "#/components/schemas/NoteId"
* parentNoteId:
* $ref: "#/components/schemas/NoteId"
* notePosition:
* type: integer
* example: 20
* prefix:
* type:
* - string
* - 'null'
* isExpanded:
* type: boolean
* BranchId:
* type: string
* example: "WUjhaGp4EKah_ur11rSfHkzeV"
* description: Equal to `{parentNoteId}_{noteId}`
* DateTime:
* type: string
* example: "2025-02-14 08:19:59.203+0100"
* EntityChange:
* type: object
* properties:
* entityChange:
* type: object
* properties:
* entityName:
* type: string
* example: "notes"
* description: Database table for this entity.
* changeId:
* type: string
* example: "changeId9630"
* description: ID, referenced in `entity_changes` table.
* entity:
* type: object
* description: Encoded entity data. Object has one property for each database column.
* Note:
* type: object
* properties:
* noteId:
* $ref: "#/components/schemas/NoteId"
* title:
* type: string
* isProtected:
* type: boolean
* type:
* type: string
* example: "text"
* enum: ["text", "code", "render", "file", "image", "search", "relationMap", "book", "noteMap", "mermaid", "canvas", "webView", "launcher", "doc", "contentWidget", "mindMap", "geoMap"]
* description: "[Reference list](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/services/note_types.ts)"
* mime:
* type: string
* example: "text/html"
* blobId:
* type: string
* example: "z4PhNX7vuL3xVChQ1m2A"
* NoteId:
* type: string
* example: "ur11rSfHkzeV"
* description: "12-character note ID. Special values: \"none\"`, `\"root\"."
* Timestamps:
* type: object
* properties:
* dateCreated:
* $ref: "#/components/schemas/DateTime"
* dateModified:
* $ref: "#/components/schemas/DateTime"
* utcDateCreated:
* $ref: "#/components/schemas/UtcDateTime"
* utcDateModified:
* $ref: "#/components/schemas/UtcDateTime"
* UtcDateTime:
* type: string
* example: "2025-02-13T07:42:47.698Z"
* description: "Result of `new Date().toISOString().replace('T', ' ')`"
* securitySchemes:
* user-password:
* type: apiKey
* name: trilium-cred
* in: header
* description: "Username and password, formatted as `user:password`"
* session:
* type: apiKey
* in: cookie
* name: trilium.sid
*/

View File

@ -1,7 +1,5 @@
#!/usr/bin/env bash
export GITHUB_REPO=trilium
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
@ -32,7 +30,7 @@ mv package.json.tmp package.json
git add package.json
npm run update-build-info
npm run chore:update-build-info
git add src/services/build.ts
@ -40,10 +38,18 @@ TAG=v$VERSION
echo "Committing package.json version change"
git commit -m "release $VERSION"
git commit -m "chore(release): $VERSION"
git push
echo "Tagging commit with $TAG"
git tag $TAG
git push origin $TAG
echo "Updating master"
git fetch
git checkout master
git reset --hard origin/master
git merge origin/develop
git push

View File

@ -28,6 +28,21 @@ keyPath=
# expressjs shortcuts are supported: loopback(127.0.0.1/8, ::1/128), linklocal(169.254.0.0/16, fe80::/10), uniquelocal(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7)
trustedReverseProxy=false
[Session]
# Use this setting to set a custom value for the "Path" Attribute value of the session cookie.
# This can be useful, when you have several instances running on the same domain, under different paths (e.g. by using a reverse proxy).
# It prevents your instances from overwriting each others' cookies, allowing you to stay logged in multiple instances simultanteously.
# E.g. if you have instances running under https://your-domain.com/triliumNext/instanceA and https://your-domain.com/triliumNext/instanceB
# you would want to set the cookiePath value to "/triliumNext/instanceA" for your first and "/triliumNext/instanceB" for your second instance
cookiePath=/
# Use this setting to set a custom value for the "Max-Age" Attribute of the session cookie.
# This controls how long your session will be valid, before it expires and you need to log in again, when you use the "Remember Me" option.
# Value needs to be entered in Seconds.
# Default value is 1814400 Seconds, which is 21 Days.
cookieMaxAge=1814400
[Sync]
#syncServerHost=
#syncServerTimeout=

Binary file not shown.

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS "tasks"
(
"taskId" TEXT NOT NULL PRIMARY KEY,
"parentNoteId" TEXT NOT NULL,
"title" TEXT NOT NULL DEFAULT "",
"dueDate" INTEGER,
"isDone" INTEGER NOT NULL DEFAULT 0,
"isDeleted" INTEGER NOT NULL DEFAULT 0,
"utcDateModified" TEXT NOT NULL
);

View File

@ -132,3 +132,14 @@ CREATE INDEX IDX_attachments_ownerId_role
CREATE INDEX IDX_notes_blobId on notes (blobId);
CREATE INDEX IDX_revisions_blobId on revisions (blobId);
CREATE INDEX IDX_attachments_blobId on attachments (blobId);
CREATE TABLE IF NOT EXISTS "tasks"
(
"taskId" TEXT NOT NULL PRIMARY KEY,
"parentNoteId" TEXT NOT NULL,
"title" TEXT NOT NULL DEFAULT "",
"dueDate" INTEGER,
"isDone" INTEGER NOT NULL DEFAULT 0,
"isDeleted" INTEGER NOT NULL DEFAULT 0,
"utcDateModified" TEXT NOT NULL
);

View File

@ -38,12 +38,12 @@
<div id="content" class="type-text ck-content">
<h3>The native node bindings</h3><p><code>better-sqlite3</code> has native Node bindings. With updates of <code>better-sqlite3</code>, but also of Electron and Node.js versions, these bindings need to be updated.</p><p>Note that Electron and Node.js versions need different versions of these bindings, since Electron usually packs a different version of Node.js.</p><p>During development, <code>npm install</code> tries to build or reuse prebuilt natives for the current Node.js version. This makes <code>npm run start-server</code> work out of the box. Trying to run <code>npm run start-electron</code> with these versions generally causes an error such as this:</p><pre><code class="language-text-plain">Uncaught Exception:
<h3>The native node bindings</h3><p><code>better-sqlite3</code> has native Node bindings. With updates of <code>better-sqlite3</code>, but also of Electron and Node.js versions, these bindings need to be updated.</p><p>Note that Electron and Node.js versions need different versions of these bindings, since Electron usually packs a different version of Node.js.</p><p>During development, <code>npm install</code> tries to build or reuse prebuilt natives for the current Node.js version. This makes <code>npm run server:start</code> work out of the box. Trying to run <code>npm run electron:start</code> with these versions generally causes an error such as this:</p><pre><code class="language-text-plain">Uncaught Exception:
Error: The module '/Users/elian/Projects/Notes/node_modules/better-sqlite3/build/Release/better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 108. This version of Node.js requires
NODE_MODULE_VERSION 116. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).</code></pre><h3>How the natives are handled</h3><p>Locally, this can be fixed by rebuilding the binaries, which is what <code>npm run switch-electron</code> does, which uses <code>electron-rebuild</code> under the hood.</p><p>When the deliveries are built (see&nbsp;<a class="reference-link type-text" href="UTB518X6X9Uh.html">Build deliveries locally</a>), it is not feasible to rebuild the dependencies since we are building for multiple platforms. Luckily, <code>better-sqlite3</code> provides these prebuilt binaries from us, available as artifacts on <a href="https://github.com/WiseLibs/better-sqlite3/releases/">their GitHub releases page</a>.&nbsp;</p><p>The build script manages the natives for <code>better-sqlite3</code> by keeping a copy of the <code>.node</code> file for every platform in <code>bin/better-sqlite3</code>.</p><p>Whenever the version of <code>better-sqlite3</code> changes, the <code>.node</code> files must also be renewed based on their releases page. To simplify this process, a script was created in <code>bin/better-sqlite3/update.sh</code>.</p><h2>How to update the natives</h2><p>The update script needs to know the version of Electron or Node.js for which to download the prebuilt binaries.</p><p>If you get errors during download, check on the <a href="https://github.com/WiseLibs/better-sqlite3/releases/">releases page</a> to ensure that this particular combination of Electron/Node actually exists for the given release.</p><p>To determine the <code>NODE_MODULE_VERSION</code> that is required, look for <code>This version of Node.js requires</code><br><code>NODE_MODULE_VERSION</code> in the error when starting Trilium via:</p><ul><li><code>npm run start-electron</code> (or run any Electron <a href="UTB518X6X9Uh.html" class="type-text">delivery</a>), case in which the <span style="color:#c0bfbc;"><code>ELECTRON_VERSION</code> variable needs to be changed.</span></li><li><span style="color:#c0bfbc;"><code>npm run start-server</code></span> (or run the Linux server delivery), case in which the <code>NODE_VERSION</code> variable needs to be changed.</li></ul><p>Check which files got changed after running the update script and for each platform that got changed, test it locally via&nbsp;<a class="reference-link type-text" href="UTB518X6X9Uh.html">Build deliveries locally</a>&nbsp;or via the CI.</p>
the module (for instance, using `npm rebuild` or `npm install`).</code></pre><h3>How the natives are handled</h3><p>Locally, this can be fixed by rebuilding the binaries, which is what <code>npm run electron:switch</code> does, which uses <code>electron-rebuild</code> under the hood.</p><p>When the deliveries are built (see&nbsp;<a class="reference-link type-text" href="UTB518X6X9Uh.html">Build deliveries locally</a>), it is not feasible to rebuild the dependencies since we are building for multiple platforms. Luckily, <code>better-sqlite3</code> provides these prebuilt binaries from us, available as artifacts on <a href="https://github.com/WiseLibs/better-sqlite3/releases/">their GitHub releases page</a>.&nbsp;</p><p>The build script manages the natives for <code>better-sqlite3</code> by keeping a copy of the <code>.node</code> file for every platform in <code>bin/better-sqlite3</code>.</p><p>Whenever the version of <code>better-sqlite3</code> changes, the <code>.node</code> files must also be renewed based on their releases page. To simplify this process, a script was created in <code>bin/better-sqlite3/update.sh</code>.</p><h2>How to update the natives</h2><p>The update script needs to know the version of Electron or Node.js for which to download the prebuilt binaries.</p><p>If you get errors during download, check on the <a href="https://github.com/WiseLibs/better-sqlite3/releases/">releases page</a> to ensure that this particular combination of Electron/Node actually exists for the given release.</p><p>To determine the <code>NODE_MODULE_VERSION</code> that is required, look for <code>This version of Node.js requires</code><br><code>NODE_MODULE_VERSION</code> in the error when starting Trilium via:</p><ul><li><code>npm run electron:start</code> (or run any Electron <a href="UTB518X6X9Uh.html" class="type-text">delivery</a>), case in which the <span style="color:#c0bfbc;"><code>ELECTRON_VERSION</code> variable needs to be changed.</span></li><li><span style="color:#c0bfbc;"><code>npm run server:start</code></span> (or run the Linux server delivery), case in which the <code>NODE_VERSION</code> variable needs to be changed.</li></ul><p>Check which files got changed after running the update script and for each platform that got changed, test it locally via&nbsp;<a class="reference-link type-text" href="UTB518X6X9Uh.html">Build deliveries locally</a>&nbsp;or via the CI.</p>
</div>

View File

@ -38,7 +38,7 @@
<div id="content" class="type-text ck-content">
<h2>Server live reload</h2><p>If running the server using <code>npm run start-server</code>, the server will watch for changes in <code>src/public</code> and trigger a frontend reload if that occurs.</p><h2>Electron live reload</h2><p>Similarly, <code>npm run start-electron</code> supports live refresh &nbsp;as well.</p><p>However, a core difference is that Electron watches <code>dist/src/public</code> instead of <code>src/public</code> since Electron runs on its own copy of the files.</p><p>To ameliorate that, a separate watch script has been implemented which automatically copies files from <code>src/public</code> to <code>dist/src/public</code> whenever a change is detected. To run it:</p><pre><code class="language-text-plain">npm run </code></pre><h2>Technical details</h2><ul><li>This mechanism is managed at server level by watching for changes in<code>services/ws.ts</code>.</li></ul>
<h2>Server live reload</h2><p>If running the server using <code>npm run server:start</code>, the server will watch for changes in <code>src/public</code> and trigger a frontend reload if that occurs.</p><h2>Electron live reload</h2><p>Similarly, <code>npm run electron:start</code> supports live refresh &nbsp;as well.</p><p>However, a core difference is that Electron watches <code>dist/src/public</code> instead of <code>src/public</code> since Electron runs on its own copy of the files.</p><p>To ameliorate that, a separate watch script has been implemented which automatically copies files from <code>src/public</code> to <code>dist/src/public</code> whenever a change is detected. To run it:</p><pre><code class="language-text-plain">npm run </code></pre><h2>Technical details</h2><ul><li>This mechanism is managed at server level by watching for changes in<code>services/ws.ts</code>.</li></ul>
</div>

View File

@ -71,7 +71,7 @@
<a id="server" class="tsd-anchor"></a><h3 class="tsd-anchor-link">Server<a href="#server" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><p>To install TriliumNext on your own server (including via Docker from <a href="https://hub.docker.com/r/triliumnext/notes" target="_blank" class="external">Dockerhub</a>) follow <a href="https://triliumnext.github.io/Docs/Wiki/server-installation" target="_blank" class="external">the server installation docs</a>.</p>
<a id="📝-documentation" class="tsd-anchor"></a><h2 class="tsd-anchor-link">📝 Documentation<a href="#📝-documentation" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><p><a href="https://triliumnext.github.io/Docs" target="_blank" class="external">See wiki for complete list of documentation pages.</a></p>
<p>You can also read <a href="https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge" target="_blank" class="external">Patterns of personal knowledge base</a> to get some inspiration on how you might use TriliumNext.</p>
<a id="💻-contribute" class="tsd-anchor"></a><h2 class="tsd-anchor-link">💻 Contribute<a href="#💻-contribute" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><a id="code" class="tsd-anchor"></a><h3 class="tsd-anchor-link">Code<a href="#code" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="shell"><span class="hl-0">git</span><span class="hl-1"> </span><span class="hl-3">clone</span><span class="hl-1"> </span><span class="hl-3">https://github.com/TriliumNext/Notes.git</span><br/><span class="hl-0">cd</span><span class="hl-1"> </span><span class="hl-3">Notes</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-3">install</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-3">run</span><span class="hl-1"> </span><span class="hl-3">start-server</span>
<a id="💻-contribute" class="tsd-anchor"></a><h2 class="tsd-anchor-link">💻 Contribute<a href="#💻-contribute" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2><a id="code" class="tsd-anchor"></a><h3 class="tsd-anchor-link">Code<a href="#code" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><pre><code class="shell"><span class="hl-0">git</span><span class="hl-1"> </span><span class="hl-3">clone</span><span class="hl-1"> </span><span class="hl-3">https://github.com/TriliumNext/Notes.git</span><br/><span class="hl-0">cd</span><span class="hl-1"> </span><span class="hl-3">Notes</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-3">install</span><br/><span class="hl-0">npm</span><span class="hl-1"> </span><span class="hl-3">run</span><span class="hl-1"> </span><span class="hl-3">server:start</span>
</code><button type="button">Copy</button></pre>
<a id="documentation" class="tsd-anchor"></a><h3 class="tsd-anchor-link">Documentation<a href="#documentation" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h3><p>Head on over to our <a href="https://github.com/TriliumNext/Docs" target="_blank" class="external">Docs repo</a></p>

View File

@ -78,7 +78,7 @@ Trilium 也提供 Flatpak
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 致谢

View File

@ -86,7 +86,7 @@ Clone localmente y ejecute
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 Reconocimientos

View File

@ -73,7 +73,7 @@ Clona localmente ed esegui
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 Riconoscimenti

View File

@ -54,7 +54,7 @@ Trilium は Flatpak としても提供されます:
```shell
npm install
npm run start-server
npm run server:start
```
## 📢 シャウトアウト

View File

@ -102,7 +102,7 @@ You can also read [Patterns of personal knowledge base](https://triliumnext.gith
git clone https://github.com/TriliumNext/Notes.git
cd Notes
npm install
npm run start-server
npm run server:start
```
### Documentation

View File

@ -44,7 +44,7 @@ Trilium предоставляется в виде десктопного при
```shell
npm install
npm run start-server
npm run server:start
```
## 👏 Благодарности

View File

@ -38,7 +38,7 @@
<div id="content" class="type-text ck-content">
<h3>Run server</h3><p>Run with default settings:</p><pre><code class="language-text-plain">npm run start-server</code></pre><p>Run with custom port:</p><pre><code class="language-text-plain">TRILIUM_PORT=8082 npm run start-server</code></pre><h3>Run Electron</h3><p>Rebuild <code>better-sqlite3</code> dependency:</p><pre><code class="language-text-plain">npm run switch-electron</code></pre><p>Then run Electron:</p><pre><code class="language-text-plain">npm run start-electron</code></pre><p>To run Electron using the same data directory as the production version:</p><pre><code class="language-text-plain">npm run start-electron-no-dir</code></pre><p>When done, switch back the <code>better-sqlite3</code> dependency:</p><pre><code class="language-text-plain">npm run switch-server</code></pre>
<h3>Run server</h3><p>Run with default settings:</p><pre><code class="language-text-plain">npm run server:start</code></pre><p>Run with custom port:</p><pre><code class="language-text-plain">TRILIUM_PORT=8082 npm run server:start</code></pre><h3>Run Electron</h3><p>Rebuild <code>better-sqlite3</code> dependency:</p><pre><code class="language-text-plain">npm run electron:switch</code></pre><p>Then run Electron:</p><pre><code class="language-text-plain">npm run electron:start</code></pre><p>To run Electron using the same data directory as the production version:</p><pre><code class="language-text-plain">npm run electron:start-no-dir</code></pre><p>When done, switch back the <code>better-sqlite3</code> dependency:</p><pre><code class="language-text-plain">npm run server:switch</code></pre>
</div>

View File

@ -38,7 +38,7 @@
<div id="content" class="type-text ck-content">
<ul><li>Provides context about when the build was made and the corresponding Git revision.</li><li>The information is displayed to the client when going in the about dialog.</li><li>The build information is hard-coded in <code>src/services/build.ts</code>. This file is generated automatically via <code>npm run update-build-info</code> which itself is run automatically whenever making a build in the CI, or a <a href="UTB518X6X9Uh.html" class="type-text">local delivery</a>.</li></ul>
<ul><li>Provides context about when the build was made and the corresponding Git revision.</li><li>The information is displayed to the client when going in the about dialog.</li><li>The build information is hard-coded in <code>src/services/build.ts</code>. This file is generated automatically via <code>npm run chore:update-build-info</code> which itself is run automatically whenever making a build in the CI, or a <a href="UTB518X6X9Uh.html" class="type-text">local delivery</a>.</li></ul>
</div>

View File

@ -8,7 +8,7 @@ test("Help popup", async ({ page, context }) => {
await app.goto();
const popupPromise = page.waitForEvent("popup");
await app.currentNoteSplit.press("F1");
await app.currentNoteSplit.press("Shift+F1");
await page.getByRole("link", { name: "online" }).click();
const popup = await popupPromise;
expect(popup.url()).toBe("https://triliumnext.github.io/Docs/");

10
entitlements.plist Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>

View File

@ -3,6 +3,13 @@ const fs = require("fs-extra");
const APP_NAME = "TriliumNext Notes";
const extraResourcesForPlatform = getExtraResourcesForPlatform();
const baseLinuxMakerConfigOptions = {
icon: "./images/app-icons/png/128x128.png",
desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs"),
categories: ["Office", "Utility"]
};
module.exports = {
packagerConfig: {
executableName: "trilium",
@ -10,37 +17,39 @@ module.exports = {
overwrite: true,
asar: true,
icon: "./images/app-icons/icon",
osxSign: {},
osxNotarize: {
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
teamId: process.env.APPLE_TEAM_ID
},
extraResource: [
// Moved to root
...getExtraResourcesForPlatform(),
// All resources should stay in Resources directory for macOS
...(process.platform === "darwin" ? [] : extraResourcesForPlatform),
// Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS)
// These always go in Resources
"translations/",
"node_modules/@highlightjs/cdn-assets/styles"
],
afterComplete: [
(buildPath, _electronVersion, platform, _arch, callback) => {
const extraResources = getExtraResourcesForPlatform();
for (const resource of extraResources) {
const baseName = path.basename(resource);
let sourcePath;
if (platform === "darwin") {
sourcePath = path.join(buildPath, `${APP_NAME}.app`, "Contents", "Resources", baseName);
} else {
sourcePath = path.join(buildPath, "resources", baseName);
}
let destPath;
// Only move resources on non-macOS platforms
if (platform !== "darwin") {
for (const resource of extraResourcesForPlatform) {
const baseName = path.basename(resource);
const sourcePath = path.join(buildPath, "resources", baseName);
if (baseName !== "256x256.png") {
destPath = path.join(buildPath, baseName);
} else {
destPath = path.join(buildPath, "icon.png");
}
// prettier-ignore
const destPath = (baseName !== "256x256.png")
? path.join(buildPath, baseName)
: path.join(buildPath, "icon.png");
// Copy files from resources folder to root
fs.move(sourcePath, destPath)
.then(() => callback())
.catch((err) => callback(err));
fs.move(sourcePath, destPath)
.then(() => callback())
.catch((err) => callback(err));
}
} else {
callback();
}
}
]
@ -53,17 +62,38 @@ module.exports = {
name: "@electron-forge/maker-deb",
config: {
options: {
icon: "./images/app-icons/png/128x128.png",
desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs")
...baseLinuxMakerConfigOptions
}
}
},
{
name: "@electron-forge/maker-flatpak",
config: {
options: {
...baseLinuxMakerConfigOptions,
id: "com.triliumnext.notes",
runtimeVersion: "24.08",
base: "org.electronjs.Electron2.BaseApp",
baseVersion: "24.08",
baseFlatpakref: "https://flathub.org/repo/flathub.flatpakrepo",
modules: [
{
name: "zypak",
sources: {
type: "git",
url: "https://github.com/refi64/zypak",
tag: "v2024.01.17"
}
}
]
},
}
},
{
name: "@electron-forge/maker-rpm",
config: {
options: {
icon: "./images/app-icons/png/128x128.png",
desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs")
...baseLinuxMakerConfigOptions
}
}
},
@ -100,21 +130,20 @@ module.exports = {
};
function getExtraResourcesForPlatform() {
let resources = ["dump-db/", "./bin/tpl/anonymize-database.sql"];
const scripts = ["trilium-portable", "trilium-safe-mode", "trilium-no-cert-check"];
const resources = ["dump-db/", "./bin/tpl/anonymize-database.sql"];
const getScriptRessources = () => {
const scripts = ["trilium-portable", "trilium-safe-mode", "trilium-no-cert-check"];
const scriptExt = (process.platform === "win32") ? "bat" : "sh";
return scripts.map(script => `./bin/tpl/${script}.${scriptExt}`);
}
switch (process.platform) {
case "win32":
for (const script of scripts) {
resources.push(`./bin/tpl/${script}.bat`);
}
break;
case "darwin":
resources.push(...getScriptRessources())
break;
case "linux":
resources.push("images/app-icons/png/256x256.png");
for (const script of scripts) {
resources.push(`./bin/tpl/${script}.sh`);
}
resources.push(...getScriptRessources(), "images/app-icons/png/256x256.png");
break;
default:
break;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -1,124 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve">
<style type="text/css">
.st0{fill:#686768;}
.st1{fill:#808080;}
.st2{fill:url(#SVGID_1_);}
.st3{fill:url(#SVGID_2_);}
.st4{fill:url(#SVGID_3_);}
.st5{fill:#D9D9D9;}
.st6{fill:url(#SVGID_4_);}
.st7{opacity:0.47;}
.st8{fill:#5B5A5A;}
.st9{fill:#95C980;}
.st10{fill:#72B755;}
.st11{fill:#4FA52B;}
.st12{fill:#EE8C89;}
.st13{fill:#E96562;}
.st14{fill:#E33F3B;}
.st15{fill:#EFB075;}
.st16{fill:#E99547;}
.st17{fill:#E47B19;}
.st18{opacity:0.38;fill:url(#SVGID_5_);}
</style>
<g id="Layer_1">
<g id="Layer_1_1_">
</g>
</g>
<g id="Layer_2">
<polygon class="st0" points="86.3,63.2 86.2,99.4 33.1,101.1 32.5,99.9 53.9,67.1 "/>
<path class="st1" d="M86.3,61.9l-0.2,37.5c0,0-53.9,0.8-53.7,0.5l21.2-34L86.3,61.9z"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="86.3479" y1="130.1949" x2="208.0555" y2="130.1949" gradientTransform="matrix(1 0 0 -1 0 258)">
<stop offset="0" style="stop-color:#E3E3E3"/>
<stop offset="1" style="stop-color:#F4F4F4"/>
</linearGradient>
<polygon class="st2" points="86.3,61.9 207.8,68.9 208.1,188.4 86.7,193.7 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="53.6067" y1="130.1949" x2="86.6402" y2="130.1949" gradientTransform="matrix(1 0 0 -1 0 258)">
<stop offset="0" style="stop-color:#D9D9D9"/>
<stop offset="1" style="stop-color:#D4D4D4"/>
</linearGradient>
<polygon class="st3" points="53.6,65.8 86.3,61.9 86.6,193.7 53.6,189.9 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="154.1068" y1="201.7921" x2="146.7211" y2="271.9565" gradientTransform="matrix(0.9941 1.431752e-03 -1.431754e-03 -1.1143 -2.6949 323.3557)">
<stop offset="0" style="stop-color:#B3B3B3"/>
<stop offset="0.4752" style="stop-color:#B5B5B5"/>
<stop offset="0.6464" style="stop-color:#BCBCBC"/>
<stop offset="0.7685" style="stop-color:#C7C7C7"/>
<stop offset="0.8671" style="stop-color:#D8D8D8"/>
<stop offset="0.9506" style="stop-color:#EEEEEE"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon class="st4" points="208.1,103.8 109,99.5 86.3,62.2 207.8,68.9 "/>
<polygon class="st1" points="112.8,93.1 234.1,99.3 235.4,97.8 88.5,63.5 86.3,61.9 "/>
<polygon class="st5" points="235.4,97.8 133.9,92.5 112.8,91.1 86.6,62.2 208,68.9 219.8,81.8 235,97.4 235,97.4 "/>
<radialGradient id="SVGID_4_" cx="480.8341" cy="689.2393" r="3.2653" gradientTransform="matrix(0.5088 -4.329579e-03 -0.1464 -14.7395 -56.4799 10295.6123)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="6.758273e-02" style="stop-color:#FFFFFF;stop-opacity:0.9324"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</radialGradient>
<path class="st6" d="M88.5,147.7c0.2,21.3,0.7,34.5,0.1,34.5c-0.7,0-1.2-5.1-2-26.3c-0.9-21.2-1.1-69.1-0.4-69.2
C86.8,86.5,88.3,126.5,88.5,147.7z"/>
<g class="st7">
<path class="st8" d="M53.5,189.5l16.6,1.7c2.7,0.3,5.6,0.5,8.3,0.9l8.3,1h-0.1l60.7-2.6l30.4-1.4l7.6-0.3c2.5-0.1,5.1-0.2,7.6-0.3
l15.2-0.4l-15.2,1c-2.5,0.2-5.1,0.3-7.6,0.4l-7.6,0.3l-30.4,1.4l-60.6,2.6l0,0l0,0l-8.3-1c-2.7-0.3-5.5-0.7-8.3-1.1L53.5,189.5z"
/>
<path class="st1" d="M53.1,189.5c2.7,0.2,5.5,0.3,8.4,0.5c2.9,0.2,5.8,0.4,8.6,0.7l8.3,0.9l8.3,1l-0.1,0.9l-0.1,0l0-0.9l60.8-2.5
l30.4-1.3l15.2-0.6l15.2-0.4l0,0.5l-15.2,0.9l-7.6,0.4l-7.6,0.3l-30.4,1.3l-60.7,2.5l0,0l0,0l-8.3-1l-8.3-1.1l-16.6-2.2
L53.1,189.5z M54.5,189.7l15.6,2l8.3,1.1l8.3,1l0,0l60.7-2.7l30.4-1.4l7.6-0.3l7.6-0.4l15.2-1l0,0.5l-15.2,0.4l-15.2,0.6
l-30.4,1.4l-60.7,2.7l0-0.9l0.1,0l-0.1,0.9l-8.3-1l-8.3-0.9c-2.7-0.3-5.3-0.5-7.9-0.9C59.6,190.4,57.1,190,54.5,189.7z"/>
</g>
<g>
<g>
<g>
<path class="st9" d="M177.3,134.6c-7.1,5.5-19.1,6.6-27.5,4.5c2.1-1.9,3.1-2.8,5.1-4.7c2-1.8,2.9-2.6,4.9-4.4
c6.8-6,10.2-8.8,16.8-14.4c-6.9,5.2-10.5,7.8-17.6,13.3c-2,1.6-3,2.3-5,3.9c-2.1,1.6-3.1,2.5-5.2,4.1c-0.4-7.7,0.9-18,8.4-23.5
c0.6-0.5,1.4-0.9,2.1-1.4c1-0.5,2-1.1,3.2-1.5c9.3-3.6,20.2-6.1,30.3-4.8c0.6,5.3-4,15.4-9.6,22.9c-0.8,1-1.5,2-2.3,2.9
C179.6,132.6,178.4,133.7,177.3,134.6z"/>
<path class="st10" d="M180.7,131.4c-7.5,4.9-18.4,4.7-25.8,3c2-1.8,2.9-2.6,4.9-4.4c6.8-6,10.2-8.8,16.8-14.4
c-6.9,5.2-10.5,7.8-17.6,13.3c-2,1.6-3,2.3-5,3.9c-0.4-6.4,0.3-15,5.3-20.7c1-0.5,2-1.1,3.2-1.5c9.3-3.7,20.2-6.1,30.3-4.8
c0.6,5.3-4,15.4-9.6,22.9C182.3,129.5,181.5,130.5,180.7,131.4z"/>
<path class="st11" d="M183.1,128.5c-7.2,3.4-16.7,3.1-23.2,1.5c6.8-6,10.2-8.8,16.8-14.4c-6.9,5.2-10.5,7.8-17.6,13.3
c-0.4-5.5,0-12.8,3.5-18.4c9.3-3.6,20.2-6.1,30.3-4.8C193.4,111,188.8,121.2,183.1,128.5z"/>
</g>
<g>
<path class="st12" d="M144,161.3c-2.9-7.2,0.4-15.9,3.8-21.1c0.9,2,1.3,3,2.2,4.9c0.9,1.9,1.3,2.8,2.1,4.6
c3,6.4,4.5,9.6,7.6,15.6c-2.5-6.2-3.8-9.4-6.4-16c-0.7-1.8-1.1-2.7-1.8-4.5c-0.8-1.9-1.2-2.9-1.9-4.9c6,1.8,13.7,5.4,16.5,12.2
c0.2,0.6,0.4,1.2,0.6,1.8c0.2,0.8,0.4,1.7,0.5,2.6c1.2,7.8-0.7,18.7-3.6,22.8c-4.5-0.7-11.8-6.3-16.3-12.3
c-0.6-0.8-1.2-1.6-1.7-2.4C145,163.5,144.5,162.4,144,161.3z"/>
<path class="st13" d="M145.7,164.6c-1.9-6.8,1.2-14.7,4.4-19.5c0.9,1.9,1.3,2.8,2.1,4.6c3,6.4,4.5,9.6,7.6,15.6
c-2.5-6.2-3.8-9.4-6.4-16c-0.7-1.8-1.1-2.7-1.8-4.5c5.1,1.4,11.7,4.2,15.2,9.2c0.2,0.8,0.4,1.7,0.5,2.6
c1.2,7.8-0.7,18.7-3.6,22.7c-4.5-0.7-11.8-6.3-16.3-12.3C146.8,166.2,146.3,165.4,145.7,164.6z"/>
<path class="st14" d="M147.4,167c-0.9-6.1,2-13,4.8-17.3c3,6.4,4.5,9.6,7.6,15.6c-2.5-6.2-3.8-9.4-6.4-16
c4.5,1.1,10.2,3.3,14,7.2c1.2,7.8-0.7,18.7-3.6,22.7C159.1,178.5,151.9,173,147.4,167z"/>
</g>
<g>
<path class="st15" d="M132.2,118.5c8.5,3.5,13.4,12.5,15.3,19c-2.4-0.8-3.5-1.1-5.9-1.9c-2.3-0.7-3.4-1.1-5.6-1.8
c-7.9-2.6-11.8-4-19.4-6.7c7.4,3.1,11.1,4.7,18.8,7.8c2.1,0.9,3.2,1.3,5.3,2.2c2.2,0.9,3.3,1.3,5.6,2.2c-6,3.4-15,6.8-23,3.7
c-0.6-0.2-1.4-0.6-2.1-1c-0.8-0.5-1.8-1-2.7-1.7c-6.9-4.9-14.3-13.6-17-21.8c4-2.8,14.3-3.4,23-1.9c1.2,0.2,2.3,0.4,3.5,0.7
C129.6,117.5,130.9,117.9,132.2,118.5z"/>
<path class="st16" d="M128,117.1c7.4,3.9,11.8,12.2,13.6,18.4c-2.3-0.7-3.4-1.1-5.6-1.8c-7.9-2.6-11.8-4-19.4-6.7
c7.4,3.1,11.1,4.7,18.7,7.8c2.1,0.9,3.2,1.3,5.3,2.2c-5,2.9-12.5,6-19.4,4.9c-0.8-0.5-1.8-1-2.7-1.7c-6.9-4.9-14.3-13.6-17-21.8
c4-2.8,14.3-3.4,23-1.9C125.8,116.6,126.9,116.7,128,117.1z"/>
<path class="st17" d="M124.6,116.3c6.1,4.3,9.8,11.7,11.4,17.3c-7.9-2.6-11.8-4-19.4-6.7c7.4,3.1,11.1,4.7,18.8,7.8
c-4.4,2.6-10.6,5.4-16.8,5.3c-6.9-4.9-14.3-13.6-17-21.8C105.6,115.4,115.8,114.8,124.6,116.3z"/>
</g>
</g>
</g>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="226.3925" y1="147.1083" x2="159.9567" y2="186.9981">
<stop offset="0.1721" style="stop-color:#C7C7C7"/>
<stop offset="0.3798" style="stop-color:#D8D8D8"/>
<stop offset="0.6814" style="stop-color:#DADADA"/>
<stop offset="0.7898" style="stop-color:#E1E1E1"/>
<stop offset="0.867" style="stop-color:#ECECEC"/>
<stop offset="0.8745" style="stop-color:#EEEEEE"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<path class="st18" d="M208,128c-0.8,0.3-2.7,12.8-3,15.6c-0.6,4.7-3.2,23.3-9,33.5c-5.9,10.4-12.8,11.1-13.3,11.9l25.3-0.7
C208,169.3,208,146.9,208,128L208,128z"/>
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve">
<style type="text/css">
.st0{fill:#686768;}
.st1{fill:#808080;}
.st2{fill:url(#SVGID_1_);}
.st3{fill:url(#SVGID_2_);}
.st4{fill:url(#SVGID_3_);}
.st5{fill:#D9D9D9;}
.st6{fill:url(#SVGID_4_);}
.st7{opacity:0.47;}
.st8{fill:#5B5A5A;}
.st9{fill:#95C980;}
.st10{fill:#72B755;}
.st11{fill:#4FA52B;}
.st12{fill:#EE8C89;}
.st13{fill:#E96562;}
.st14{fill:#E33F3B;}
.st15{fill:#EFB075;}
.st16{fill:#E99547;}
.st17{fill:#E47B19;}
.st18{opacity:0.38;fill:url(#SVGID_5_);enable-background:new ;}
</style>
<g id="Layer_1_2_">
<g id="Layer_1_1_">
</g>
</g>
<g id="Layer_2_1_">
<polygon class="st0" points="69.5,48.6 69.3,93.1 4,95.2 3.3,93.7 29.6,53.4 "/>
<path class="st1" d="M69.5,47l-0.2,46.1c0,0-66.3,1-66,0.6l26.1-41.8L69.5,47z"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="69.458" y1="120.0202" x2="219.2576" y2="120.0202" gradientTransform="matrix(1 0 0 1 0 8)">
<stop offset="0" style="stop-color:#E3E3E3"/>
<stop offset="1" style="stop-color:#F4F4F4"/>
</linearGradient>
<polygon class="st2" points="69.5,47 218.9,55.6 219.3,202.6 69.9,209.1 "/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="29.2408" y1="120.0202" x2="69.8681" y2="120.0202" gradientTransform="matrix(1 0 0 1 0 8)">
<stop offset="0" style="stop-color:#D9D9D9"/>
<stop offset="1" style="stop-color:#D4D4D4"/>
</linearGradient>
<polygon class="st3" points="29.2,51.8 69.5,47 69.8,209.1 29.2,204.4 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="151.9309" y1="42.7213" x2="142.8473" y2="-43.5726" gradientTransform="matrix(0.9941 1.431752e-03 1.431754e-03 1.1143 -3.0394 44.4335)">
<stop offset="0" style="stop-color:#B3B3B3"/>
<stop offset="0.4752" style="stop-color:#B5B5B5"/>
<stop offset="0.6464" style="stop-color:#BCBCBC"/>
<stop offset="0.7685" style="stop-color:#C7C7C7"/>
<stop offset="0.8671" style="stop-color:#D8D8D8"/>
<stop offset="0.9506" style="stop-color:#EEEEEE"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<polygon class="st4" points="219.3,98.5 97.4,93.2 69.5,47.3 218.9,55.6 "/>
<polygon class="st1" points="102,85.3 251.2,93 252.8,91.1 72.2,48.9 69.5,47 "/>
<polygon class="st5" points="252.8,91.1 128,84.6 102,82.9 69.8,47.3 219.1,55.6 233.6,71.4 252.3,90.6 252.3,90.6 "/>
<radialGradient id="SVGID_4_" cx="445.2994" cy="-436.338" r="4.0179" gradientTransform="matrix(0.5088 -4.329579e-03 0.1464 14.7395 -92.0455 6569.5317)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#FFFFFF"/>
<stop offset="6.758273e-02" style="stop-color:#FFFFFF;stop-opacity:0.9324"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</radialGradient>
<path class="st6" d="M72.2,152.5c0.2,26.2,0.9,42.4,0.1,42.4c-0.9,0-1.5-6.3-2.5-32.3c-1.1-26.1-1.4-85-0.5-85.1
C70.1,77.2,71.9,126.4,72.2,152.5z"/>
<g class="st7">
<path class="st8" d="M29.1,203.9l20.4,2.1c3.3,0.4,6.9,0.6,10.2,1.1l10.2,1.2h-0.1l74.7-3.2l37.4-1.7l9.3-0.4
c3.1-0.1,6.3-0.2,9.3-0.4l18.7-0.5l-18.7,1.2c-3.1,0.2-6.3,0.4-9.3,0.5l-9.3,0.4l-37.4,1.7l-74.5,3.2l0,0l0,0L59.7,208
c-3.3-0.4-6.8-0.9-10.2-1.4L29.1,203.9z"/>
<path class="st1" d="M28.6,203.9c3.3,0.2,6.8,0.4,10.3,0.6s7.1,0.5,10.6,0.9l10.2,1.1l10.2,1.2l-0.1,1.1h-0.1v-1.1l74.8-3.1
l37.4-1.6l18.7-0.7l18.7-0.5v0.6l-18.7,1.1l-9.3,0.5l-9.3,0.4l-37.4,1.6l-74.7,3.1l0,0l0,0l-10.2-1.2l-10.2-1.4L29,203.8
L28.6,203.9z M30.3,204.1l19.2,2.5l10.2,1.4l10.2,1.2l0,0l74.7-3.3l37.4-1.7l9.3-0.4l9.3-0.5l18.7-1.2v0.6l-18.7,0.5l-18.7,0.7
l-37.4,1.7l-74.7,3.3v-1.1h0.1l-0.1,1.1l-10.2-1.2l-10.2-1.1c-3.3-0.4-6.5-0.6-9.7-1.1C36.6,205,33.5,204.5,30.3,204.1z"/>
</g>
<g>
<g>
<g>
<path class="st9" d="M181.4,136.4c-8.7,6.8-23.5,8.1-33.8,5.5c2.6-2.3,3.8-3.4,6.3-5.8c2.5-2.2,3.6-3.2,6-5.4
c8.4-7.4,12.5-10.8,20.7-17.7c-8.5,6.4-12.9,9.6-21.6,16.4c-2.5,2-3.7,2.8-6.1,4.8c-2.6,2-3.8,3.1-6.4,5
c-0.5-9.5,1.1-22.1,10.3-28.9c0.7-0.6,1.7-1.1,2.6-1.7c1.2-0.6,2.5-1.4,3.9-1.8c11.4-4.4,24.8-7.5,37.3-5.9
c0.7,6.5-4.9,18.9-11.8,28.2c-1,1.2-1.8,2.5-2.8,3.6C184.2,133.9,182.7,135.3,181.4,136.4z"/>
<path class="st10" d="M185.6,132.4c-9.2,6-22.6,5.8-31.7,3.7c2.5-2.2,3.6-3.2,6-5.4c8.4-7.4,12.5-10.8,20.7-17.7
c-8.5,6.4-12.9,9.6-21.6,16.4c-2.5,2-3.7,2.8-6.1,4.8c-0.5-7.9,0.4-18.4,6.5-25.5c1.2-0.6,2.5-1.4,3.9-1.8
c11.4-4.6,24.8-7.5,37.3-5.9c0.7,6.5-4.9,18.9-11.8,28.2C187.5,130.1,186.5,131.3,185.6,132.4z"/>
<path class="st11" d="M188.5,128.9c-8.9,4.2-20.5,3.8-28.5,1.8c8.4-7.4,12.5-10.8,20.7-17.7c-8.5,6.4-12.9,9.6-21.6,16.4
c-0.5-6.8,0-15.7,4.3-22.6c11.4-4.4,24.8-7.5,37.3-5.9C201.2,107.4,195.5,119.9,188.5,128.9z"/>
</g>
<g>
<path class="st12" d="M140.4,169.2c-3.6-8.9,0.5-19.6,4.7-26c1.1,2.5,1.6,3.7,2.7,6c1.1,2.3,1.6,3.4,2.6,5.7
c3.7,7.9,5.5,11.8,9.3,19.2c-3.1-7.6-4.7-11.6-7.9-19.7c-0.9-2.2-1.4-3.3-2.2-5.5c-1-2.3-1.5-3.6-2.3-6
c7.4,2.2,16.8,6.6,20.3,15c0.2,0.7,0.5,1.5,0.7,2.2c0.2,1,0.5,2.1,0.6,3.2c1.5,9.6-0.9,23-4.4,28c-5.5-0.9-14.5-7.7-20-15.1
c-0.7-1-1.5-2-2.1-3C141.7,171.9,141,170.6,140.4,169.2z"/>
<path class="st13" d="M142.5,173.3c-2.3-8.4,1.5-18.1,5.4-24c1.1,2.3,1.6,3.4,2.6,5.7c3.7,7.9,5.5,11.8,9.3,19.2
c-3.1-7.6-4.7-11.6-7.9-19.7c-0.9-2.2-1.4-3.3-2.2-5.5c6.3,1.7,14.4,5.2,18.7,11.3c0.2,1,0.5,2.1,0.6,3.2
c1.5,9.6-0.9,23-4.4,27.9c-5.5-0.9-14.5-7.7-20-15.1C143.9,175.2,143.3,174.3,142.5,173.3z"/>
<path class="st14" d="M144.6,176.2c-1.1-7.5,2.5-16,5.9-21.3c3.7,7.9,5.5,11.8,9.3,19.2c-3.1-7.6-4.7-11.6-7.9-19.7
c5.5,1.4,12.5,4.1,17.2,8.9c1.5,9.6-0.9,23-4.4,27.9C159,190.4,150.1,183.6,144.6,176.2z"/>
</g>
<g>
<path class="st15" d="M125.9,116.6c10.5,4.3,16.5,15.4,18.8,23.4c-3-1-4.3-1.4-7.3-2.3c-2.8-0.9-4.2-1.4-6.9-2.2
c-9.7-3.2-14.5-4.9-23.9-8.2c9.1,3.8,13.7,5.8,23.1,9.6c2.6,1.1,3.9,1.6,6.5,2.7c2.7,1.1,4.1,1.6,6.9,2.7
c-7.4,4.2-18.4,8.4-28.3,4.6c-0.7-0.2-1.7-0.7-2.6-1.2c-1-0.6-2.2-1.2-3.3-2.1c-8.5-6-17.6-16.7-20.9-26.8
c4.9-3.4,17.6-4.2,28.3-2.3c1.5,0.2,2.8,0.5,4.3,0.9C122.7,115.4,124.3,115.8,125.9,116.6z"/>
<path class="st16" d="M120.7,114.9c9.1,4.8,14.5,15,16.7,22.6c-2.8-0.9-4.2-1.4-6.9-2.2c-9.7-3.2-14.5-4.9-23.9-8.2
c9.1,3.8,13.7,5.8,23,9.6c2.6,1.1,3.9,1.6,6.5,2.7c-6.1,3.6-15.4,7.4-23.9,6c-1-0.6-2.2-1.2-3.3-2.1c-8.5-6-17.6-16.7-20.9-26.8
c4.9-3.4,17.6-4.2,28.3-2.3C118,114.2,119.4,114.4,120.7,114.9z"/>
<path class="st17" d="M116.6,113.9c7.5,5.3,12.1,14.4,14,21.3c-9.7-3.2-14.5-4.9-23.9-8.2c9.1,3.8,13.7,5.8,23.1,9.6
c-5.4,3.2-13,6.6-20.7,6.5c-8.5-6-17.6-16.7-20.9-26.8C93.2,112.8,105.7,112,116.6,113.9z"/>
</g>
</g>
</g>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="241.7537" y1="104.2354" x2="160.0455" y2="55.1756" gradientTransform="matrix(1 0 0 -1 0 256)">
<stop offset="0.1721" style="stop-color:#C7C7C7"/>
<stop offset="0.3798" style="stop-color:#D8D8D8"/>
<stop offset="0.6814" style="stop-color:#DADADA"/>
<stop offset="0.7898" style="stop-color:#E1E1E1"/>
<stop offset="0.867" style="stop-color:#ECECEC"/>
<stop offset="0.8745" style="stop-color:#EEEEEE"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</linearGradient>
<path class="st18" d="M219.1,128.3c-1,0.4-3.3,15.7-3.7,19.2c-0.7,5.8-3.9,28.7-11.1,41.2c-7.3,12.8-15.7,13.7-16.4,14.6l31.1-0.9
C219.1,179.1,219.1,151.5,219.1,128.3L219.1,128.3z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -198,6 +198,7 @@
ext: [ "hcl " ],
mime: "text/x-hcl",
mode: "hcl",
name: "Terraform (HCL)"
});
});

2666
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "trilium",
"productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.91.5",
"version": "0.92.2-beta",
"license": "AGPL-3.0-only",
"main": "./dist/electron-main.js",
"author": {
@ -20,61 +20,68 @@
},
"type": "module",
"scripts": {
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-server-safe": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-test-server": "npm run switch-server && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts",
"qstart-server": "npm run switch-server && npm run start-server",
"start-electron": "npm run prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.js --inspect=5858 .",
"start-electron-nix": "electron-rebuild --version 33.3.1 && npm run prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"start-electron-no-dir": "npm run prepare-dist && cross-env TRILIUM_ENV=dev electron --inspect=5858 .",
"start-electron-no-dir-nix": "electron-rebuild --version 33.3.1 && npm run prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"qstart-electron": "npm run switch-electron && npm run start-electron",
"switch-server": "rimraf ./node_modules/better-sqlite3 && npm install",
"switch-electron": "electron-rebuild",
"build-backend-docs": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts",
"build-frontend-docs": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts",
"test-playwright": "playwright test",
"test": "cross-env TRILIUM_DATA_DIR=./data-test vitest",
"test-coverage": "cross-env TRILIUM_DATA_DIR=./data-test vitest --coverage",
"start-electron-forge": "npm run prepare-dist && electron-forge start",
"make-electron": "npm run webpack && npm run prepare-dist && electron-forge make",
"package-electron": "electron-forge package",
"prepare-dist": "rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
"watch-dist": "tsx ./bin/watch-dist.ts",
"update-build-info": "tsx bin/update-build-info.ts",
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
"ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts",
"prettier-check": "prettier . --check",
"prettier-fix": "prettier . --write"
"server:start": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"server:start-safe": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"server:start-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"server:start-test": "npm run server:switch && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts",
"server:qstart": "npm run server:switch && npm run server:start",
"server:switch": "rimraf ./node_modules/better-sqlite3 && npm install",
"electron:start": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./electron-main.ts --inspect=5858 .",
"electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev electron --inspect=5858 .",
"electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_34 --run \"electron ./electron-main.ts --inspect=5858 .\"",
"electron:start-nix-no-dir": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
"electron:start-prod": "npm run build:prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.js --inspect=5858 .",
"electron:start-prod-no-dir": "npm run build:prepare-dist && cross-env TRILIUM_ENV=dev electron --inspect=5858 .",
"electron:start-prod-nix": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"electron:qstart": "npm run electron:switch && npm run electron:start",
"electron:switch": "electron-rebuild",
"electron-forge:start": "npm run build:prepare-dist && electron-forge start",
"electron-forge:make": "npm run build:prepare-dist && electron-forge make",
"electron-forge:package": "npm run build:prepare-dist && electron-forge package",
"docs:build-backend": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts",
"docs:build-frontend": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"docs:build": "npm run docs:build-backend && npm run docs:build-frontend",
"build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts",
"build:prepare-dist": "npm run build:webpack && rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
"test": "cross-env TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest",
"test:coverage": "cross-env TRILIUM_DATA_DIR=./integration-tests/db vitest --coverage",
"test:playwright": "playwright test",
"test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test:integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"dev:watch-dist": "tsx ./bin/watch-dist.ts",
"dev:prettier-check": "prettier . --check",
"dev:prettier-fix": "prettier . --write",
"chore:update-build-info": "tsx bin/update-build-info.ts",
"chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts",
"chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
"chore:generate-openapi": "tsx bin/generate-openapi.js"
},
"dependencies": {
"@braintree/sanitize-url": "7.1.1",
"@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "0.17.6",
"@fullcalendar/core": "6.1.15",
"@fullcalendar/daygrid": "6.1.15",
"@fullcalendar/interaction": "6.1.15",
"@highlightjs/cdn-assets": "11.11.1",
"@joplin/turndown-plugin-gfm": "1.0.61",
"@mermaid-js/layout-elk": "0.1.7",
"@mind-elixir/node-menu": "1.0.4",
"@triliumnext/express-partial-content": "1.0.1",
"@types/leaflet": "1.9.16",
"@types/react-dom": "18.3.5",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"autocomplete.js": "0.38.1",
"axios": "1.7.9",
"better-sqlite3": "11.8.1",
"bootstrap": "5.3.3",
"boxicons": "2.1.4",
"chardet": "2.0.0",
"cheerio": "1.0.0",
"chokidar": "4.0.3",
"cls-hooked": "4.2.2",
"codemirror": "5.65.18",
"compression": "1.7.5",
"compression": "1.8.0",
"cookie-parser": "1.4.7",
"csrf-csrf": "3.1.0",
"dayjs": "1.11.13",
@ -105,10 +112,10 @@
"is-animated": "2.0.2",
"is-svg": "5.1.0",
"jimp": "1.6.0",
"joplin-turndown-plugin-gfm": "1.0.12",
"jquery": "3.7.1",
"jquery-hotkeys": "0.2.2",
"jquery.fancytree": "2.38.4",
"js-yaml": "4.1.0",
"jsdom": "26.0.0",
"jsplumb": "2.15.6",
"katex": "0.16.21",
@ -116,10 +123,10 @@
"leaflet": "1.9.4",
"leaflet-gpx": "2.1.2",
"mark.js": "8.11.1",
"marked": "15.0.6",
"marked": "15.0.7",
"mermaid": "11.4.1",
"mime-types": "2.1.35",
"mind-elixir": "4.3.6",
"mind-elixir": "4.3.7",
"multer": "1.4.5-lts.1",
"normalize-strings": "1.1.1",
"normalize.css": "8.0.1",
@ -136,26 +143,30 @@
"source-map-support": "0.5.21",
"split.js": "1.6.5",
"stream-throttle": "0.1.3",
"strip-bom": "5.0.0",
"striptags": "3.2.0",
"swagger-ui-express": "5.0.1",
"tmp": "0.2.3",
"ts-loader": "9.5.2",
"turndown": "7.2.0",
"unescape": "1.0.1",
"vanilla-js-wheel-zoom": "9.0.4",
"ws": "8.18.0",
"ws": "8.18.1",
"xml2js": "0.6.2",
"yauzl": "3.2.0"
},
"devDependencies": {
"@electron-forge/cli": "7.6.1",
"@electron-forge/maker-deb": "7.6.1",
"@electron-forge/maker-dmg": "7.6.1",
"@electron-forge/maker-rpm": "7.6.1",
"@electron-forge/maker-squirrel": "7.6.1",
"@electron-forge/maker-zip": "7.6.1",
"@electron-forge/plugin-auto-unpack-natives": "7.6.1",
"@electron-forge/cli": "7.7.0",
"@electron-forge/maker-deb": "7.7.0",
"@electron-forge/maker-dmg": "7.7.0",
"@electron-forge/maker-flatpak": "7.7.0",
"@electron-forge/maker-rpm": "7.7.0",
"@electron-forge/maker-squirrel": "7.7.0",
"@electron-forge/maker-zip": "7.7.0",
"@electron-forge/plugin-auto-unpack-natives": "7.7.0",
"@electron/rebuild": "3.7.1",
"@playwright/test": "1.50.1",
"@popperjs/core": "2.11.8",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
"@types/bootstrap": "5.2.10",
@ -172,14 +183,16 @@
"@types/fs-extra": "11.0.4",
"@types/html": "1.0.4",
"@types/ini": "4.1.1",
"@types/jasmine": "5.1.5",
"@types/jquery": "3.5.32",
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7",
"@types/leaflet": "1.9.16",
"@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.13.1",
"@types/node": "22.13.5",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
@ -187,28 +200,36 @@
"@types/session-file-store": "1.2.5",
"@types/source-map-support": "0.5.10",
"@types/stream-throttle": "0.1.4",
"@types/swagger-ui-express": "4.1.8",
"@types/tmp": "0.2.6",
"@types/turndown": "5.0.5",
"@types/ws": "8.5.14",
"@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.0.5",
"@vitest/coverage-v8": "3.0.6",
"autoprefixer": "10.4.20",
"bootstrap": "5.3.3",
"cross-env": "7.0.3",
"electron": "34.0.2",
"css-loader": "7.1.2",
"electron": "34.2.0",
"esm": "3.2.25",
"jasmine": "5.5.0",
"jsdoc": "4.0.4",
"lorem-ipsum": "2.0.8",
"mini-css-extract-plugin": "2.9.2",
"nodemon": "3.1.9",
"prettier": "3.4.2",
"postcss-loader": "8.1.1",
"prettier": "3.5.2",
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"sass": "1.85.0",
"sass-loader": "16.0.5",
"swagger-jsdoc": "6.2.8",
"tslib": "2.8.1",
"tsx": "4.19.2",
"typedoc": "0.27.6",
"tsx": "4.19.3",
"typedoc": "0.27.8",
"typescript": "5.7.3",
"vitest": "3.0.5",
"webpack": "5.97.1",
"vitest": "3.0.6",
"webpack": "5.98.0",
"webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2"
}

View File

@ -74,7 +74,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: !process.env.TRILIUM_DOCKER ? {
command: 'npm run integration-mem-db-dev',
command: 'npm run test:integration-mem-db-dev',
url: SERVER_URL,
reuseExistingServer: !process.env.CI,
} : undefined,

View File

@ -1,8 +1,9 @@
import etapi from "../support/etapi.js";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("app_info", () => {
it("get", async () => {
const appInfo = await etapi.getEtapi("app-info");
expect(appInfo.clipperProtocolVersion).toEqual("1.0");
});
});
*/

View File

@ -1,8 +1,10 @@
import etapi from "../support/etapi.js";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("backup", () => {
it("create", async () => {
const response = await etapi.putEtapiContent("backup/etapi_test");
expect(response.status).toEqual(204);
});
});
*/

View File

@ -3,6 +3,7 @@ import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("import", () => {
// temporarily skip this test since test-export.zip is missing
xit("import", async () => {
@ -22,3 +23,4 @@ etapi.describeEtapi("import", () => {
expect(content).toContain("test export content");
});
});
*/

View File

@ -1,6 +1,7 @@
import crypto from "crypto";
import etapi from "../support/etapi.js";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("notes", () => {
it("create", async () => {
const { note, branch } = await etapi.postEtapi("create-note", {
@ -99,3 +100,4 @@ etapi.describeEtapi("notes", () => {
expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
});
});
*/

View File

@ -4,14 +4,14 @@ import BAttribute from "../../src/becca/entities/battribute.js";
import becca from "../../src/becca/becca.js";
import randtoken from "rand-token";
import type SearchResult from "../../src/services/search/search_result.js";
import type { NoteType } from "../../src/becca/entities/rows.js";
import type { NoteRow, NoteType } from "../../src/becca/entities/rows.js";
randtoken.generator({ source: "crypto" });
function findNoteByTitle(searchResults: Array<SearchResult>, title: string): BNote | undefined {
export function findNoteByTitle(searchResults: Array<SearchResult>, title: string): BNote | undefined {
return searchResults.map((sr) => becca.notes[sr.noteId]).find((note) => note.title === title);
}
class NoteBuilder {
export class NoteBuilder {
note: BNote;
constructor(note: BNote) {
this.note = note;
@ -55,11 +55,11 @@ class NoteBuilder {
}
}
function id() {
export function id() {
return randtoken.generate(10);
}
function note(title: string, extraParams = {}) {
export function note(title: string, extraParams: Partial<NoteRow> = {}) {
const row = Object.assign(
{
noteId: id(),
@ -74,9 +74,3 @@ function note(title: string, extraParams = {}) {
return new NoteBuilder(note);
}
export default {
NoteBuilder,
findNoteByTitle,
note
};

View File

@ -1,4 +1,5 @@
import type child_process from "child_process";
import { describe, beforeAll, afterAll } from "vitest";
let etapiAuthToken: string | undefined;

View File

@ -1,3 +1,35 @@
/**
* Reads the level of indentation of the first line and trims the identation for all the text by that amount.
*
* For example, for:
*
* ```json
* {
* "hello": "world"
* }
* ```
*
* it results in:
*
* ```json
* {
* "hello": "world"
* }
* ```
*
* This is meant to be used as a template string, where it allows the indentation of the template without affecting whitespace changes.
*
* @example const html = trimIndentation`\
* <h1>Heading 1</h1>
* <h2>Heading 2</h2>
* <h3>Heading 3</h3>
* <h4>Heading 4</h4>
* <h5>Heading 5</h5>
* <h6>Heading 6</h6>
* `;
* @param strings
* @returns
*/
export function trimIndentation(strings: TemplateStringsArray) {
const str = strings.toString();

View File

@ -12,6 +12,7 @@ import type { AttachmentRow, BlobRow, RevisionRow } from "./entities/rows.js";
import BBlob from "./entities/bblob.js";
import BRecentNote from "./entities/brecent_note.js";
import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
import type BTask from "./entities/btask.js";
interface AttachmentOpts {
includeContentLength?: boolean;
@ -32,6 +33,7 @@ export default class Becca {
attributeIndex!: Record<string, BAttribute[]>;
options!: Record<string, BOption>;
etapiTokens!: Record<string, BEtapiToken>;
tasks!: Record<string, BTask>;
allNoteSetCache: NoteSet | null;
@ -48,6 +50,7 @@ export default class Becca {
this.attributeIndex = {};
this.options = {};
this.etapiTokens = {};
this.tasks = {};
this.dirtyNoteSetCache();
@ -213,6 +216,14 @@ export default class Becca {
return this.etapiTokens[etapiTokenId];
}
getTasks(): BTask[] {
return Object.values(this.tasks);
}
getTask(taskId: string): BTask | null {
return this.tasks[taskId];
}
getEntity<T extends AbstractBeccaEntity<T>>(entityName: string, entityId: string): AbstractBeccaEntity<T> | null {
if (!entityName || !entityId) {
return null;

View File

@ -11,9 +11,10 @@ import BOption from "./entities/boption.js";
import BEtapiToken from "./entities/betapi_token.js";
import cls from "../services/cls.js";
import entityConstructor from "../becca/entity_constructor.js";
import type { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from "./entities/rows.js";
import type { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow, TaskRow } from "./entities/rows.js";
import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
import ws from "../services/ws.js";
import BTask from "./entities/btask.js";
const beccaLoaded = new Promise<void>(async (res, rej) => {
const sqlInit = (await import("../services/sql_init.js")).default;
@ -63,6 +64,10 @@ function load() {
for (const row of sql.getRows<EtapiTokenRow>(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) {
new BEtapiToken(row);
}
for (const row of sql.getRows<TaskRow>(`SELECT taskId, parentNoteId, title, dueDate, isDone, isDeleted FROM tasks WHERE isDeleted = 0`)) {
new BTask(row);
}
});
for (const noteId in becca.notes) {

View File

@ -159,7 +159,7 @@ class BBranch extends AbstractBeccaEntity<BBranch> {
}
}
if (this.noteId === "root" || this.noteId === cls.getHoistedNoteId()) {
if ((this.noteId === "root" || this.noteId === cls.getHoistedNoteId()) && !this.isWeak) {
throw new Error("Can't delete root or hoisted branch/note");
}

View File

@ -0,0 +1,84 @@
import date_utils from "../../services/date_utils.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
import type BOption from "./boption.js";
import type { TaskRow } from "./rows.js";
export default class BTask extends AbstractBeccaEntity<BOption> {
static get entityName() {
return "tasks";
}
static get primaryKeyName() {
return "taskId";
}
static get hashedProperties() {
return [ "taskId", "parentNoteId", "title", "dueDate", "isDone", "isDeleted" ];
}
taskId?: string;
parentNoteId!: string;
title!: string;
dueDate?: string;
isDone!: boolean;
private _isDeleted?: boolean;
constructor(row?: TaskRow) {
super();
if (!row) {
return;
}
this.updateFromRow(row);
this.init();
}
get isDeleted() {
return !!this._isDeleted;
}
updateFromRow(row: TaskRow) {
this.taskId = row.taskId;
this.parentNoteId = row.parentNoteId;
this.title = row.title;
this.dueDate = row.dueDate;
this.isDone = !!row.isDone;
this._isDeleted = !!row.isDeleted;
this.utcDateModified = row.utcDateModified;
if (this.taskId) {
this.becca.tasks[this.taskId] = this;
}
}
init() {
if (this.taskId) {
this.becca.tasks[this.taskId] = this;
}
}
protected beforeSaving(opts?: {}): void {
super.beforeSaving();
this.utcDateModified = date_utils.utcNowDateTime();
if (this.taskId) {
this.becca.tasks[this.taskId] = this;
}
}
getPojo() {
return {
taskId: this.taskId,
parentNoteId: this.parentNoteId,
title: this.title,
dueDate: this.dueDate,
isDone: this.isDone,
isDeleted: this.isDeleted,
utcDateModified: this.utcDateModified
};
}
}

View File

@ -1,4 +1,5 @@
// TODO: Booleans should probably be numbers instead (as SQLite does not have booleans.);
// TODO: check against schema.sql which properties really are "optional"
export interface AttachmentRow {
attachmentId?: string;
@ -12,6 +13,8 @@ export interface AttachmentRow {
dateModified?: string;
utcDateModified?: string;
utcDateScheduledForErasureSince?: string;
isDeleted?: boolean;
deleteId?: string;
contentLength?: number;
content?: Buffer | string;
}
@ -136,3 +139,13 @@ export interface NoteRow {
utcDateModified: string;
content?: string | Buffer;
}
export interface TaskRow {
taskId?: string;
parentNoteId: string;
title: string;
dueDate?: string;
isDone?: boolean;
isDeleted?: boolean;
utcDateModified?: string;
}

View File

@ -9,6 +9,7 @@ import BNote from "./entities/bnote.js";
import BOption from "./entities/boption.js";
import BRecentNote from "./entities/brecent_note.js";
import BRevision from "./entities/brevision.js";
import BTask from "./entities/btask.js";
type EntityClass = new (row?: any) => AbstractBeccaEntity<any>;
@ -21,7 +22,8 @@ const ENTITY_NAME_TO_ENTITY: Record<string, ConstructorData<any> & EntityClass>
notes: BNote,
options: BOption,
recent_notes: BRecentNote,
revisions: BRevision
revisions: BRevision,
tasks: BTask
};
function getEntityFromEntityName(entityName: keyof typeof ENTITY_NAME_TO_ENTITY) {

View File

@ -18,12 +18,12 @@ import type NoteDetailWidget from "../widgets/note_detail.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
import type { Node } from "../services/tree.js";
import type LoadResults from "../services/load_results.js";
import type { Attribute } from "../services/attribute_parser.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
import type { ContextMenuEvent } from "../menus/context_menu.js";
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget;
@ -48,21 +48,21 @@ export interface CommandData {
* Represents a set of commands that are triggered from the context menu, providing information such as the selected note.
*/
export interface ContextMenuCommandData extends CommandData {
node: Node;
notePath: string;
node: Fancytree.FancytreeNode;
notePath?: string;
noteId?: string;
selectedOrActiveBranchIds: any; // TODO: Remove any once type is defined
selectedOrActiveBranchIds?: any; // TODO: Remove any once type is defined
selectedOrActiveNoteIds: any; // TODO: Remove any once type is defined
}
export interface NoteCommandData extends CommandData {
notePath: string;
notePath?: string;
hoistedNoteId?: string;
viewScope?: ViewScope;
}
export interface ExecuteCommandData extends CommandData {
resolve: unknown;
export interface ExecuteCommandData<T> extends CommandData {
resolve: (data: T) => void
}
/**
@ -71,13 +71,17 @@ export interface ExecuteCommandData extends CommandData {
export type CommandMappings = {
"api-log-messages": CommandData;
focusTree: CommandData,
focusOnTitle: CommandData;
focusOnDetail: CommandData;
focusOnSearchDefinition: Required<CommandData>;
searchNotes: CommandData & {
searchString?: string;
ancestorNoteId?: string | null;
};
closeTocCommand: CommandData;
closeHlt: CommandData;
showLaunchBarSubtree: CommandData;
showRevisions: CommandData;
showOptions: CommandData & {
section: string;
};
@ -103,13 +107,20 @@ export type CommandMappings = {
showPromptDialog: PromptDialogOptions;
showInfoDialog: ConfirmWithMessageOptions;
showConfirmDialog: ConfirmWithMessageOptions;
showRecentChanges: CommandData & { ancestorNoteId: string };
showImportDialog: CommandData & { noteId: string; };
openNewNoteSplit: NoteCommandData;
openInWindow: NoteCommandData;
openNoteInNewTab: CommandData;
openNoteInNewSplit: CommandData;
openNoteInNewWindow: CommandData;
openAboutDialog: CommandData;
hideFloatingButtons: {};
hideLeftPane: CommandData;
showLeftPane: CommandData;
hoistNote: CommandData & { noteId: string };
leaveProtectedSession: CommandData;
enterProtectedSession: CommandData;
openInTab: ContextMenuCommandData;
openNoteInSplit: ContextMenuCommandData;
@ -117,9 +128,12 @@ export type CommandMappings = {
insertNoteAfter: ContextMenuCommandData;
insertChildNote: ContextMenuCommandData;
delete: ContextMenuCommandData;
editNoteTitle: ContextMenuCommandData;
protectSubtree: ContextMenuCommandData;
unprotectSubtree: ContextMenuCommandData;
openBulkActionsDialog: ContextMenuCommandData;
openBulkActionsDialog: ContextMenuCommandData | {
selectedOrActiveNoteIds?: string[]
};
editBranchPrefix: ContextMenuCommandData;
convertNoteToAttachment: ContextMenuCommandData;
duplicateSubtree: ContextMenuCommandData;
@ -138,6 +152,11 @@ export type CommandMappings = {
importIntoNote: ContextMenuCommandData;
exportNote: ContextMenuCommandData;
searchInSubtree: ContextMenuCommandData;
moveNoteUp: ContextMenuCommandData;
moveNoteDown: ContextMenuCommandData;
moveNoteUpInHierarchy: ContextMenuCommandData;
moveNoteDownInHierarchy: ContextMenuCommandData;
selectAllNotesInParent: ContextMenuCommandData;
addNoteLauncher: ContextMenuCommandData;
addScriptLauncher: ContextMenuCommandData;
@ -151,12 +170,16 @@ export type CommandMappings = {
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
};
executeWithTextEditor: CommandData &
ExecuteCommandData & {
ExecuteCommandData<TextEditor> & {
callback?: GetTextEditorCallback;
};
executeWithCodeEditor: CommandData & ExecuteCommandData;
executeWithContentElement: CommandData & ExecuteCommandData;
executeWithTypeWidget: CommandData & ExecuteCommandData;
executeWithCodeEditor: CommandData & ExecuteCommandData<null>;
/**
* Called upon when attempting to retrieve the content element of a {@link NoteContext}.
* Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}.
*/
executeWithContentElement: CommandData & ExecuteCommandData<JQuery<HTMLElement>>;
executeWithTypeWidget: CommandData & ExecuteCommandData<TypeWidget | null>;
addTextToActiveEditor: CommandData & {
text: string;
};
@ -166,6 +189,7 @@ export type CommandMappings = {
importMarkdownInline: CommandData;
showPasswordNotSet: CommandData;
showProtectedSessionPasswordDialog: CommandData;
showUploadAttachmentsDialog: CommandData & { noteId: string };
closeProtectedSessionPasswordDialog: CommandData;
copyImageReferenceToClipboard: CommandData;
copyImageToClipboard: CommandData;
@ -189,6 +213,7 @@ export type CommandMappings = {
screen: Screen;
};
closeTab: CommandData;
closeToc: CommandData;
closeOtherTabs: CommandData;
closeRightTabs: CommandData;
closeAllTabs: CommandData;
@ -202,9 +227,28 @@ export type CommandMappings = {
zoomFactor: string;
}
reEvaluateRightPaneVisibility: CommandData;
runActiveNote: CommandData;
scrollContainerToCommand: CommandData & {
position: number;
};
scrollToEnd: CommandData;
closeThisNoteSplit: CommandData;
moveThisNoteSplit: CommandData & { isMovingLeft: boolean; };
// Geomap
deleteFromMap: { noteId: string },
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
toggleZenMode: CommandData;
updateAttributeList: CommandData & { attributes: Attribute[] };
saveAttributes: CommandData;
reloadAttributes: CommandData;
refreshNoteList: CommandData & { noteId: string; };
refreshResults: {};
refreshSearchDefinition: {};
};
type EventMappings = {
@ -247,7 +291,7 @@ type EventMappings = {
};
noteSwitched: {
noteContext: NoteContext;
notePath: string | null;
notePath?: string | null;
};
noteSwitchedAndActivatedEvent: {
noteContext: NoteContext;
@ -262,9 +306,16 @@ type EventMappings = {
reEvaluateHighlightsListWidgetVisibility: {
noteId: string | undefined;
};
reEvaluateTocWidgetVisibility: {
noteId: string | undefined;
};
showHighlightsListWidget: {
noteId: string;
};
showSearchError: {
error: string;
};
searchRefreshed: { ntxId?: string | null };
hoistedNoteChanged: {
noteId: string;
ntxId: string | null;
@ -279,6 +330,7 @@ type EventMappings = {
noteContextReorderEvent: {
oldMainNtxId: string;
newMainNtxId: string;
ntxIdsInOrder: string[];
};
newNoteContextCreated: {
noteContext: NoteContext;
@ -287,7 +339,7 @@ type EventMappings = {
ntxIds: string[];
};
exportSvg: {
ntxId: string;
ntxId: string | null | undefined;
};
geoMapCreateChildNote: {
ntxId: string | null | undefined; // TODO: deduplicate ntxId
@ -297,7 +349,12 @@ type EventMappings = {
};
refreshNoteList: {
noteId: string;
}
};
showToc: {
noteId: string;
};
noteTypeMimeChanged: { noteId: string };
zenModeChanged: { isEnabled: boolean };
};
export type EventListener<T extends EventNames> = {
@ -335,6 +392,8 @@ class AppContext extends Component {
layout?: Layout;
noteTreeWidget?: NoteTreeWidget;
lastSearchString?: string;
constructor(isMainWindow: boolean) {
super();

View File

@ -80,8 +80,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
return promises.length > 0 ? Promise.all(promises) : null;
}
triggerCommand<K extends CommandNames>(name: string, _data?: CommandMappings[K]): Promise<unknown> | undefined | null {
const data = _data || {};
triggerCommand<K extends CommandNames>(name: K, data?: CommandMappings[K]): Promise<unknown> | undefined | null {
const fun = (this as any)[`${name}Command`];
if (fun) {

View File

@ -9,8 +9,9 @@ import hoistedNoteService from "../services/hoisted_note.js";
import options from "../services/options.js";
import type { ViewScope } from "../services/link.js";
import type FNote from "../entities/fnote.js";
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
interface SetNoteOpts {
export interface SetNoteOpts {
triggerSwitchEvent?: unknown;
viewScope?: ViewScope;
}
@ -288,8 +289,8 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
hasNoteList() {
return (
this.note &&
this.viewScope?.viewMode === "default" &&
this.note.hasChildren() &&
["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "") &&
(this.note.hasChildren() || this.note.getLabelValue("viewType") === "calendar") &&
["book", "text", "code"].includes(this.note.type) &&
this.note.mime !== "text/x-sqlite;schema=trilium" &&
!this.note.isLabelTruthy("hideChildrenOverview")
@ -319,6 +320,15 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
);
}
/**
* Returns a promise which will retrieve the JQuery element of the content of this note context.
*
* Do note that retrieving the content element needs to be handled by the type widget, which is the one which
* provides the content element by listening to the `executeWithContentElement` event. Not all note types support
* this.
*
* If no content could be determined `null` is returned instead.
*/
async getContentElement() {
return this.timeout<JQuery<HTMLElement>>(
new Promise((resolve) =>
@ -332,7 +342,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
async getTypeWidget() {
return this.timeout(
new Promise((resolve) =>
new Promise<TypeWidget | null>((resolve) =>
appContext.triggerCommand("executeWithTypeWidget", {
resolve,
ntxId: this.ntxId

View File

@ -90,6 +90,10 @@ export default class RootCommandExecutor extends Component {
await appContext.tabManager.openTabWithNoteWithHoisting("_backendLog", { activate: true });
}
async showHelpCommand() {
await this.showAndHoistSubtree("_help");
}
async showLaunchBarSubtreeCommand() {
const rootNote = utils.isMobile() ? "_lbMobileRoot" : "_lbRoot";
await this.showAndHoistSubtree(rootNote);
@ -174,6 +178,13 @@ export default class RootCommandExecutor extends Component {
for (const window of windows) window[action]();
}
toggleZenModeCommand() {
const $body = $("body");
$body.toggleClass("zen");
const isEnabled = $body.hasClass("zen");
appContext.triggerEvent("zenModeChanged", { isEnabled });
}
firstTabCommand() {
this.#goToTab(1);
}

View File

@ -10,6 +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";
await appContext.earlyInit();
@ -50,6 +51,7 @@ function initOnElectron() {
const currentWindow = electronRemote.getCurrentWindow();
const style = window.getComputedStyle(document.body);
initDarkOrLightMode(style);
initTransparencyEffects(style, currentWindow);
if (options.get("nativeTitleBarVisible") !== "true") {
@ -91,3 +93,21 @@ function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Elec
}
}
}
/**
* Informs Electron that we prefer a dark or light theme. Apart from changing prefers-color-scheme at CSS level which is a side effect,
* this fixes color issues with background effects or native title bars.
*
* @param style the root CSS element to read variables from.
*/
function initDarkOrLightMode(style: CSSStyleDeclaration) {
let themeSource: typeof nativeTheme.themeSource = "system";
const themeStyle = style.getPropertyValue("--theme-style");
if (style.getPropertyValue("--theme-style-auto") !== "true" && (themeStyle === "light" || themeStyle === "dark")) {
themeSource = themeStyle;
}
const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote;
nativeTheme.themeSource = themeSource;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Custom resource providers</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Custom resource providers</h1>
<div class="ck-content">
<p>A custom resource provider allows any file imported into Trilium (images,
fonts, stylesheets) to be publicly accessible via a URL.</p>
<p>A potential use case for this is to add embed a custom font alongside
a theme.</p>
<h2>Steps for creating a custom resource provider</h2>
<ol>
<li>Import a file such as an image or a font into Trilium by drag &amp; drop.</li>
<li>Select the file and go to the <i>Owned Attributes</i> section.</li>
<li>Add the label <code>#customResourceProvider=hello</code>.</li>
<li>To test if it is working, use a browser to navigate to <code>&lt;protocol&gt;://&lt;host&gt;/custom/hello</code> (where <code>&lt;protocol&gt;</code> is
either <code>http</code> or <code>https</code> based on your setup, and <code>&lt;host&gt;</code> is
the host or IP to your Trilium server instance). If you are running the
TriliumNext application without a server, use <code>http://localhost:37840</code> as
the base URL.</li>
<li>If everything went well, at the previous step the browser should have
downloaded the file uploaded in the first step.</li>
</ol>
<p>Instead of <code>hello</code>, the name can be:</p>
<ul>
<li>A path, such as <code>fonts/Roboto.ttf</code>, which would be accessible
via <code>&lt;host&gt;/custom/fonts/Roboto.ttf</code>.</li>
<li>As a more advanced use case, a regular expression to match multiple routes,
such as <code>hello/.*</code> which will be accessible via <code>/custom/hello/1</code>, <code>/custom/hello/2</code>, <code>/custom/hello/world</code>,
etc.</li>
</ul>
<h2>Using it in a theme</h2>
<p>For example, if you have a custom font to be imported by the theme, first
upload a font file into Trilium and assign it the <code>#customResourceProvider=fonts/myfont.ttf</code> attribute.</p>
<p>Then modify the theme CSS to point to:</p><pre><code class="language-text-css">@font-face {
font-family: customFont;
src: url("/custom/fonts/myfont.ttf");
}
div {
font-family: customFont;
}</code></pre>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -0,0 +1,55 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Export as PDF</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Export as PDF</h1>
<div class="ck-content">
<figure class="image image-style-align-right image_resized" style="width:50.63%;">
<img style="aspect-ratio:951/432;" src="Export as PDF_image.png" width="951"
height="432">
<figcaption>Screenshot of the note contextual menu indicating the “Export as PDF”
option.</figcaption>
</figure>
<p>On the desktop application of Trilium it is possible to export a note
as PDF. On the server or PWA (mobile), the option is not available due
to technical constraints and it will be hidden.</p>
<p>To print a note, select the
<img src="2_Export as PDF_image.png" width="29"
height="31">button to the right of the note and select <i>Export as PDF</i>.</p>
<p>Afterwards you will be prompted to select where to save the PDF file.
Upon confirmation, the resulting PDF will be opened automatically using
the default/system application configured for PDFs.</p>
<p>Should you encounter any visual issues in the resulting PDF file (e.g.
a table does not fit properly, there is cut off text, etc.) feel free to
<a
href="#root/OeKBfN6JbMIq/jRV1MPt4mNSP/hrC6xn7hnDq5">report the issue</a>. In this case, it's best to offer a sample note (click
on the
<img src="2_Export as PDF_image.png" width="29" height="31">button, select Export note → This note and all of its descendants → HTML
in ZIP archive). Make sure not to accidentally leak any personal information.</p>
<h2>Landscape mode</h2>
<p>When exporting to PDF, there are no customizable settings such as page
orientation, size, etc. However, it is possible to specify a given note
to be printed as a PDF in landscape mode by adding the <code>#printLandscape</code> attribute
to it (see&nbsp;<a class="reference-link" href="#root/9QRytp0ZYFIf/PnO38wN0ffOA">Adding an attribute to a note</a>).</p>
<h2>Page size</h2>
<p>By default, the resulting PDF will be in Letter format. It is possible
to adjust it to another page size via the <code>#printPageSize</code> attribute,
with one of the following values: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.</p>
<h2>Keyboard shortcut</h2>
<p>It's possible to trigger the export to PDF from the keyboard by going
to&nbsp;<i>Keyboard shortcuts</i>&nbsp;and assigning a key combination
for the <code>exportAsPdf</code> action.</p>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -0,0 +1,73 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Zen mode</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Zen mode</h1>
<div class="ck-content">
<figure class="image image-style-align-center image_resized" style="width:62.15%;">
<img style="aspect-ratio:855/677;" src="5_Zen mode_image.png" width="855"
height="677">
<figcaption>Screenshot of Zen Mode activated on a Windows 11 system with native title
bar off and background effects on.</figcaption>
</figure>
<p>When Zen Mode is activated (pictured on the side), most of the user interface
of Trilium is hidden away in order to be able to focus on the content,
whether it's for reading or writing.</p>
<figure class="image image-style-align-right image_resized"
style="width:17.65%;">
<img style="aspect-ratio:265/386;" src="3_Zen mode_image.png" width="265"
height="386">
<figcaption>Screenshot of the Zen Mode option in the global menu.</figcaption>
</figure>
<h2>Activating &amp; deactivating</h2>
<p>The Zen Mode can be activated by accessing the global menu and selecting
the “Zen Mode” option:</p>
<p>Aside from the global menu, it's also possible to activate this mode by
using a keyboard shortcut which is Alt+Z by default. Look for <code>toggleZenMode</code> in
the shortcut configuration.</p>
<p>Once Zen Mode is activated, all the UI elements of the application will
be hidden away, including the global menu. In that case, the Zen Mode can
be deactivated either by pressing the
<img src="6_Zen mode_image.png" width="29"
height="31">icon in the top-right corner of the window or by pressing the keyboard
combination again.</p>
<p>Do note that, by design, activating or deactivating the Zen Mode applies
only to the current window. Restarting the application will also disable
the Zen Mode.</p>
<h2>Moving the window around</h2>
<p>If “Native title bar” is activated, then the operating system's default
title bar can be used to drag the window around. If deactivated, the window
can still be moved by dragging the mouse across the top part of the window
where the note titles are.</p>
<figure class="image image-style-align-left image_resized"
style="width:50%;">
<img style="aspect-ratio:1060/707;" src="7_Zen mode_image.png" width="1060"
height="707">
<figcaption>Screenshot of two notes side-by-side while Zen Mode is active, on Windows
11 with background effects off.</figcaption>
</figure>
<h2>Split windows and tabs</h2>
<p>Tabs are completely hidden, however it's still possible to use keyboard
shortcuts such as <code>firstTab</code> (Ctrl+1 by default), <code>secondTab</code> (Ctrl+2
by default). There are also some newer shortcuts such as <code>activateNextTab</code> (Ctrl+Tab)
or <code>activatePreviousTab</code> (Ctrl+Shift+Tab) that allow easy navigation,
however make sure that they are configured properly in the settings.</p>
<p>For the split view of notes, there are no keyboard shortcuts at the time
of writing, but it's still possible to have them in Zen Mode by creating
the split while the Zen Mode is off and then reactivating it afterwards.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

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