Merge remote-tracking branch 'origin/develop' into feature/better_sidebar

This commit is contained in:
Elian Doran 2025-03-16 16:53:08 +02:00
commit 8f900ccf65
No known key found for this signature in database
1185 changed files with 53995 additions and 120813 deletions

View File

@ -1,10 +1,42 @@
.git
.idea
# ignored Files
.dockerignore
.editorconfig
.git*
.prettier*
electron*
entitlements.plist
nodemon.json
renovate.json
trilium.iml
Dockerfile
Dockerfile.*
npm-debug.log
/src/**/*.spec.ts
# ignored folders
/.cache
/.git
/.github
/.idea
/.vscode
/bin
/build
/dist
/docs
/npm-debug.log
node_modules
/dump-db
/e2e
/integration-tests
/spec
/test
/test-etapi
/node_modules
src/**/*.ts
!src/services/asset_path.ts
# exceptions
!/bin/copy-dist.ts
# temporary exception to make copy-dist inside Docker build not fail
# TriliumNextTODO: make copy-dist *not* requiring to copy this file for builds other than electron-forge
!forge.config.cjs
!/bin/tpl
!/bin/electron-forge/desktop.ejs

View File

@ -15,3 +15,9 @@ indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.yml]
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

1
.gitattributes vendored
View File

@ -1,3 +1,4 @@
package-lock.json linguist-generated=true
**/package-lock.json linguist-generated=true
src/public/app/doc_notes/en/User[[:space:]]Guide/** linguist-generated=true
libraries/** linguist-vendored

View File

@ -1,7 +1,6 @@
name: Bug Report
description: Report a bug
title: "(Bug report) "
labels: "Type: Bug"
type: "Bug"
body:
- type: textarea
attributes:

View File

@ -1,12 +1,11 @@
name: Feature Request
description: Ask for a new feature to be added
title: "(Feature request) "
labels: "Type: Enhancement"
type: "Feature"
body:
- type: textarea
attributes:
label: Describe feature
description: A clear and concise description of what you want to be added..
description: A clear and concise description of what you want to be added.
validations:
required: true
- type: textarea

10
.github/ISSUE_TEMPLATE/task.yml vendored Normal file
View File

@ -0,0 +1,10 @@
name: Task
description: Create a new Task
type: "Task"
body:
- type: textarea
attributes:
label: Describe Task
description: A clear and concise description of what the task is about.
validations:
required: true

View File

@ -0,0 +1,213 @@
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"
required: true
arch:
description: "The architecture to build for: x64, arm64"
required: true
extension:
description: "Platform specific extensions to copy in the output: dmg, deb, rpm, exe, zip"
required: true
runs:
using: composite
steps:
# Certificate setup
- name: Import Apple certificates
if: inputs.os == 'macos'
uses: apple-actions/import-codesign-certs@v3
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@v3
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
brew install create-dmg
- name: Install dependencies for RPM and Flatpak package building
if: ${{ inputs.os == 'linux' }}
shell: bash
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 chore:update-build-info
# Critical debugging configuration
- name: Run electron-forge build with enhanced logging
shell: bash
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 ./dist -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 ./dist -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 ./dist -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 ./dist -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
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 ./dist -name "*.dmg" -print -quit)
if [ -n "$dmg_file" ]; then
echo "Found DMG: $dmg_file"
cp "$dmg_file" "upload/TriliumNextNotes-${{ github.ref_name }}-macos-${{ inputs.arch }}.dmg"
else
echo "Warning: No DMG file found"
fi
# Look for ZIP files recursively
echo "Looking for ZIP files..."
zip_file=$(find ./dist -name "*.zip" -print -quit)
if [ -n "$zip_file" ]; then
echo "Found ZIP: $zip_file"
cp "$zip_file" "upload/TriliumNextNotes-${{ github.ref_name }}-macos-${{ 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 ./dist -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/

31
.github/actions/build-server/action.yml vendored Normal file
View File

@ -0,0 +1,31 @@
inputs:
os:
description: "One of the supported platforms: windows"
required: true
arch:
description: "The architecture to build for: x64, arm64"
required: true
runs:
using: composite
steps:
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
shell: bash
run: npm ci
- name: Run Linux server build
env:
MATRIX_ARCH: ${{ inputs.arch }}
shell: bash
run: |
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-Server-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}.tar.xz"

View File

@ -1,9 +1,9 @@
name: Dev
on:
push:
branches-ignore:
- 'develop'
- 'feature/update**'
branches: [ develop ]
pull_request:
branches: [ develop ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -33,6 +33,10 @@ jobs:
- name: Run the TypeScript build
run: npx tsc
- name: Run the unit tests
run: npm run test
build_docker:
name: Build Docker image
runs-on: ubuntu-latest
@ -40,22 +44,12 @@ jobs:
- test_dev
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
cache-from: type=gha
cache-to: type=gha,mode=max
cache-to: type=gha,mode=max
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
@ -69,7 +63,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
@ -78,20 +72,6 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Build and export to Docker
uses: docker/build-push-action@v6
with:
@ -101,12 +81,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 --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:

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,18 +47,15 @@ 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
- name: Build and export to Docker
uses: docker/build-push-action@v6
@ -69,12 +66,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 +79,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 +97,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 +118,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,24 +144,12 @@ 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
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Login to GHCR
uses: docker/login-action@v3
@ -169,14 +157,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 +174,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 +208,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 +225,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 +243,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 +255,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

@ -7,7 +7,7 @@ on:
paths-ignore:
- "docs/**"
- ".github/workflows/main-docker.yml"
workflow_dispatch:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -23,13 +23,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
@ -37,31 +37,27 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Python for appdmg to be installed
if: ${{ matrix.os.name == 'macos' }}
run: brew install python-setuptools
- name: Install dependencies
run: npm ci
- name: Update build info
run: npm run update-build-info
- name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix)
if: runner.os != 'windows'
- name: Run the build
uses: ./.github/actions/build-electron
with:
os: ${{ matrix.os.name }}
arch: ${{ matrix.arch }}
extension: ${{ 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 }}
# Clean up keychain after build
- name: Clean up keychain
if: matrix.os.name == 'macos' && always()
run: |
mkdir -p upload
file=$(find out/make -name '*.zip' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-${{ github.ref_name }}.zip"
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-${{ github.ref_name }}.${{ matrix.os.extension }}"
- name: Prepare artifacts (Windows)
if: runner.os == 'windows'
run: |
mkdir upload
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-${{ github.ref_name }}.zip"
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-${{ github.ref_name }}.${{ matrix.os.extension }}"
security delete-keychain build.keychain
- name: Publish artifacts
uses: actions/upload-artifact@v4
with:
@ -72,6 +68,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:
@ -86,24 +83,10 @@ jobs:
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
- name: Run the build
uses: ./.github/actions/build-server
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Linux server build
env:
MATRIX_ARCH: ${{ matrix.arch }}
run: |
npm run update-build-info
./bin/build-server.sh
- name: Prepare artifacts
run: |
mkdir -p upload
file=$(find dist -name '*.tar.xz' -print -quit)
cp "$file" "upload/TriliumNextNotes-linux-${{ matrix.arch }}-${{ github.ref_name }}.tar.xz"
arch: ${{ matrix.arch }}
- uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes linux server ${{ matrix.arch }}

View File

@ -2,7 +2,7 @@ name: Nightly Release
on:
# This can be used to automatically publish nightlies at UTC nighttime
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
- cron: "0 2 * * *" # run at 2 AM UTC
# This can be used to allow manually triggering nightlies from the web interface
workflow_dispatch:
env:
@ -12,7 +12,7 @@ permissions:
contents: write
jobs:
nightly-electron:
name: Deploy nightly
name: Deploy nightly
strategy:
fail-fast: false
matrix:
@ -20,110 +20,79 @@ 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
runs-on: ${{ matrix.os.image }}
extension: [exe, zip]
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Python for appdmg to be installed
if: ${{ matrix.os.name == 'macos' }}
run: brew install python-setuptools
- name: Install dependencies
shell: bash
run: npm ci
- name: Update build info
run: npm run update-build-info
- name: Update nightly version
run: npm run ci-update-nightly-version
- name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix)
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find out/make -name '*.zip' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Prepare artifacts (Windows)
if: runner.os == 'windows'
run: |
mkdir upload
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Publish artifacts
uses: actions/upload-artifact@v4
run: npm run chore:ci-update-nightly-version
- name: Run the build
uses: ./.github/actions/build-electron
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.zip
overwrite: true
- name: Publish installer artifacts
uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.${{ matrix.os.extension }}
overwrite: true
os: ${{ matrix.os.name }}
arch: ${{ matrix.arch }}
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: 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-${{ matrix.os.name }}-${{ matrix.arch }}.zip # path to archive to upload
asset_name: TriliumNextNotes-${{ 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
- 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-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }} # path to archive to upload
asset_name: TriliumNextNotes-${{ 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
make_latest: false
prerelease: true
draft: false
fail_on_unmatched_files: true
files: upload/*.*
tag_name: nightly
name: Nightly Build
nightly-server:
name: Deploy server nightly
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [x64, arm64]
include:
- arch: x64
runs-on: ubuntu-latest
- arch: arm64
runs-on: ubuntu-24.04-arm
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Linux server build (x86_64)
run: |
npm run update-build-info
npm run ci-update-nightly-version
./bin/build-server.sh
- name: Prepare artifacts
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find dist -name '*.tar.xz' -print -quit)
cp "$file" "upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz"
- uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes linux server x64
path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz
overwrite: true
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.2.0
- name: Run the build
uses: ./.github/actions/build-server
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz # path to archive to upload
asset_name: TriliumNextNotes-linux-x64-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
os: linux
arch: ${{ matrix.arch }}
- name: Publish release
uses: softprops/action-gh-release@v2
with:
make_latest: false
prerelease: true
draft: false
fail_on_unmatched_files: true
files: upload/*.*
tag_name: nightly
name: Nightly Build

View File

@ -3,7 +3,7 @@ on:
push:
tags:
- "v*"
workflow_dispatch:
workflow_dispatch:
permissions:
contents: write
concurrency:
@ -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
@ -34,62 +34,52 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Python for appdmg to be installed
if: ${{ matrix.os.name == 'macos' }}
run: brew install python-setuptools
- name: Install dependencies
run: npm ci
- name: Update build info
run: npm run update-build-info
- name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix)
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find out/make -name '*.zip' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Prepare artifacts (Windows)
if: runner.os == 'windows'
run: |
mkdir upload
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Run the build
uses: ./.github/actions/build-electron
with:
os: ${{ matrix.os.name }}
arch: ${{ matrix.arch }}
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 x86_64
runs-on: ubuntu-latest
name: Build Linux Server
strategy:
fail-fast: false
matrix:
arch: [x64, arm64]
include:
- arch: x64
runs-on: ubuntu-latest
- arch: arm64
runs-on: ubuntu-24.04-arm
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
- name: Run the build
uses: ./.github/actions/build-server
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Linux server build (x86_64)
run: |
npm run update-build-info
./bin/build-server.sh
- name: Prepare artifacts
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find dist -name '*.tar.xz' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-server-linux-x64.tar.xz"
os: linux
arch: ${{ matrix.arch }}
- name: Publish release
uses: softprops/action-gh-release@v2
with:
draft: true
fail_on_unmatched_files: true
files: upload/*.*
files: upload/*.*

11
.gitignore vendored
View File

@ -1,3 +1,4 @@
.cache
.DS_Store
node_modules/
dist/
@ -7,8 +8,10 @@ src/public/app-dist/
npm-debug.log
yarn-error.log
po-*/
.flatpak-builder/
*.db
!test/**/*.db
!integration-tests/db/document.db
!integration-tests/db/config.ini
integration-tests/db/log
@ -33,4 +36,10 @@ images/app-icons/mac/*.png
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
/playwright/.auth/
data-docs/backup
data-docs/log
data-docs/session
data-docs/session_secret.txt
data-docs/document.*

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

1
.npmrc
View File

@ -1,2 +1 @@
save-prefix = ''
legacy-peer-deps = true

View File

@ -2,4 +2,5 @@
*.md
*.yml
libraries/*
docs/*
docs/*
src/public/app/doc_notes/**/*

View File

@ -1,3 +1,7 @@
{
"recommendations": ["lokalise.i18n-ally", "editorconfig.editorconfig"]
"recommendations": [
"lokalise.i18n-ally",
"editorconfig.editorconfig",
"vitest.explorer"
]
}

View File

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

View File

@ -1,63 +1,46 @@
# Build stage
FROM node:22.13.0-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 \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
WORKDIR /usr/src/app/build
# Copy only necessary files for build
COPY . .
COPY server-package.json package.json
# Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm docker_healthcheck.ts && \
npm install && \
npm run webpack && \
npm prune --omit=dev && \
RUN npm ci && \
npm run build:prepare-dist && \
npm cache clean --force && \
cp src/public/app/share.js src/public/app-dist/. && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \
rm -rf src/public/app/* && \
mkdir -p src/public/app/services && \
cp -r build/src/public/app/services/mime_type_definitions.js src/public/app/services/mime_type_definitions.js && \
rm src/services/asset_path.ts && \
rm -r build
rm -rf build/node_modules && \
mv build/* \
start-docker.sh \
/usr/src/app/ && \
rm -rf \
/usr/src/app/build \
/tmp/node-compile-cache
#TODO: improve node_modules handling in copy-dist/Dockerfile -> remove duplicated work
# currently copy-dist will copy certain node_module folders, but in the Dockerfile we delete them again (to keep image size down),
# as we install necessary dependencies in runtime buildstage anyways
# Runtime stage
FROM node:22.13.0-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \
&& rm -rf /var/lib/apt/lists/* && \
rm -rf /var/cache/apt/*
FROM node:22.14.0-bullseye-slim
WORKDIR /usr/src/app
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
# Install only runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gosu && \
rm -rf \
/var/lib/apt/lists/* \
/var/cache/apt/*
COPY --from=builder /usr/src/app ./
RUN sed -i "/electron/d" package.json && \
npm ci --omit=dev && \
npm cache clean --force && \
rm -rf /tmp/node-compile-cache
# Configure container
EXPOSE 8080

View File

@ -1,59 +1,41 @@
# Build stage
FROM node:22.13.0-alpine AS builder
FROM node:22.14.0-alpine AS builder
# Configure build dependencies
RUN apk add --no-cache --virtual .build-dependencies \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3
WORKDIR /usr/src/app
WORKDIR /usr/src/app/build
# Copy only necessary files for build
COPY . .
COPY server-package.json package.json
# Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm docker_healthcheck.ts && \
npm install && \
npm run webpack && \
npm prune --omit=dev && \
RUN npm ci && \
npm run build:prepare-dist && \
npm cache clean --force && \
cp src/public/app/share.js src/public/app-dist/. && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \
rm -rf src/public/app && \
mkdir -p src/public/app/services && \
cp -r build/src/public/app/services/mime_type_definitions.js src/public/app/services/mime_type_definitions.js && \
rm src/services/asset_path.ts && \
rm -r build
rm -rf build/node_modules && \
mv build/* \
start-docker.sh \
/usr/src/app/ && \
rm -rf \
/usr/src/app/build \
/tmp/node-compile-cache
#TODO: improve node_modules handling in copy-dist/Dockerfile -> remove duplicated work
# currently copy-dist will copy certain node_module folders, but in the Dockerfile we delete them again (to keep image size down),
# as we install necessary dependencies in runtime buildstage anyways
# Runtime stage
FROM node:22.13.0-alpine
FROM node:22.14.0-alpine
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow
WORKDIR /usr/src/app
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
COPY --from=builder /usr/src/app ./
RUN sed -i "/electron/d" package.json && \
npm ci --omit=dev && \
npm cache clean --force && \
rm -rf /tmp/node-compile-cache
# Add application user
RUN adduser -s /bin/false node; exit 0

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

@ -5,11 +5,6 @@ set -e # Fail on any command error
VERSION=`jq -r ".version" package.json`
SERIES=${VERSION:0:4}-latest
cat package.json | grep -v electron > server-package.json
echo "Compiling typescript..."
npx tsc
sudo docker build -t triliumnext/notes:$VERSION --network host -t triliumnext/notes:$SERIES .
if [[ $VERSION != *"beta"* ]]; then

View File

@ -22,55 +22,42 @@ echo "Selected Arch: $ARCH"
# Set Node.js version and architecture-specific filename
NODE_VERSION=20.15.1
NODE_ARCH=$ARCH
# Debug output
echo "Node arch: $NODE_ARCH"
BUILD_DIR="./build"
DIST_DIR="./dist"
# Special case for x64 in Node.js downloads
if [ "$NODE_ARCH" = "x64" ]; then
NODE_FILENAME="x64"
elif [ "$NODE_ARCH" = "arm64" ]; then
NODE_FILENAME="arm64"
fi
./bin/copy-trilium.sh
# Debug output
echo "Node filename: $NODE_FILENAME"
NODE_FILENAME=node-v${NODE_VERSION}-linux-${ARCH}
PKG_DIR=dist/trilium-linux-${ARCH}-server
echo "Package directory: $PKG_DIR"
if [ "$1" != "DONTCOPY" ]
then
# Need to modify copy-trilium.sh to accept the target directory
./bin/copy-trilium.sh "$PKG_DIR"
fi
cd dist
wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_FILENAME}.tar.xz
tar xfJ node-v${NODE_VERSION}-linux-${NODE_FILENAME}.tar.xz
rm node-v${NODE_VERSION}-linux-${NODE_FILENAME}.tar.xz
echo "Downloading Node.js runtime $NODE_FILENAME..."
cd $BUILD_DIR
wget -qO- https://nodejs.org/dist/v${NODE_VERSION}/${NODE_FILENAME}.tar.xz | tar xfJ -
mv $NODE_FILENAME node
cd ..
mv dist/node-v${NODE_VERSION}-linux-${NODE_FILENAME} $PKG_DIR/node
rm -r $PKG_DIR/node/lib/node_modules/npm
rm -r $PKG_DIR/node/include/node
rm -r $BUILD_DIR/node/lib/node_modules/npm \
$BUILD_DIR/node/include/node \
$BUILD_DIR/node_modules/electron* \
$BUILD_DIR/electron*.{js,map}
rm -r $PKG_DIR/node_modules/electron*
rm -r $PKG_DIR/electron*.js
printf "#!/bin/sh\n./node/bin/node src/main" > $BUILD_DIR/trilium.sh
chmod 755 $BUILD_DIR/trilium.sh
printf "#!/bin/sh\n./node/bin/node src/main" > $PKG_DIR/trilium.sh
chmod 755 $PKG_DIR/trilium.sh
cp bin/tpl/anonymize-database.sql $PKG_DIR/
cp -r translations $PKG_DIR/
cp -r dump-db $PKG_DIR/
rm -rf $PKG_DIR/dump-db/node_modules
# TriliumNextTODO: is this still required? If yes → move to copy-dist/copy-trilium
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
VERSION=`jq -r ".version" package.json`
cd dist
tar cJf trilium-linux-${ARCH}-server-${VERSION}.tar.xz trilium-linux-${ARCH}-server
ARCHIVE_NAME="TriliumNextNotes-Server-${VERSION}-linux-${ARCH}"
echo "Creating Archive $ARCHIVE_NAME..."
mkdir $DIST_DIR
cp -r "$BUILD_DIR" "$DIST_DIR/$ARCHIVE_NAME"
cd $DIST_DIR
tar cJf "$ARCHIVE_NAME.tar.xz" "$ARCHIVE_NAME"
rm -rf "$ARCHIVE_NAME"
echo "Server Build Completed!"

View File

@ -1,53 +0,0 @@
#!/usr/bin/env bash
set -e # Fail on any command error
if ! command -v jq &> /dev/null; then
echo "Missing command: jq"
exit 1
fi
if ! command -v fakeroot &> /dev/null; then
echo "Missing command: fakeroot"
exit 1
fi
if ! command -v dpkg-deb &> /dev/null; then
echo "Missing command: dpkg-deb"
exit 1
fi
if dpkg-deb 2>&1 | grep BusyBox &> /dev/null; then
echo "The dpkg-deb binary provided by BusyBox is not compatible. The Debian tool needs to be used instead."
exit 1
fi
if ! command -v wine &> /dev/null; then
echo "Missing command: wine"
exit 1
fi
echo "Deleting existing builds"
rm -rf dist/*
SRC_DIR=dist/trilium-src
bin/copy-trilium.sh $SRC_DIR
# we'll just copy the same SRC dir to all the builds so we don't have to do npm install in each separately
cp -r $SRC_DIR ./dist/trilium-linux-x64-src
cp -r $SRC_DIR ./dist/trilium-linux-x64-server
cp -r $SRC_DIR ./dist/trilium-windows-x64-src
cp -r $SRC_DIR ./dist/trilium-mac-x64-src
cp -r $SRC_DIR ./dist/trilium-mac-arm64-src
bin/build-win-x64.sh DONTCOPY
bin/build-mac-x64.sh DONTCOPY
bin/build-mac-arm64.sh DONTCOPY
bin/build-linux-x64.sh DONTCOPY
bin/build-server.sh DONTCOPY

View File

@ -1,50 +1,52 @@
import fs from "fs-extra";
import path from "path";
const DEST_DIR = "./dist";
const DEST_DIR_SRC = path.join(DEST_DIR, "src");
const DEST_DIR_NODE_MODULES = path.join(DEST_DIR, "node_modules");
const DEST_DIR = "./build";
const VERBOSE = process.env.VERBOSE;
function log(...args) {
function log(...args: any[]) {
if (VERBOSE) {
console.log(args);
console.log(...args);
}
}
async function copyNodeModuleFileOrFolder(source: string) {
const adjustedSource = source.substring(13);
const destination = path.join(DEST_DIR_NODE_MODULES, adjustedSource);
function copyNodeModuleFileOrFolder(source: string) {
const destination = path.join(DEST_DIR, source);
log(`Copying ${source} to ${destination}`);
await fs.ensureDir(path.dirname(destination));
await fs.copy(source, destination);
fs.ensureDirSync(path.dirname(destination));
fs.copySync(source, destination);
}
const copy = async () => {
for (const srcFile of fs.readdirSync("build")) {
const destFile = path.join(DEST_DIR, path.basename(srcFile));
log(`Copying source ${srcFile} -> ${destFile}.`);
fs.copySync(path.join("build", srcFile), destFile, { recursive: true });
}
try {
const filesToCopy = ["config-sample.ini", "tsconfig.webpack.json"];
for (const file of filesToCopy) {
log(`Copying ${file}`);
await fs.copy(file, path.join(DEST_DIR, file));
}
const assetsToCopy = new Set([
"./images",
"./libraries",
"./translations",
"./db",
"./config-sample.ini",
"./package-lock.json",
"./package.json",
"./LICENSE",
"./README.md",
"./forge.config.cjs",
"./bin/tpl/",
"./bin/electron-forge/desktop.ejs",
"./src/views/",
"./src/etapi/etapi.openapi.yaml",
"./src/routes/api/openapi.json",
"./src/public/icon.png",
"./src/public/manifest.webmanifest",
"./src/public/robots.txt",
"./src/public/fonts",
"./src/public/stylesheets",
"./src/public/translations"
]);
const dirsToCopy = ["images", "libraries", "translations", "db"];
for (const dir of dirsToCopy) {
log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR, dir));
}
const srcDirsToCopy = ["./src/public", "./src/views", "./build"];
for (const dir of srcDirsToCopy) {
log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir)));
for (const asset of assetsToCopy) {
log(`Copying ${asset}`);
fs.copySync(asset, path.join(DEST_DIR, asset));
}
/**
@ -53,10 +55,10 @@ const copy = async () => {
const publicDirsToCopy = ["./src/public/app/doc_notes"];
const PUBLIC_DIR = path.join(DEST_DIR, "src", "public", "app-dist");
for (const dir of publicDirsToCopy) {
await fs.copy(dir, path.join(PUBLIC_DIR, path.basename(dir)));
fs.copySync(dir, path.join(PUBLIC_DIR, path.basename(dir)));
}
const nodeModulesFile = [
const nodeModulesFile = new Set([
"node_modules/react/umd/react.production.min.js",
"node_modules/react/umd/react.development.js",
"node_modules/react-dom/umd/react-dom.production.min.js",
@ -66,14 +68,10 @@ const copy = async () => {
"node_modules/katex/dist/contrib/auto-render.min.js",
"node_modules/@highlightjs/cdn-assets/highlight.min.js",
"node_modules/@mind-elixir/node-menu/dist/node-menu.umd.cjs"
];
]);
for (const file of nodeModulesFile) {
await copyNodeModuleFileOrFolder(file);
}
const nodeModulesFolder = [
"node_modules/@excalidraw/excalidraw/dist/",
const nodeModulesFolder = new Set([
"node_modules/@excalidraw/excalidraw/dist/prod/fonts/",
"node_modules/katex/dist/",
"node_modules/dayjs/",
"node_modules/boxicons/css/",
@ -81,7 +79,6 @@ const copy = async () => {
"node_modules/mermaid/dist/",
"node_modules/jquery/dist/",
"node_modules/jquery-hotkeys/",
"node_modules/print-this/",
"node_modules/split.js/dist/",
"node_modules/panzoom/dist/",
"node_modules/i18next/",
@ -89,10 +86,8 @@ const copy = async () => {
"node_modules/jsplumb/dist/",
"node_modules/vanilla-js-wheel-zoom/dist/",
"node_modules/mark.js/dist/",
"node_modules/knockout/build/output/",
"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/",
@ -100,14 +95,18 @@ const copy = async () => {
"node_modules/codemirror/keymap/",
"node_modules/mind-elixir/dist/",
"node_modules/@highlightjs/cdn-assets/languages",
"node_modules/@highlightjs/cdn-assets/styles"
];
"node_modules/@highlightjs/cdn-assets/styles",
"node_modules/leaflet/dist"
]);
for (const folder of nodeModulesFolder) {
await copyNodeModuleFileOrFolder(folder);
for (const nodeModuleItem of [...nodeModulesFile, ...nodeModulesFolder]) {
copyNodeModuleFileOrFolder(nodeModuleItem);
}
};
console.log("Copying complete!")
copy()
.then(() => console.log("Copying complete!"))
.catch((err) => console.error("Error during copy:", err));
} catch(err) {
console.error("Error during copy:", err)
process.exit(1)
}

View File

@ -3,73 +3,42 @@
set -e # Fail on any command error
shopt -s globstar
if [[ $# -eq 0 ]] ; then
echo "Missing argument of target directory"
exit 1
fi
BUILD_DIR="./build"
if ! [[ $(which npm) ]]; then
echo "Missing npm"
exit 1
fi
# Trigger the TypeScript build
echo TypeScript build start
npx tsc
echo TypeScript build finished
# Copy the TypeScript artifacts
DIR="$1"
rm -rf "$DIR"
mkdir -pv "$DIR"
echo Webpack start
npm run webpack
echo Webpack finish
echo "Copying Trilium to build directory $DIR"
for d in 'images' 'libraries' 'src' 'db'; do
cp -r "$d" "$DIR"/
done
for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample.ini'; do
cp "$f" "$DIR"/
done
# Trigger the build
echo Build start
npm run build:prepare-dist
echo Build finished
# Patch package.json main
sed -i 's/.\/dist\/electron-main.js/electron-main.js/g' "$DIR/package.json"
script_dir=$(realpath $(dirname $0))
cp -R "$script_dir/../build/src" "$DIR"
cp "$script_dir/../build/electron-main.js" "$DIR"
sed -i 's|./dist/electron-main.js|electron-main.js|g' "$BUILD_DIR/package.json"
# run in subshell (so we return to original dir)
(cd $DIR && npm install --omit=dev --legacy-peer-deps)
(cd $BUILD_DIR && npm ci --omit=dev)
if [[ -d "$DIR"/node_modules ]]; then
if [[ -d "$BUILD_DIR"/node_modules ]]; then
# cleanup of useless files in dependencies
for d in 'image-q/demo' \
'@excalidraw/excalidraw/dist/excalidraw-assets-dev' '@excalidraw/excalidraw/dist/excalidraw.development.js' '@excalidraw/excalidraw/dist/excalidraw-with-preact.development.js' \
'mermaid/dist/mermaid.js' \
'boxicons/svg' 'boxicons/node_modules/react'/* \
'@jimp/plugin-print/fonts' 'jimp/browser' 'jimp/fonts'; do
[[ -e "$DIR"/node_modules/"$d" ]] && rm -r "$DIR"/node_modules/"$d"
[[ -e "$BUILD_DIR"/node_modules/"$d" ]] && rm -r "$BUILD_DIR"/node_modules/"$d"
done
# delete all tests (there are often large images as test file for jimp etc.)
for d in 'test' 'docs' 'demo' 'example'; do
find "$DIR"/node_modules -name "$d" -exec rm -rf {} +
find "$BUILD_DIR"/node_modules -name "$d" -exec rm -rf {} +
done
fi
find $DIR/libraries -name "*.map" -type f -delete
find $DIR/node_modules -name "*.map" -type f -delete
find $DIR -name "*.ts" -type f -delete
find $BUILD_DIR/libraries -name "*.map" -type f -delete
find $BUILD_DIR/node_modules -name "*.map" -type f -delete
find $BUILD_DIR -name "*.ts" -type f -delete
d="$DIR"/src/public
[[ -d "$d"/app-dist ]] || mkdir -pv "$d"/app-dist
cp "$d"/app/share.js "$d"/app-dist/
cp -r "$d"/app/doc_notes "$d"/app-dist/
rm -rf "$d"/app
unset f d DIR
unset f d BUILD_DIR

View File

@ -22,6 +22,10 @@ inkscape -w 180 -h 180 "../icon-color.svg" -o "./ios/apple-touch-icon.png"
# Build PNGs
inkscape -w 128 -h 128 "../icon-color.svg" -o "./png/128x128.png"
inkscape -w 256 -h 256 "../icon-color.svg" -o "./png/256x256.png"
# Build dev icons (including tray)
inkscape -w 16 -h 16 "../icon-purple.svg" -o "./png/16x16-dev.png"
inkscape -w 32 -h 32 "../icon-purple.svg" -o "./png/32x32-dev.png"
inkscape -w 256 -h 256 "../icon-purple.svg" -o "./png/256x256-dev.png"
# Build Mac .icns
@ -41,5 +45,8 @@ icnsutil compose -f "mac/icon.icns" ./mac/*.png
# Build Windows icon
magick -background none "../icon-color.svg" -define icon:auto-resize=16,32,48,64,128,256 "./icon.ico"
# Build Windows setup icon
magick -background none "../icon-installer.svg" -define icon:auto-resize=16,32,48,64,128,256 "./win/setup.ico"
# Build Squirrel splash image
magick "./png/256x256.png" -background "#ffffff" -gravity center -extent 640x480 "./win/setup-banner.gif"

View File

@ -1,12 +1,17 @@
[Desktop Entry]
<% if (productName) { %>Name=<%= productName %>
<% } %><% if (description) { %>Comment=<%= description %>
<% } %><% if (genericName) { %>GenericName=<%= genericName %>
<% } %><% if (name) { %>Exec=<%= name %> %U
Icon=<%= name %>
<% } %>Type=Application
StartupNotify=true
<% if (productName) { %>StartupWMClass=<%= productName %>
<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>;
<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>;
<% } %>
<%=
Object.entries({
"Name": productName,
"Comment": description,
"GenericName": genericName,
"Exec": name ? `${name} %U` : undefined,
"Icon": name,
"Type": "Application",
"StartupNotify": "true",
"StartupWMClass": productName,
"Categories": categories?.length ? `${categories.join(";")};` : undefined,
"MimeType": mimeType?.length ? `${mimeType.join(";")};` : undefined
})
.map(line => line[1] ? line.join("=") : undefined)
.filter(line => !!line)
.join("\n")%>

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

@ -0,0 +1,191 @@
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,15 +1,15 @@
/**
* Usage: node src/tools/generate_document.js 1000
* Usage: tsx ./generate_document.ts 1000
* will create 1000 new notes and some clones into the current document.db
*/
import sqlInit from "../services/sql_init.js";
import noteService from "../services/notes.js";
import attributeService from "../services/attributes.js";
import cls from "../services/cls.js";
import cloningService from "../services/cloning.js";
import sqlInit from "../src/services/sql_init.js";
import noteService from "../src/services/notes.js";
import attributeService from "../src/services/attributes.js";
import cls from "../src/services/cls.js";
import cloningService from "../src/services/cloning.js";
import loremIpsum from "lorem-ipsum";
import "../becca/entity_constructor.js";
import "../src/becca/entity_constructor.js";
const noteCount = parseInt(process.argv[2]);
@ -90,4 +90,6 @@ async function start() {
process.exit(0);
}
sqlInit.dbReady.then(cls.wrap(start));
// @TriliumNextTODO sqlInit.dbReady never seems to resolve so program hangs
// see https://github.com/TriliumNext/Notes/issues/1020
sqlInit.dbReady.then(cls.wrap(start)).catch((err) => console.error(err));

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
export GITHUB_REPO=trilium
set -e
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
@ -32,7 +32,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 +40,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

@ -16,7 +16,7 @@ chcp 65001
:: Get Current Trilium executable directory and compute data directory
SET DIR=%~dp0
set NODE_TLS_REJECT_UNAUTHORIZED=0
cd %DIR%
cd "%DIR%"
start trilium.exe
GOTO END

View File

@ -15,8 +15,9 @@ chcp 65001
:: Get Current Trilium executable directory and compute data directory
SET DIR=%~dp0
SET DIR=%DIR:~0,-1%
SET TRILIUM_DATA_DIR=%DIR%\trilium-data
cd %DIR%
cd "%DIR%"
start trilium.exe
GOTO END

View File

@ -16,7 +16,7 @@ chcp 65001
:: Get Current Trilium executable directory and compute data directory
SET DIR=%~dp0
SET TRILIUM_SAFE_MODE=1
cd %DIR%
cd "%DIR%"
start trilium.exe --disable-gpu
GOTO END

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-bookmark"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 7v14l-6 -4l-6 4v-14a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4z" /></svg>

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1,39 @@
#!/usr/bin/env bash
if ! command -v magick &> /dev/null; then
echo "This tool requires ImageMagick to be installed in order to create the icons."
exit 1
fi
if ! command -v inkscape &> /dev/null; then
echo "This tool requires Inkscape to be render sharper SVGs than ImageMagick."
exit 1
fi
script_dir=$(realpath $(dirname $0))
images_dir="$script_dir/../../images"
output_dir="$images_dir/app-icons/tray"
function generateDpiScaledIcons {
file=$1
suffix=$2
name="$(basename $file .svg)$suffix"
inkscape -w 16 -h 16 "$file" -o "$output_dir/$name.png"
inkscape -w 20 -h 20 "$file" -o "$output_dir/$name@1.25x.png"
inkscape -w 24 -h 24 "$file" -o "$output_dir/$name@1.5x.png"
inkscape -w 32 -h 32 "$file" -o "$output_dir/$name@2x.png"
}
generateDpiScaledIcons "$images_dir/icon-black.svg" "Template"
generateDpiScaledIcons "$images_dir/icon-color.svg"
generateDpiScaledIcons "$images_dir/icon-purple.svg"
for file in *.svg; do
name="$(basename $file .svg)Template"
generateDpiScaledIcons "$file" "Template"
magick "$output_dir/$name.png" -channel RGB -negate "$output_dir/$name-inverted.png"
magick "$output_dir/$name@1.25x.png" -channel RGB -negate "$output_dir/$name-inverted@1.25x.png"
magick "$output_dir/$name@1.5x.png" -channel RGB -negate "$output_dir/$name-inverted@1.5x.png"
magick "$output_dir/$name@2x.png" -channel RGB -negate "$output_dir/$name-inverted@2x.png"
done

1
bin/tray-icons/close.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-x"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12" /><path d="M6 6l12 12" /></svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-plus"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5l0 14" /><path d="M5 12l14 0" /></svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-history"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 8l0 4l2 2" /><path d="M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5" /></svg>

After

Width:  |  Height:  |  Size: 387 B

1
bin/tray-icons/today.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-calendar-star"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11 21h-5a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v3.5" /><path d="M16 3v4" /><path d="M8 3v4" /><path d="M4 11h11" /><path d="M17.8 20.817l-2.172 1.138a.392 .392 0 0 1 -.568 -.41l.415 -2.411l-1.757 -1.707a.389 .389 0 0 1 .217 -.665l2.428 -.352l1.086 -2.193a.392 .392 0 0 1 .702 0l1.086 2.193l2.428 .352a.39 .39 0 0 1 .217 .665l-1.757 1.707l.414 2.41a.39 .39 0 0 1 -.567 .411l-2.172 -1.138z" /></svg>

After

Width:  |  Height:  |  Size: 734 B

View File

@ -27,3 +27,23 @@ keyPath=
# once set, expressjs will use the X-Forwarded-For header set by the rev. proxy to determinate the real IPs of clients.
# 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=
#syncServerProxy=

49
data-docs/config.ini Normal file
View File

@ -0,0 +1,49 @@
[General]
# Instance name can be used to distinguish between different instances using backend api.getInstanceName()
instanceName=
# set to true to allow using Trilium without authentication (makes sense for server build only, desktop build doesn't need password)
noAuthentication=false
# set to true to disable backups (e.g. because of limited space on server)
noBackup=false
# Disable automatically generating desktop icon
# noDesktopIcon=true
[Network]
# host setting is relevant only for web deployments - set the host on which the server will listen
# host=0.0.0.0
# port setting is relevant only for web deployments, desktop builds run on a fixed port (changeable with TRILIUM_PORT environment variable)
port=8080
# true for TLS/SSL/HTTPS (secure), false for HTTP (insecure).
https=false
# path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
certPath=
keyPath=
# setting to give trust to reverse proxies, a comma-separated list of trusted rev. proxy IPs can be specified (CIDR notation is permitted),
# alternatively 'true' will make use of the leftmost IP in X-Forwarded-For, ultimately an integer can be used to tell about the number of hops between
# Trilium (which is hop 0) and the first trusted rev. proxy.
# once set, expressjs will use the X-Forwarded-For header set by the rev. proxy to determinate the real IPs of clients.
# 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=
#syncServerProxy=

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,11 +1,24 @@
module.exports = () => {
const sql = require("../../src/services/sql");
const utils = require("../../src/services/utils");
import sql from "../../src/services/sql";
import utils from "../../src/services/utils";
interface NoteContentsRow {
noteId: string;
content: string | Buffer;
dateModified: string;
utcDateModified: string;
}
interface NoteRevisionContents {
noteRevisionId: string;
content: string | Buffer;
utcDateModified: string;
}
export default () => {
const existingBlobIds = new Set();
for (const noteId of sql.getColumn(`SELECT noteId FROM note_contents`)) {
const row = sql.getRow(`SELECT noteId, content, dateModified, utcDateModified FROM note_contents WHERE noteId = ?`, [noteId]);
for (const noteId of sql.getColumn<string>(`SELECT noteId FROM note_contents`)) {
const row = sql.getRow<NoteContentsRow>(`SELECT noteId, content, dateModified, utcDateModified FROM note_contents WHERE noteId = ?`, [noteId]);
const blobId = utils.hashedBlobId(row.content);
if (!existingBlobIds.has(blobId)) {
@ -28,7 +41,7 @@ module.exports = () => {
}
for (const noteRevisionId of sql.getColumn(`SELECT noteRevisionId FROM note_revision_contents`)) {
const row = sql.getRow(`SELECT noteRevisionId, content, utcDateModified FROM note_revision_contents WHERE noteRevisionId = ?`, [noteRevisionId]);
const row = sql.getRow<NoteRevisionContents>(`SELECT noteRevisionId, content, utcDateModified FROM note_revision_contents WHERE noteRevisionId = ?`, [noteRevisionId]);
const blobId = utils.hashedBlobId(row.content);
if (!existingBlobIds.has(blobId)) {
@ -44,7 +57,7 @@ module.exports = () => {
sql.execute("UPDATE entity_changes SET entityName = 'blobs', entityId = ? WHERE entityName = 'note_revision_contents' AND entityId = ?", [blobId, row.noteRevisionId]);
} else {
// duplicates
sql.execute("DELETE FROM entity_changes WHERE entityName = 'note_revision_contents' AND entityId = ?", [row.noteId]);
sql.execute("DELETE FROM entity_changes WHERE entityName = 'note_revision_contents' AND entityId = ?", [row.noteRevisionId]);
}
sql.execute("UPDATE note_revisions SET blobId = ? WHERE noteRevisionId = ?", [blobId, row.noteRevisionId]);

View File

@ -1,15 +1,15 @@
module.exports = () => {
const beccaLoader = require("../../src/becca/becca_loader");
const becca = require("../../src/becca/becca");
const cls = require("../../src/services/cls");
const log = require("../../src/services/log");
const sql = require("../../src/services/sql");
import becca from "../../src/becca/becca";
import becca_loader from "../../src/becca/becca_loader";
import cls from "../../src/services/cls";
import log from "../../src/services/log";
import sql from "../../src/services/sql";
export default () => {
cls.init(() => {
// emergency disabling of image compression since it appears to make problems in migration to 0.61
sql.execute(`UPDATE options SET value = 'false' WHERE name = 'compressImages'`);
beccaLoader.load();
becca_loader.load();
for (const note of Object.values(becca.notes)) {
try {

View File

@ -1,8 +1,5 @@
import http from "http";
import ini from "ini";
import fs from "fs";
import dataDir from "./src/services/data_dir.js";
const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, "utf-8"));
import config from "./src/services/config.js";
if (config.Network.https) {
// built-in TLS (terminated by trilium) is not supported yet, PRs are welcome

8132
docs/User Guide/!!!meta.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
# User Guide
The sub-children of this note are automatically synced.

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -0,0 +1,9 @@
# Advanced Showcases
Trilium offers advanced functionality through [Scripts](../Note%20Types/Code/Scripts.md) and [Promoted Attributes](Attributes/Promoted%20Attributes.md). To illustrate these features, we've prepared several showcases available in the [demo notes](Database.md):
* [Relation Map](Relation%20Map.md)
* [Day Notes](Advanced%20Showcases/Day%20Notes.md)
* [Weight Tracker](Advanced%20Showcases/Weight%20Tracker.md)
* [Task Manager](Advanced%20Showcases/Task%20Manager.md)
It's important to note that these examples are not natively supported by Trilium out of the box; instead, they demonstrate what you can build within Trilium.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,59 @@
# Day Notes
A common pattern in note-taking is that a lot of notes will be centered around a certain date - e.g. you have some tasks which needs to be done on a certain date, you have meeting minutes from a certain date, you have your thoughts etc. and it all revolves around a date on which they occurred. For this reason, it makes sense to create a certain "day workspace" which will centralize all those notes relevant for a certain date.
For this, Trilium provides a concept of "day note". Trilium semi-automatically generates a single note for each day. Under this note you can save all those relevant notes.
Select an existing day note, and the menubar contains a calendar widget. Select any day to create a note for that day. 
![](1_Day%20Notes_image.png)
This pattern works well also because of [Cloning Notes](../../Basic%20Concepts/Note/Cloning%20Notes.md) functionality - note can appear in multiple places in the note tree, so besides appearing under day note, it can also be categorized into other notes.
## Demo
![](Day%20Notes_image.png)
You can see the structure of day notes appearing under "Journal" note - there's a note for the whole year 2017, under it, you have "12 - December" which then contains "18 - Monday". This is our "day note" which contains some text in its content and also has some child notes (some of them are from [Task manager](Task%20Manager.md)).
You can also notice how this day note has [promoted attribute](../Attributes/Promoted%20Attributes.md) "weight" where you can track your daily weight. This data is then used in [Weight tracker](Weight%20Tracker.md).
## Templates
Trilium provides [template](../Attributes/Template.md) functionality, and it could be used together with day notes.
You can define one of the following relations on the root of the journal (identified by `#calendarRoot` label):
* yearTemplate
* monthTemplate
* dateTemplate
All of these are relations. When Trilium creates a new note for year or month or date, it will take a look at the root and attach a corresponding `~template` relation to the newly created role. Using this, you can e.g. create your daily template with e.g. checkboxes for daily routine etc.
## Date pattern
It's possible to customize the title of generated date notes by defining a `#datePattern` label on a root calendar note (identified by `#calendarRoot` label). Following are possible values:
* `{dayInMonthPadded} - {weekDay}` day notes are named e.g. "24 - Monday"
* `{dayInMonthPadded}: {weekDay3}` day notes are named e.g. "24: Mon"
* `{dayInMonthPadded}: {weekDay2}` day notes are named e.g. "24: Mo"
* `{isoDate} - {weekDay}` day notes are named e.g. "2020-12-24 - Monday"
* `{ordinal}` is replaced with the ordinal date (e.g. 1st, 2nd, 3rd) etc.
## Month pattern
It is also possible to customize the title of generated month notes through the `#monthPattern` attribute, much like `#datePattern`. The options are:
* `{monthNumberPadded}` results in a number like `09` for September, and `11` for November
* `{month}` results in the full month name (e.g. `September` or `October`)
* `{shortMonth3}` is replaced with the first 3 letters of the month, e.g. Jan, Feb, etc.
* `{shortMonth4}` is replaced with the first 4 letters of the month, e.g. Sept, Octo, etc.
The default is `{monthNumberPadded} - {month}`
## Implementation
Trilium has some special support for day notes in the form of [backend Script API](https://triliumnext.github.io/Notes/backend_api/BackendScriptApi.html) - see e.g. getDayNote() function.
Day (and year, month) notes are created with a label - e.g. `#dateNote="2018-08-16"` this can then be used by other scripts to add new notes to day note etc.
Journal also has relation `child:child:child:template=Day template` (see \[\[attribute inheritance\]\]) which effectively adds \[\[template\]\] to day notes (grand-grand-grand children of Journal).

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -0,0 +1,63 @@
# Task Manager
Task Manager is a [promoted attributes](../Attributes/Promoted%20Attributes.md) and [scripts](../../Note%20Types/Code/Scripts.md)showcase present in the [demo notes](../Database.md).
## Demo
![](../../Attachments/task-manager.png)
Task Manager manages outstanding (TODO) tasks and finished tasks (non-empty doneDate attribute). Outstanding tasks are further categorized by location and arbitrary tags - whenever you change tag attribute in the task note, this task is then automatically moved to appropriate location.
Task Manager also integrates with [day notes](Day%20Notes.md) - notes are [cloned](../../Basic%20Concepts/Note/Cloning%20Notes.md) into day note to both todoDate note and doneDate note (with [prefix](../../Basic%20Concepts/Navigation/Tree%20Concepts.md) of either "TODO" or "DONE").
## Implementation
New tasks are created in the TODO note which has `~child:template` [relation](../Attributes.md)(see [attribute inheritance](../Attributes/Attribute%20Inheritance.md)) pointing to the task template.
### Attributes
Task template defines several [promoted attributes](../Attributes/Promoted%20Attributes.md) - todoDate, doneDate, tags, location. Importantly it also defines `~runOnAttributeChange` relation - [event](../../Note%20Types/Code/Events.md) handler which is run on attribute change. This [script](../../Note%20Types/Code/Scripts.md) handles when e.g. we fill out the doneDate attribute - meaning the task is done and should be moved to "Done" note and removed from TODO, locations and tags.
### New task button
There's also "button" note which contains simple script which adds a button to create new note (task) in the TODO note.
```
api.addButtonToToolbar({
title: 'New task',
icon: 'check',
shortcut: 'alt+n',
action: async () => {
// creating notes is backend (server) responsibility so we need to pass
// the control there
const taskNoteId = await api.runOnBackend(async () => {
const todoRootNote = await api.getNoteWithLabel('taskTodoRoot');
const {note} = await api.createNote(todoRootNote.noteId, 'new task', '');
return note.noteId;
});
// we got an ID of newly created note and we want to immediatelly display it
await api.activateNewNote(taskNoteId);
}
});
```
### CSS
In the demo screenshot above you may notice that TODO tasks are in red color and DONE tasks are green.
This is done by having this CSS [code note](../../Note%20Types/Code.md) which defines extra CSS classes:
```
span.fancytree-node.todo .fancytree-title {
color: red !important;
}
span.fancytree-node.done .fancytree-title {
color: green !important;
}
```
This [code note](../../Note%20Types/Code.md) has `#appCss` [label](../Attributes.md)which is recognized by Trilium on startup and loaded as CSS into the application.
Second part of this functionality is based in event handler described above which assigns `#cssClass` label to the task to either "done" or "todo" based on the task status.

View File

@ -0,0 +1,70 @@
# Weight Tracker
![](Weight%20Tracker_image.png)
The `Weight Tracker` is a [Script API](../../Note%20Types/Code/Script%20API.md) showcase present in the [demo notes](../Database.md).
By adding `weight` as a [promoted attribute](../Attributes/Promoted%20Attributes.md) in the [template](../Attributes/Template.md) from which [day notes](Day%20Notes.md) are created, you can aggregate the data and plot weight change over time.
## Implementation
The `Weight Tracker` note in the screenshot above is of the type `Render Note`. That type of note doesn't have any useful content itself. Instead it is a placeholder where a [script](../../Note%20Types/Code/Scripts.md) can render its output.
Scripts for `Render Notes` are defined in a [relation](../Attributes.md) called `~renderNote`. In this example, it's the `Weight Tracker`'s child `Implementation`. The Implementation consists of two [code notes](../../Note%20Types/Code.md) that contain some HTML and JavaScript respectively, which load all the notes with a `weight` attribute and display their values in a chart.
To actually render the chart, we're using a third party library called [chart.js](https://www.chartjs.org/)which is imported as an attachment, since it's not built into Trilium.
### Code
Here's the content of the script which is placed in a [code note](../../Note%20Types/Code.md) of type `JS Frontend`:
```
async function getChartData() {
const days = await api.runOnBackend(async () => {
const notes = api.getNotesWithLabel('weight');
const days = [];
for (const note of notes) {
const date = note.getLabelValue('dateNote');
const weight = parseFloat(note.getLabelValue('weight'));
if (date && weight) {
days.push({ date, weight });
}
}
days.sort((a, b) => a.date > b.date ? 1 : -1);
return days;
});
const datasets = [
{
label: "Weight (kg)",
backgroundColor: 'red',
borderColor: 'red',
data: days.map(day => day.weight),
fill: false,
spanGaps: true,
datalabels: {
display: false
}
}
];
return {
datasets: datasets,
labels: days.map(day => day.date)
};
}
const ctx = $("#canvas")[0].getContext("2d");
new chartjs.Chart(ctx, {
type: 'line',
data: await getChartData()
});
```
## How to remove the Weight Tracker button from the top bar
In the link map of the `Weight Tracker`, there is a note called `Button`. Open it and delete or comment out its contents. The `Weight Tracker` button will disappear after you restart Trilium.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,82 @@
# Attributes
In Trilium, attributes are key-value pairs assigned to notes, providing additional metadata or functionality. There are two primary types of attributes:
1. **Labels**: Simple key-value text records
2. **Relations**: Named links to other notes
These attributes play a crucial role in organizing, categorising, and enhancing the functionality of notes.
![](Attributes_image.png)
## Labels
Labels in Trilium can be used for a variety of purposes:
* **Metadata**: Assign labels with optional values for categorization, such as `#year=1999`, `#genre="sci-fi"`, or `#author="Neal Stephenson"`
* **Configuration**: Labels can configure advanced features or settings
* **Scripts and Plugins**: Used to tag notes with special metadata, such as the "weight" attribute in the [Weight Tracker](Advanced%20Showcases/Weight%20Tracker.md)
Labels are also searchable, enhancing note retrieval.
### Common Labels for Advanced Configuration
* `**disableVersioning**`: Disables automatic versioning, ideal for large, unimportant notes like script libraries
* `**versioningLimit**`: Used to limit the number of revisions for a single note
* `**calendarRoot**`: Marks the note as the root for [day notes](Advanced%20Showcases/Day%20Notes.md). Only one note should carry this label
* `**archived**`: Hides notes from default search results and dialogs
* `**excludeFromExport**`: Excludes notes and their subtrees from export operations
* `**run**`: Specifies events to trigger scripts (e.g., `frontendStartup`, `hourly`)
* `**runAtHour**`: Defines specific hours for scripts to run, used with `#run=hourly`
* `**disableInclusion**`: Prevents a script from being included in parent script executions
* `**sorted**`: Automatically sorts child notes alphabetically by title
* `**top**`: Keeps the note at the top of its parent's list, useful with `sorted`
* `**hidePromotedAttributes**`: Hides certain attributes in the note's display
* `**readOnly**`: Sets the note to read-only mode, applicable to text and code notes
* `**autoReadOnlyDisabled**`: Disables automatic read-only mode for large notes
* `**appCss**`: Marks CSS notes used to modify Triliums appearance
* `**appTheme**`: Marks full CSS themes available in Trilium's options
* `**cssClass**`: Adds a CSS class to the note's representation in the tree
* `**iconClass**`: Adds a CSS class to the note's icon, useful for distinguishing notes visually. See [note icons](../Basic%20Concepts/Note/Note%20Icons.md)
* `**pageSize**`: Specifies the number of items per page in note listings
* `**customRequestHandler**` **and** `**customResourceProvider**`: Refer to [Custom request handler](Custom%20Request%20Handler.md)
* `**widget**`: Marks a note as a custom widget, added to Trilium's component tree
* `**workspace**` **and related attributes**: See [Workspace](../Basic%20Concepts/Navigation/Workspace.md) for more details
* `**searchHome**`: Specifies the parent for new search notes
* `**inbox**`: Designates a default location for new notes created via the sidebar
* `**sqlConsoleHome**`: Default location for SQL console notes
* `**bookmarked**` **and** `**bookmarkFolder**`: See [Bookmarks](../Basic%20Concepts/Navigation/Bookmarks.md)
* `**shareXXX**`: See [Sharing](Sharing.md)
* `**keyboardShortcut**`: Assigns a keyboard shortcut to open the note
* `**displayRelations**` **and** `**hideRelations**`: Manages the display of note relations
* `**titleTemplate**`: See [Default note title](Default%20Note%20Title.md)
* `**template**`: Makes the note available as a template
* `**toc**`: Controls the visibility of the table of contents
* `**color**`: Defines the color of the note in the tree and links
* `**hideChildrenOverview**`: Hides child notes in the parent note's editor
* `**viewType**`: Sets the view of child notes (grid or list)
## Relations
Relations define connections between notes, similar to links.
### Uses
* **Metadata Relationships**: For example, linking a book note to an author note
* **Scripting**: Attaching scripts to events or conditions related to the note
### Common Relations
* **Event-based Relations**: Such as `runOnNoteCreation` or `runOnNoteChange`, which trigger scripts on specific actions
* **Other Relations**: Include `template`, `renderNote`, `widget`, and sharing-related relations
## Multiplicity
Attributes in Trilium can be "multivalued", meaning multiple attributes with the same name can coexist.
## Attribute Definitions and Promoted Attributes
Special labels create "label/attribute" definitions, enhancing the organization and management of attributes. For more details, see [Promoted attributes](Attributes/Promoted%20Attributes.md).
## Attribute Inheritance
Trilium supports attribute inheritance, allowing child notes to inherit attributes from their parents. For more information, see [Attribute inheritance](Attributes/Attribute%20Inheritance.md).

View File

@ -0,0 +1,25 @@
# Attribute Inheritance
## 1\. Standard Inheritance
In Trilium, attributes can be automatically inherited by child notes if they have the `isInheritable` flag set to `true`. This means the attribute (a key-value pair) is applied to the note and all its descendants.
### Example Use Case
The `archived` label can be set to be inheritable, allowing you to hide a whole subtree of notes from searches and other dialogs by applying this label at the top level.
## 2\. Copying Inheritance
Copying inheritance differs from standard inheritance by using a `child:` prefix in the attribute name. This prefix causes new child notes to automatically receive specific attributes from the parent note. These attributes are independent of the parent and will persist even if the note is moved elsewhere.
### How to Use
* **Syntax:** `#child:attributeName`
* **Chained Inheritance:** You can chain this inheritance, such as `#child:child:attributeName`, where each child down the hierarchy receives the appropriate attribute.
### Example
If a parent note has the label `#child:exampleAttribute`, all newly created child notes will inherit the `#exampleAttribute` label. This can be useful for setting default properties for notes in a specific section.
## 3\. Template Inheritance
Attributes can also be inherited from [templates](Template.md). When a new note is created using a template, it inherits the attributes defined in that template. This is particularly useful for maintaining consistency across notes that follow a similar structure or function.

View File

@ -0,0 +1,32 @@
# Promoted Attributes
Promoted attributes are [attributes](../Attributes.md) which are considered important and thus are "promoted" onto the main note UI. See example below:
![](../../Attachments/promoted-attributes.png)
You can see the note having kind of form with several fields. Each of these is just regular attribute, the only difference is that they appear on the note itself.
Attributes can be pretty useful since they allow for querying and script automation etc. but they are also inconveniently hidden. This allows you to select few of the important ones and push them to the front of the user.
Now, how do we make attribute to appear on the UI?
## Attribute definition
Attribute is always name-value pair where both name and value are strings.
_Attribute definition_ specifies how should this value be interpreted - is it just string, or is it a date? Should we allow multiple values or note? And importantly, should we _promote_ the attribute or not?
![](Promoted%20Attributes_image.png)
You can notice tag attribute definition. These "definition" attributes define how the "value" attributes should behave.
So there's one attribute for value and one for definition. But notice how definition attribute is [Inheritable](Attribute%20Inheritance.md), meaning that it's also applied to all descendant note. So in a way, this definition is used for the whole subtree while "value" attributes are applied only for this note.
### Inverse relation
Some relations always occur in pairs - my favorite example is on the family. If you have a note representing husband and note representing wife, then there might be a relation between those two of `isPartnerOf`. This is bidirectional relationship - meaning that if a relation is pointing from husband to wife then there should be always another relation pointing from wife to husband.
Another example is with parent - child relationship. Again these always occur in pairs, but in this case it's not exact same relation - the one going from parent to child might be called `isParentOf` and the other one going from child to parent might be called `isChildOf`.
Relation definition allows you to specify such "inverse relation" - for the relation you just define you specify which is the inverse relation. Note that in the second example we should have two relation definitions - one for `isParentOf` which defines `isChildOf` as inverse relation and then second relation definition for `isChildOf` which defines `isParentOf` as inverse relation.
What this does internally is that whenever we save a relation which has defined inverse relation, we check that this inverse relation exists on the relation target note. Similarly, when we delete relation, we also delete inverse relation on the target note.

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -0,0 +1,36 @@
# Template
A template in Trilium serves as a predefined structure for other notes, referred to as instance notes. Assigning a template to a note brings three main effects:
1. **Attribute Inheritance**: All attributes from the template note are [inherited](Attribute%20Inheritance.md) by the instance notes. Even attributes with `#isInheritable=false` are inherited by the instance notes, although only inheritable attributes are further inherited by the children of the instance notes.
2. **Content Duplication**: The content of the template note is copied to the instance note, provided the instance note is empty at the time of template assignment.
3. **Child Note Duplication**: All child notes of the template are deep-duplicated to the instance note.
## Example
A typical example would be a "Book" template note, which might include:
* **Promoted Attributes**: Such as publication year, author, etc. (see [promoted attributes](Promoted%20Attributes.md)).
* **Outline**: An outline for a book review, including sections like themes, conclusion, etc.
* **Child Notes**: Additional notes for highlights, summary, etc.
![Template Example](../../Attachments/template.png)
## Instance Note
An instance note is a note related to a template note. This relationship means the instance note's content is initialized from the template, and all attributes from the template are inherited.
To create an instance note through the UI:
![show child note templates](../../Attachments/template-create-instance-n.png)
For the template to appear in the menu, the template note must have the `#template` label. Do not confuse this with the `~template` relation, which links the instance note to the template note. If you use [workspaces](../../Basic%20Concepts/Navigation/Workspace.md), you can also mark templates with `#workspaceTemplate` to display them only in the workspace.
Templates can also be added or changed after note creation by creating a `~template` relation pointing to the desired template note.
## Additional Notes
From a visual perspective, templates can define `#iconClass` and `#cssClass` attributes, allowing all instance notes (e.g., books) to display a specific icon and CSS style.
Explore the concept further in the [demo notes](../Database.md), including examples like the [Relation Map](../Relation%20Map.md), [Task Manager](../Advanced%20Showcases/Task%20Manager.md), and [Day Notes](../Advanced%20Showcases/Day%20Notes.md).
Additionally, see [default note title](../Default%20Note%20Title.md) for creating title templates. Note templates and title templates can be combined by creating a `#titleTemplate` for a template note.

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,16 @@
# Bulk actions
### Execute script
For more complex scenarios, it is possible to type in a JavaScript expression in order to apply the necessary changes.
To apply a suffix (`- suffix` in this example), to the note title:
```javascript
note.title = note.title + " - suffix";
```
To alter attributes of a note in a bulk action, such as setting the `#shareAlias` label to the title of the note:
```javascript
note.setLabel("shareAlias", note.title)
```

View File

@ -0,0 +1,30 @@
# Configuration (config.ini or environment variables)
Trilium supports configuration via a file named `config.ini` and environment variables. Please review the file named [config-sample.ini](https://github.com/TriliumNext/Notes/blob/develop/config-sample.ini) in the [Notes](https://github.com/TriliumNext/Notes) repository to see what values are supported.
You can provide the same values via environment variables instead of the `config.ini` file, and these environment variables use the following format:
1. Environment variables should be prefixed with `TRILIUM_` and use underscores to represent the INI section structure.
2. The format is: `TRILIUM_<SECTION>_<KEY>=<VALUE>`
3. The environment variables will override any matching values from config.ini
For example, if you have this in your config.ini:
```
[Network]
host=localhost
port=8080
```
You can override these values using environment variables:
```
TRILIUM_NETWORK_HOST=0.0.0.0
TRILIUM_NETWORK_PORT=9000
```
The code will:
1. First load the `config.ini` file as before
2. Then scan all environment variables for ones starting with `TRILIUM_`
3. Parse these variables into section/key pairs
4. Merge them with the config from the file, with environment variables taking precedence

View File

@ -0,0 +1,88 @@
# Custom Request Handler
Trilium provides a mechanism for [scripts](../Note%20Types/Code/Scripts.md) to open a public REST endpoint. This opens a way for various integrations with other services - a simple example would be creating new note from Slack by issuing a slash command (e.g. `/trilium buy milk`).
## Create note from outside Trilium
Let's take a look at an example. The goal is to provide a REST endpoint to which we can send title and content and Trilium will create a note.
We'll start with creating a JavaScript backend [code note](../Note%20Types/Code.md) containing:
```
const {req, res} = api;
const {secret, title, content} = req.body;
if (req.method == 'POST' && secret === 'secret-password') {
// notes must be saved somewhere in the tree hierarchy specified by a parent note.
// This is defined by a relation from this code note to the "target" parent note
// alternetively you can just use constant noteId for simplicity (get that from "Note Info" dialog of the desired parent note)
const targetParentNoteId = api.currentNote.getRelationValue('targetNote');
const {note} = api.createTextNote(targetParentNoteId, title, content);
const notePojo = note.getPojo();
res.status(201).json(notePojo);
}
else {
res.send(400);
}
```
This script note has also following two attributes:
* label `#customRequestHandler` with value `create-note`
* relation `~targetNote` pointing to a note where new notes should be saved
### Explanation
Let's test this by using an HTTP client to send a request:
```
POST http://my.trilium.org/custom/create-note
Content-Type: application/json
{
"secret": "secret-password",
"title": "hello",
"content": "world"
}+++++++++++++++++++++++++++++++++++++++++++++++
```
Notice the `/custom` part in the request path - Trilium considers any request with this prefix as "custom" and tries to find a matching handler by looking at all notes which have `customRequestHandler` [label](Attributes.md). Value of this label then contains a regular expression which will match the request path (in our case trivial regex "create-note").
Trilium will then find our code note created above and execute it. `api.req`, `api.res` are set to [request](https://expressjs.com/en/api.html#req) and [response](https://expressjs.com/en/api.html#res)objects from which we can get details of the request and also respond.
In the code note we check the request method and then use trivial authentication - keep in mind that these endpoints are by default totally unauthenticated, and you need to take care of this yourself.
Once we pass these checks we will just create the desired note using [Script API](../Note%20Types/Code/Script%20API.md).
## Custom resource provider
Another common use case is that you want to just expose a file note - in such case you create label `customResourceProvider` (value is again path regex).
For more information, see [Custom Resource Providers](Custom%20Resource%20Providers.md).
## Advanced concepts
`api.req` and `api.res` are Express.js objects - you can always look into its [documentation](https://expressjs.com/en/api.html) for details.
### Parameters
REST request paths often contain parameters in the URL, e.g.:
```
http://my.trilium.org/custom/notes/123
```
The last part is dynamic so the matching of the URL must also be dynamic - for this reason the matching is done with regular expressions. Following `customRequestHandler` value would match it:
```
notes/([0-9]+)
```
Additionally, this also defines a matching group with the use of parenthesis which then makes it easier to extract the value. The matched groups are available in `api.pathParams`:
```
const noteId = api.pathParams[0];
```
Often you also need query params (as in e.g. `http://my.trilium.org/custom/notes?noteId=123`), you can get those with standard express `req.query.noteId`.

View File

@ -0,0 +1,34 @@
# Custom Resource Providers
A custom resource provider allows any file imported into Trilium (images, fonts, stylesheets) to be publicly accessible via a URL.
A potential use case for this is to add embed a custom font alongside a theme.
## Steps for creating a custom resource provider
1. Import a file such as an image or a font into Trilium by drag & drop.
2. Select the file and go to the _Owned Attributes_ section.
3. Add the label `#customResourceProvider=hello`.
4. To test if it is working, use a browser to navigate to `<protocol>://<host>/custom/hello` (where `<protocol>` is either `http` or `https` based on your setup, and `<host>` is the host or IP to your Trilium server instance). If you are running the TriliumNext application without a server, use `http://localhost:37840` as the base URL.
5. If everything went well, at the previous step the browser should have downloaded the file uploaded in the first step.
Instead of `hello`, the name can be:
* A path, such as `fonts/Roboto.ttf`, which would be accessible via `<host>/custom/fonts/Roboto.ttf`.
* As a more advanced use case, a regular expression to match multiple routes, such as `hello/.*` which will be accessible via `/custom/hello/1`, `/custom/hello/2`, `/custom/hello/world`, etc.
## Using it in a theme
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 `#customResourceProvider=fonts/myfont.ttf` attribute.
Then modify the theme CSS to point to:
```css
@font-face {
font-family: customFont;
src: url("/custom/fonts/myfont.ttf");
}
div {
font-family: customFont;
}
```

View File

@ -0,0 +1,37 @@
# Database
Your Trilium data is stored in a [SQLite](https://www.sqlite.org) database which contains all notes, tree structure, metadata, and most of the configuration. The database file is named `document.db` and is stored in the application's default [Data directory](../Installation%20%26%20Setup/Data%20directory.md).
## Demo Notes
When you run Trilium for the first time, it will generate a new database containing demo notes. These notes showcase its many features, such as:
* [Relation Map](Relation%20Map.md)
* [Day Notes](Advanced%20Showcases/Day%20Notes.md)
* [Weight Tracker](Advanced%20Showcases/Weight%20Tracker.md)
* [Task Manager](Advanced%20Showcases/Task%20Manager.md)
* [Custom CSS Themes](../Basic%20Concepts/Themes.md)
### Restoring Demo Notes
There are some cases in which you may want to restore the original demo notes. For example, if you experimented with some of the more advanced features and want to see the original reference, or if you simply want to explore the latest version of the demo notes, which might showcase new features.
You can easily restore the demo notes by using Trilium's built-in import feature by importing them:
* Download [this .zip archive](https://github.com/TriliumNext/Notes/raw/develop/db/demo.zip) with the latest version of the demo notes
* Right click on any note in your tree under which you would like the demo notes to be imported
* Click "Import into note"
* Select the .zip archive to import it
## Manually Modifying the Database
Trilium provides a lot of flexibility, and with it, opportunities for advanced users to tweak it. If you need to explore or modify the database directly, you can use a tool such as [SQLite Browser](https://sqlitebrowser.org/) to work directly on the database file.
See [Manually altering the database](Database/Manually%20altering%20the%20database.md) for more information.
## How to Reset the Database
If you are experimenting with Trilium and want to return it to its original state, you can do that by deleting the current database. When you restart the application, it will generate a new database containing the original demo notes.
To delete the database, simply go to the [data directory](../Installation%20%26%20Setup/Data%20directory.md) and delete the `document.db` file (and any other files starting with `document.db`).
If you do not need to preserve any configurations that might be stored in the `config.ini` file, you can just delete all of the [data directory's](../Installation%20%26%20Setup/Data%20directory.md) contents to fully restore the application to its original state. You can also review the [configuration](Configuration%20\(config.ini%20or%20e.md) file to provide all `config.ini` values as environment variables instead.

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -0,0 +1,42 @@
# Manually altering the database
There are some situations where modifying the SQLite database that Trilium uses is desirable.
If you are doing any advanced development or troubleshooting where you manually modify the database, you might want to consider creating backups of your `document.db` file.
## Modifying it internally using the SQL Console
The SQL Console is Trilium's built-in database editor.
See [SQL Console](Manually%20altering%20the%20database/SQL%20Console.md).
## Externally modifying the database
Sometimes the SQL Console cannot be used (for example if the application cannot start).
When making external modifications, consider closing the desktop application. If modifying the server database, then stop the service or Docker container.
### Using DB Browser for SQLite
DB Browser for SQLite is a cross-platform editor that can be used to alter the database using a graphical user interface.
To do so:
1. In the main menu, select File → Open database… and navigate to the database in the [Data directory](../../Installation%20%26%20Setup/Data%20directory.md).
2. Select the _Execute SQL_ tab.
3. Type in the desired SQL statement.
4. Press the "Play" button in the toolbar underneath the "Execute SQL" tab (or F5 key).
5. Press "Write Changes" in the main toolbar.
6. Close the application or close the database.
![](Manually%20altering%20the%20data.png)
### Using the SQLite CLI
First, start the SQLite 3 CLI by specifying the path to the database:
```
sqlite3 ~/.local/share/trilium-data/document.db
```
* In the prompt simply type the statement and make sure it ends with a `;` character.
* To exit, simply type `.quit` and enter.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

View File

@ -0,0 +1,28 @@
# SQL Console
The SQL Console is Trilium's built-in database editor.
It can be accessed by going to the [global menu](../../../Basic%20Concepts/UI%20Elements) → Advanced → Open SQL Console.
![](SQL%20Console_image.png)
### Interaction
* Hovering the mouse over one of the tables listed at the top of the document will show the columns and their data type.
* Only one SQL statement can be run at once.
* To run the statement, press the ![](3_SQL%20Console_image.png)icon.
* For queries that return a result, the data will displayed in a table.
![](1_SQL%20Console_image.png)
### Saved SQL console
SQL queries or commands can be saved into a dedicated note.
To do so, simply write the query and press the ![](2_SQL%20Console_image.png)button. Once saved, the note will appear in [Day Notes](../../Advanced%20Showcases/Day%20Notes.md).
* The SQL expression will not be displayed by default, but it can still be viewed by going to the note context menu and selecting _Note source_.
* The expression cannot be modified. If needed, recreate it by copying the statement back into the SQL console and then saving it again.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,30 @@
# Default Note Title
When a new note is created, its name is by default "new note". In some cases, it can be desirable to have a different or even a dynamic default note title.
For this use case, Trilium (since v0.52) supports `#titleTemplate` [label](Attributes.md). You can create such a label for a given note, assign it a value, and this value will be used as a default title when creating child notes. As with other labels, you can make it inheritable to apply recursively, and you can even place it on the root note to have it applied globally everywhere.
As an example use case, imagine you collect books you've read in a given year like this:
* 2022 Books
* Neal Stephenson: Anathem, 2008
* Franz Kafka: Die Verwandlung, 1915
Now, to the parent note "2022 Books" you can assign label `#titleTemplate="[Author name]: [Book title], [Publication year]"`.
And all children of "2022 Books" will be created with initial title "\[Author name\]: \[Book title\], \[Publication year\]". There's no artificial intelligence here, the idea is to just prompt you to manually fill in the pieces of information into the note title by yourself.
## Dynamic value
The value of `#titleTemplate` is evaluated at the point of note's creation as a JavaScript string, which means it can be enriched with the help of JS string interpolation with dynamic data.
As an example, imagine you collect server outage incidents and write some notes. It looks like this:
* Incidents
* 2022-05-09: System crash
* 2022-05-15: Backup delay
You can automatize the date assignment by assigning a label `#titleTemplate="${now.format('YYYY-MM-DD')}: "` to the parent note "Incidents". Whenever a new child note is created, the title template is evaluated with the injected [now](https://day.js.org/docs/en/display/format) object.
Second variable injected is [parentNote](https://triliumnext.github.io/Notes/backend_api/BNote.html), an example could be `#titleTemplate="${parentNote.getLabelValue('authorName')}'s literary works"`.
See also \[\[[template](Attributes/Template.md)\]\] which provides similar capabilities, including default note's content.

View File

@ -0,0 +1,28 @@
# ETAPI (REST API)
ETAPI is Trilium's public/external REST API. It is available since Trilium v0.50.
The documentation is in OpenAPI format, available [here](https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml).
[trilium-py](https://github.com/Nriver/trilium-py) is a third-party Python implementation for ETAPI client, you can use Python to communicate with Trilium.
## Authentication
All operations have to be authenticated using a token. You can get this token either from Options -> ETAPI or programmatically using the `/auth/login` REST call (see the [spec](https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml)):
```
GET https://myserver.com/etapi/app-info
Authorization: ETAPITOKEN
```
Alternatively, since 0.56 you can also use basic auth format:
```
GET https://myserver.com/etapi/app-info
Authorization: Basic BATOKEN
```
* Where `BATOKEN = BASE64(username + ':' + password)` - this is a standard Basic Auth serialization
* Where `username` is "etapi"
* And `password` is the generated ETAPI token described above.
Basic Auth is meant to be used with tools which support only basic auth.

View File

@ -0,0 +1,25 @@
# Note Map
Note map is a visualisation of connections between notes.
This provides an insight into a structure ("web") of notes.
There are two types of note map:
## Link Map
Shows [relations](Attributes.md) between notes:
![](1_Note%20Map_image.png)
## Tree Map
Shows hierarchical map of notes:
![](Note%20Map_image.png)
## See also
[Relation map](Relation%20Map.md) is a similar concept, with some differences:
* note map is automatically generated while relation map must be created manually
* relation map is a type of note while a link map is just virtual visualization

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -0,0 +1,49 @@
# Relation Map
Relation map is a type of [note](../Basic%20Concepts/Navigation/Tree%20Concepts.md) which visualizes notes and their [relations](Attributes.md). See an example:
## Development process demo
This is a basic example how you can create simple diagram using relation maps:
![](../Attachments/relation-map-dev-process.png)
And this is how you can create it:
![](Relation%20Map_relation-map-.gif)
We start completely from scratch by first creating new note called "Development process" and changing its type to "Relation map". After that we create new notes one by one and place them by clicking into the map. We also drag [relations](Attributes.md)between notes and name them. That's all!
Items on the map - "Specification", "Development", "Testing" and "Demo" are actually notes which have been created under "Development process" note - you can click on them and write some content. Connections between notes are called "[relations](Attributes.md)".
## Family demo
This is more complicated demo using some advanced concepts. Resulting diagram is here:
![](../Attachments/relation-map-family.png)
This is how you get to it:
![](../Attachments/relation-map-family-demo.gif)
There are several steps here:
* we start with empty relation map and two existing notes representing Prince Philip and Queen Elizabeth II. These two notes already have "isPartnerOf" [relations](Attributes.md)defined.
* There are actually two "inverse" relations (one from Philip to Elizabeth and one from Elizabeth to Philip)
* we drag both notes to relation map and place to suitable position. Notice how the existing "isPartnerOf" relations are displayed.
* now we create new note - we name it "Prince Charles" and place it on the relation map by clicking on the desired position. The note is by default created under the relation map note (visible in the note tree on the left).
* we create two new relations "isChildOf" targeting both Philip and Elizabeth
* now there's something unexpected - we can also see the relation to display another "hasChild" relation. This is because there's a [relation definition](Attributes/Promoted%20Attributes.md) which puts "isChildOf" as an "[inverse](Attributes/Promoted%20Attributes.md)" relation of "hasChildOf" (and vice versa) and thus it is created automatically.
* we create another note for Princess Diana and create "isPartnerOf" relation from Charles. Again notice how the relation has arrows both ways - this is because "isPartnerOf" definition specifies its inverse relation as again "isPartnerOf" so the opposite relation is created automatically.
* as the last step we pan & zoom the map to fit better to window dimensions.
Relation definitions mentioned above come from "Person template" note which is assigned to any child of "My Family Tree" relation note. You can play with the whole thing in the [demo notes](Database.md).
## Details
You can specify which relations should be displayed with comma delimited names of relations in `displayRelations` label.
Alternatively, you can specify comma delimited list of relation names in `hideRelations` which will display all relations, except for the ones defined in the label.
## See also
* [Note map](Note%20Map.md) is a similar concept

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,101 @@
# Sharing
Trilium allows you to share selected notes as **publicly accessible** read-only documents. This feature is particularly useful for publishing content directly from your Trilium notes, making it accessible to others online.
## Prerequisites
To use the sharing feature, you must have a [server installation](../Installation%20%26%20Setup/Server%20Installation.md) of Trilium. This is necessary because the notes will be hosted from the server.
## How to Share a Note
1. **Enable Sharing**: To share a note, toggle the `Shared` switch within the note's interface. Once sharing is enabled, an URL will appear, which you can click to access the shared note.
![Share Note](../Attachments/share-single-note.png)
2. **Access the Shared Note**: The link provided will open the note in your browser. If your server is not configured with a public IP, the URL will refer to `localhost (127.0.0.1)`.
![Shared Note Example](../Attachments/share-single-note-web.png)
## Sharing a Note Subtree
When you share a note, you actually share the entire subtree of notes beneath it. If the note has child notes, they will also be included in the shared content. For example, sharing the "Formatting" subtree will display a page with basic navigation for exploring all the notes within that subtree.
![Shared Subtree Example](../Attachments/share-multiple-notes-web.png)
## Viewing All Shared Notes
You can view a list of all shared notes by clicking on "Show Shared Notes Subtree." This allows you to manage and navigate through all the notes you have made public.
## Security Considerations
Shared notes are published on the open internet and can be accessed by anyone with the URL. The URL's randomness does not provide security, so it is crucial not to share sensitive information through this feature.
### Password Protection
To protect shared notes with a username and password, you can use the `#shareCredentials` attribute. Add this label to the note with the format `#shareCredentials="username:password"`. To protect an entire subtree, make sure the label is [inheritable](Attributes/Attribute%20Inheritance.md).
## Advanced Sharing Options
### Customizing the Appearance of Shared Notes
The default shared page is basic in design, but you can customize it using your own CSS:
* **Custom CSS**: Link a CSS [code note](../Note%20Types/Code.md) to the shared page by adding a `~shareCss` relation to the note. If you want this style to apply to the entire subtree, make the label inheritable. You can hide the CSS code note from the tree navigation by adding the `#shareHiddenFromTree` label.
* **Omitting Default CSS**: For extensive styling changes, use the `#shareOmitDefaultCss` label to avoid conflicts with Trilium's [default stylesheet](../Basic%20Concepts/Themes.md).
### Adding JavaScript
You can inject custom JavaScript into the shared note using the `~shareJs` relation. This allows you to access note attributes or traverse the note tree using the `fetchNote()` API, which retrieves note data based on its ID.
Example:
```javascript
const currentNote = await fetchNote();
const parentNote = await fetchNote(currentNote.parentNoteIds[0]);
for (const attr of parentNote.attributes) {
console.log(attr.type, attr.name, attr.value);
}
```
### Creating Human-Readable URL Aliases
Shared notes typically have URLs like `http://domain.tld/share/knvU8aJy4dJ7`, where the last part is the note's ID. You can make these URLs more user-friendly by adding the `#shareAlias` label to individual notes (e.g., `#shareAlias=highlighting`). This will change the URL to `http://domain.tld/share/highlighting`.
**Important**:
1. Ensure that aliases are unique.
2. Using slashes (`/`) within aliases to create subpaths is not supported.
### Viewing and Managing Shared Notes
All shared notes are grouped under an automatically managed "Shared Notes" section. From here, you can view, share, or unshare notes by moving or cloning them within this section.
![Shared Notes List](../Attachments/shared-list.png)
### Setting a Custom Favicon
To customize the favicon for your shared pages, create a relation `~shareFavicon` pointing to a file note containing the favicon (e.g., in `.ico` format).
### Sharing a Note as the Root
You can designate a specific note or folder as the root of your shared content by adding the `#shareRoot` label. This note will be linked when visiting `[http://domain.tld/share](http://domain/share)`, making it easier to use Trilium as a fully-fledged website. Consider combining this with the `#shareIndex` label, which will display a list of all shared notes.
## Additional Options
* **Raw Note Sharing**: Use the `#shareRaw` label to share a note without any HTML wrapper.
* **Disallow Robot Indexing**: Add the `#shareDisallowRobotIndexing` label to prevent search engines from indexing the shared page by including a `noindex, follow` meta tag and `X-Robots-Tag: noindex` header.
* **Shared Notes Index**: For text notes with the `#shareIndex` label, the content will display a list of all shared note roots.
## Limitations
While the sharing feature is powerful, it has some limitations:
* **No Relation Map Support**
* **Book Notes**: Only show a list of child notes.
* **Code Notes**: No syntax highlighting.
* **Static Note Tree**
* **Protected Notes**: Cannot be shared.
* **Include Notes**: Not supported.
Some of these limitations may be addressed in future updates.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -0,0 +1,16 @@
# Serving directly the content of a note
When accessing a shared note, Trilium will render it as a web page. Sometimes it's desirable to serve the content directly so that it can be used in a script or downloaded by the user.
| A note displayed as a web page (HTML) | A note displayed as a raw format |
| --- | --- |
| ![](1_Serving%20directly%20the%20conte.png) | ![](Serving%20directly%20the%20conte.png) |
## By adding an attribute to the note
Simply add the `#shareRaw` attribute and the note will always be rendered _raw_ when accessed from the share URL.
## By altering the URL
Append `?raw` to the URL to display a note in its raw format regardless of whether the `#shareRaw` attribute is added on the note.
![](Serving%20directly%20the%20conte.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

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