mirror of
https://github.com/zadam/trilium.git
synced 2025-10-26 15:56:29 +01:00
Compare commits
1 Commits
feat/ui-im
...
test-wip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f195c7d1b6 |
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
.git
|
||||
.idea
|
||||
/bin
|
||||
/dist
|
||||
/docs
|
||||
/npm-debug.log
|
||||
node_modules
|
||||
@@ -1,26 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*.{js,ts,tsx}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
|
||||
[{server,translation}.json]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
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
|
||||
7
.eslintignore
Normal file
7
.eslintignore
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
bin
|
||||
docs
|
||||
libraries
|
||||
coverage
|
||||
play
|
||||
212
.eslintrc.js
Normal file
212
.eslintrc.js
Normal file
@@ -0,0 +1,212 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
// plugins: ['prettier'], // to be activated
|
||||
extends: ['eslint:recommended', 'airbnb-base', 'plugin:jsonc/recommended-with-jsonc', 'prettier'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.json', '*.json5', '*.jsonc'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
},
|
||||
{
|
||||
files: ['package.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'jsonc/sort-keys': [
|
||||
'off',
|
||||
{
|
||||
pathPattern: '^$',
|
||||
order: [
|
||||
'name',
|
||||
'version',
|
||||
'private',
|
||||
'packageManager',
|
||||
'description',
|
||||
'type',
|
||||
'keywords',
|
||||
'homepage',
|
||||
'bugs',
|
||||
'license',
|
||||
'author',
|
||||
'contributors',
|
||||
'funding',
|
||||
'files',
|
||||
'main',
|
||||
'module',
|
||||
'exports',
|
||||
'unpkg',
|
||||
'jsdelivr',
|
||||
'browser',
|
||||
'bin',
|
||||
'man',
|
||||
'directories',
|
||||
'repository',
|
||||
'publishConfig',
|
||||
'scripts',
|
||||
'peerDependencies',
|
||||
'peerDependenciesMeta',
|
||||
'optionalDependencies',
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'engines',
|
||||
'config',
|
||||
'overrides',
|
||||
'pnpm',
|
||||
'husky',
|
||||
'lint-staged',
|
||||
'eslintConfig',
|
||||
],
|
||||
},
|
||||
{
|
||||
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$',
|
||||
order: { type: 'asc' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
globals: {
|
||||
$: true,
|
||||
jQuery: true,
|
||||
glob: true,
|
||||
log: true,
|
||||
EditorWatchdog: true,
|
||||
React: true,
|
||||
appState: true,
|
||||
ExcalidrawLib: true,
|
||||
elements: true,
|
||||
files: true,
|
||||
ReactDOM: true,
|
||||
// src\public\app\widgets\type_widgets\relation_map.js
|
||||
jsPlumb: true,
|
||||
panzoom: true,
|
||||
logError: true,
|
||||
// src\public\app\widgets\type_widgets\image.js
|
||||
WZoom: true,
|
||||
// \src\public\app\widgets\type_widgets\read_only_text.js
|
||||
renderMathInElement: true,
|
||||
// \src\public\app\widgets\type_widgets\editable_text.js
|
||||
BalloonEditor: true,
|
||||
FancytreeNode: true,
|
||||
CKEditorInspector: true,
|
||||
// \src\public\app\widgets\type_widgets\editable_code.js
|
||||
CodeMirror: true,
|
||||
// \src\public\app\services\resizer.js
|
||||
Split: true,
|
||||
// \src\public\app\services\content_renderer.js
|
||||
mermaid: true,
|
||||
// src\public\app\services\frontend_script_api.js
|
||||
dayjs: true,
|
||||
// \src\public\app\widgets\note_map.js
|
||||
ForceGraph: true,
|
||||
// \src\public\app\setup.js
|
||||
ko: true,
|
||||
syncInProgress: true,
|
||||
// src\public\app\services\utils.js
|
||||
logInfo: true,
|
||||
__non_webpack_require__: true,
|
||||
describe: true,
|
||||
it: true,
|
||||
expect: true
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
// eslint:recommended
|
||||
'no-unused-vars': 'off',
|
||||
'linebreak-style': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'no-empty': 'off',
|
||||
'no-constant-condition': 'off',
|
||||
'getter-return': 'off',
|
||||
'no-cond-assign': 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-extra-semi': 'off',
|
||||
'no-inner-declarations': 'off',
|
||||
|
||||
// prettier
|
||||
'prettier/prettier': ['off', { endOfLine: 'auto' }],
|
||||
|
||||
// airbnb-base
|
||||
'no-console': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'global-require': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'no-await-in-loop': 'off',
|
||||
radix: 'off',
|
||||
'import/order': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'prefer-destructuring': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-new': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
strict: 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'no-else-return': 'off',
|
||||
'import/no-dynamic-require': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'prefer-template': 'off',
|
||||
'consistent-return': 'off',
|
||||
'no-continue': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'one-var': 'off',
|
||||
'prefer-const': 'off',
|
||||
'spaced-comment': 'off',
|
||||
'no-loop-func': 'off',
|
||||
'arrow-body-style': 'off',
|
||||
|
||||
'guard-for-in': 'off',
|
||||
'no-return-assign': 'off',
|
||||
'dot-notation': 'off',
|
||||
|
||||
'func-names': 'off',
|
||||
'import/no-useless-path-segments': 'off',
|
||||
'default-param-last': 'off',
|
||||
'prefer-arrow-callback': 'off',
|
||||
'no-unneeded-ternary': 'off',
|
||||
'no-return-await': 'off',
|
||||
'import/extensions': 'off',
|
||||
|
||||
'no-var': 'off',
|
||||
'import/newline-after-import': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'operator-assignment': 'off',
|
||||
'no-eval': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'vars-on-top': 'off',
|
||||
'no-bitwise': 'off',
|
||||
'no-lonely-if': 'off',
|
||||
'no-multi-assign': 'off',
|
||||
'no-promise-executor-return': 'off',
|
||||
'no-empty-function': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
camelcase: 'off',
|
||||
eqeqeq: 'off',
|
||||
'lines-between-class-members': 'off',
|
||||
'import/no-cycle': 'off',
|
||||
'new-cap': 'off',
|
||||
'prefer-object-spread': 'off',
|
||||
'no-new-func': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'lines-around-directive': 'off',
|
||||
'prefer-exponentiation-operator': 'off',
|
||||
'no-restricted-properties': 'off',
|
||||
'prefer-rest-params': 'off',
|
||||
'no-unreachable-loop': 'off',
|
||||
'no-alert': 'off',
|
||||
'no-useless-return': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'prefer-regex-literals': 'off',
|
||||
'import/no-named-as-default-member': 'off',
|
||||
yoda: 'off',
|
||||
'no-script-url': 'off',
|
||||
'no-prototype-builtins':'off'
|
||||
},
|
||||
};
|
||||
21
.gitattributes
vendored
21
.gitattributes
vendored
@@ -1,21 +0,0 @@
|
||||
# Mark files as auto-generated to simplify reviews.
|
||||
package-lock.json linguist-generated=true
|
||||
**/package-lock.json linguist-generated=true
|
||||
apps/server/src/assets/doc_notes/en/User[[:space:]]Guide/** linguist-generated
|
||||
|
||||
# Ignore from GitHub language stats.
|
||||
apps/server/src/assets/doc_notes/en/User[[:space:]]Guide/**/*.html eol=lf
|
||||
apps/server/src/assets/doc_notes/** linguist-vendored=true
|
||||
apps/edit-docs/demo/** linguist-vendored=true
|
||||
docs/** linguist-vendored=true
|
||||
|
||||
# Normalize line endings.
|
||||
docs/**/*.md eol=lf
|
||||
docs/**/*.json eol=lf
|
||||
demo/**/*.html eol=lf
|
||||
demo/**/*.json eol=lf
|
||||
demo/**/*.svg eol=lf
|
||||
demo/**/*.txt eol=lf
|
||||
demo/**/*.js eol=lf
|
||||
demo/**/*.css eol=lf
|
||||
*.sh eol=lf
|
||||
6
.github/FUNDING.yml
vendored
6
.github/FUNDING.yml
vendored
@@ -1,6 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [eliandoran]
|
||||
custom: ["https://paypal.me/eliandoran"]
|
||||
liberapay: ElianDoran
|
||||
buy_me_a_coffee: eliandoran
|
||||
github: [zadam]
|
||||
custom: ["https://paypal.me/za4am"]
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
25
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,18 +1,13 @@
|
||||
name: Bug Report
|
||||
description: Report a bug
|
||||
type: "Bug"
|
||||
title: "(Bug report) "
|
||||
labels: "Type: Bug"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the bug and any additional information.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: TriliumNext Version
|
||||
description: What version of TriliumNext are you using?
|
||||
placeholder: 0.90.0-beta
|
||||
label: Trilium Version
|
||||
description: What version of Trilium are you using?
|
||||
placeholder: 0.57.0-beta
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -29,7 +24,7 @@ body:
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: What is your setup?
|
||||
description: https://triliumnext.github.io/Docs/Wiki/quick-start.html
|
||||
description: https://github.com/zadam/trilium/wiki#choose-the-setup
|
||||
options:
|
||||
- Local (no sync)
|
||||
- Local + server sync
|
||||
@@ -43,9 +38,15 @@ body:
|
||||
placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the bug and any additional information.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Error logs
|
||||
description: Please provide error logs, see [wiki page](https://triliumnext.github.io/Docs/Wiki/error-logs.html) for instructions on how to submit them.
|
||||
description: Please provide error logs, see [wiki page](https://github.com/zadam/trilium/wiki/Error-logs) for instructions on how to submit them.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
5
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,11 +1,12 @@
|
||||
name: Feature Request
|
||||
description: Ask for a new feature to be added
|
||||
type: "Feature"
|
||||
title: "(Feature request) "
|
||||
labels: "Type: Enhancement"
|
||||
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
10
.github/ISSUE_TEMPLATE/task.yml
vendored
@@ -1,10 +0,0 @@
|
||||
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
|
||||
187
.github/actions/build-electron/action.yml
vendored
187
.github/actions/build-electron/action.yml
vendored
@@ -1,187 +0,0 @@
|
||||
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
|
||||
shell:
|
||||
description: "Which shell to use"
|
||||
required: true
|
||||
forge_platform:
|
||||
description: "The --platform to pass to Electron Forge"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# Certificate setup
|
||||
- name: Import Apple certificates
|
||||
if: inputs.os == 'macos'
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ env.APPLE_APP_CERTIFICATE_BASE64 }}
|
||||
p12-password: ${{ env.APPLE_APP_CERTIFICATE_PASSWORD }}
|
||||
keychain: build-app-${{ github.run_id }}
|
||||
keychain-password: ${{ github.run_id }}
|
||||
|
||||
- name: Install Installer certificate
|
||||
if: inputs.os == 'macos'
|
||||
uses: apple-actions/import-codesign-certs@v5
|
||||
with:
|
||||
p12-file-base64: ${{ env.APPLE_INSTALLER_CERTIFICATE_BASE64 }}
|
||||
p12-password: ${{ env.APPLE_INSTALLER_CERTIFICATE_PASSWORD }}
|
||||
keychain: build-installer-${{ github.run_id }}
|
||||
keychain-password: ${{ github.run_id }}
|
||||
|
||||
- name: Verify certificates
|
||||
if: inputs.os == 'macos'
|
||||
shell: ${{ inputs.shell }}
|
||||
run: |
|
||||
echo "Available signing identities in app keychain:"
|
||||
security find-identity -v -p codesigning build-app-${{ github.run_id }}.keychain
|
||||
|
||||
echo "Available signing identities in installer keychain:"
|
||||
security find-identity -v -p codesigning build-installer-${{ github.run_id }}.keychain
|
||||
|
||||
# Make the keychains searchable
|
||||
security list-keychains -d user -s build-app-${{ github.run_id }}.keychain build-installer-${{ github.run_id }}.keychain $(security list-keychains -d user | tr -d '"')
|
||||
security default-keychain -s build-app-${{ github.run_id }}.keychain
|
||||
security unlock-keychain -p ${{ github.run_id }} build-app-${{ github.run_id }}.keychain
|
||||
security unlock-keychain -p ${{ github.run_id }} build-installer-${{ github.run_id }}.keychain
|
||||
security set-keychain-settings -t 3600 -l build-app-${{ github.run_id }}.keychain
|
||||
security set-keychain-settings -t 3600 -l build-installer-${{ github.run_id }}.keychain
|
||||
|
||||
- name: Set up Python and other macOS dependencies
|
||||
if: ${{ inputs.os == 'macos' }}
|
||||
shell: ${{ inputs.shell }}
|
||||
run: |
|
||||
brew install python-setuptools
|
||||
brew install create-dmg
|
||||
|
||||
- name: Install dependencies for RPM and Flatpak package building
|
||||
if: ${{ inputs.os == 'linux' }}
|
||||
shell: ${{ inputs.shell }}
|
||||
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
|
||||
|
||||
- name: Update build info
|
||||
shell: ${{ inputs.shell }}
|
||||
run: pnpm run chore:update-build-info
|
||||
|
||||
# Critical debugging configuration
|
||||
- name: Run electron-forge build with enhanced logging
|
||||
shell: ${{ inputs.shell }}
|
||||
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 }}
|
||||
WINDOWS_SIGN_EXECUTABLE: ${{ env.WINDOWS_SIGN_EXECUTABLE }}
|
||||
TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}
|
||||
TARGET_ARCH: ${{ inputs.arch }}
|
||||
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
|
||||
|
||||
# Add DMG signing step
|
||||
- name: Sign DMG
|
||||
if: inputs.os == 'macos'
|
||||
shell: ${{ inputs.shell }}
|
||||
run: |
|
||||
echo "Signing DMG file..."
|
||||
dmg_file=$(find ./apps/desktop/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-app-${{ github.run_id }}.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: ${{ inputs.shell }}
|
||||
run: |
|
||||
echo "Verifying code signing for all artifacts..."
|
||||
|
||||
# First check the .app bundle
|
||||
echo "Looking for .app bundle..."
|
||||
app_bundle=$(find ./apps/desktop/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 ./apps/desktop/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 ./apps/desktop/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: Sign the RPM
|
||||
if: inputs.os == 'linux'
|
||||
shell: ${{ inputs.shell }}
|
||||
run: |
|
||||
echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --import
|
||||
|
||||
# Import the key into RPM for verification
|
||||
gpg --export -a > pubkey
|
||||
rpm --import pubkey
|
||||
rm pubkey
|
||||
|
||||
# Sign the RPM
|
||||
rpm_file=$(find ./apps/desktop/upload -name "*.rpm" -print -quit)
|
||||
rpmsign --define "_gpg_name Trilium Notes Signing Key <triliumnotes@outlook.com>" --addsign "$rpm_file"
|
||||
rpm -Kv "$rpm_file"
|
||||
|
||||
# Validate code signing
|
||||
if ! rpm -K "$rpm_file" | grep -q "digests signatures OK"; then
|
||||
echo .rpm file not signed
|
||||
exit 1
|
||||
fi
|
||||
33
.github/actions/build-server/action.yml
vendored
33
.github/actions/build-server/action.yml
vendored
@@ -1,33 +0,0 @@
|
||||
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:
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run Linux server build
|
||||
env:
|
||||
MATRIX_ARCH: ${{ inputs.arch }}
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm run chore:update-build-info
|
||||
pnpm run --filter server package
|
||||
- name: Prepare artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p upload
|
||||
file=$(find ./apps/server/out -name '*.tar.xz' -print -quit)
|
||||
name=${{ github.ref_name }}
|
||||
cp "$file" "upload/TriliumNotes-Server-${name//\//-}-${{ inputs.os }}-${{ inputs.arch }}.tar.xz"
|
||||
@@ -1,103 +0,0 @@
|
||||
name: "Deploy to Cloudflare Pages"
|
||||
description: "Deploys to Cloudflare Pages on either a temporary branch with preview comment, or on the production version if on the main branch."
|
||||
inputs:
|
||||
project_name:
|
||||
description: "CloudFlare Pages project name"
|
||||
comment_body:
|
||||
description: "The message to display when deployment is ready"
|
||||
default: "Deployment is ready."
|
||||
required: false
|
||||
production_url:
|
||||
description: "The URL to mention as the production URL."
|
||||
required: true
|
||||
deploy_dir:
|
||||
description: "The directory from which to deploy."
|
||||
required: true
|
||||
cloudflare_api_token:
|
||||
description: "The Cloudflare API token to use for deployment."
|
||||
required: true
|
||||
cloudflare_account_id:
|
||||
description: "The Cloudflare account ID to use for deployment."
|
||||
required: true
|
||||
github_token:
|
||||
description: "The GitHub token to use for posting PR comments."
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# Install wrangler globally to avoid workspace issues
|
||||
- name: Install Wrangler
|
||||
shell: bash
|
||||
run: npm install -g wrangler
|
||||
|
||||
# Deploy using Wrangler (use pre-installed wrangler)
|
||||
- name: Deploy to Cloudflare Pages
|
||||
id: deploy
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ inputs.cloudflare_api_token }}
|
||||
accountId: ${{ inputs.cloudflare_account_id }}
|
||||
command: pages deploy ${{ inputs.deploy_dir }} --project-name=${{ inputs.project_name}} --branch=${{ github.ref_name }}
|
||||
wranglerVersion: '' # Use pre-installed version
|
||||
|
||||
# Deploy preview for PRs
|
||||
- name: Deploy Preview to Cloudflare Pages
|
||||
id: preview-deployment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ inputs.cloudflare_api_token }}
|
||||
accountId: ${{ inputs.cloudflare_account_id }}
|
||||
command: pages deploy ${{ inputs.deploy_dir }} --project-name=${{ inputs.project_name}} --branch=pr-${{ github.event.pull_request.number }}
|
||||
wranglerVersion: '' # Use pre-installed version
|
||||
|
||||
# Post deployment URL as PR comment
|
||||
- name: Comment PR with Preview URL
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
COMMENT_BODY: ${{ inputs.comment_body }}
|
||||
PRODUCTION_URL: ${{ inputs.production_url }}
|
||||
PROJECT_NAME: ${{ inputs.project_name }}
|
||||
with:
|
||||
github-token: ${{ inputs.github_token }}
|
||||
script: |
|
||||
const prNumber = context.issue.number;
|
||||
// Construct preview URL based on Cloudflare Pages pattern
|
||||
const projectName = process.env.PROJECT_NAME;
|
||||
const previewUrl = `https://pr-${prNumber}.${projectName}.pages.dev`;
|
||||
|
||||
// Check if we already commented
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const customMessage = process.env.COMMENT_BODY;
|
||||
const botComment = comments.data.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes(customMessage)
|
||||
);
|
||||
|
||||
const mainUrl = process.env.PRODUCTION_URL;
|
||||
const commentBody = `${customMessage}!\n\n🔗 Preview URL: ${previewUrl}\n📖 Production URL: ${mainUrl}\n\n✅ All checks passed\n\n_This preview will be updated automatically with new commits._`;
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: commentBody
|
||||
});
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: prNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody
|
||||
});
|
||||
}
|
||||
79
.github/actions/report-size/action.yml
vendored
79
.github/actions/report-size/action.yml
vendored
@@ -1,79 +0,0 @@
|
||||
name: 'Bundle size reporter'
|
||||
description: 'Post bundle size difference compared to another branch'
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to compare to'
|
||||
required: true
|
||||
default: 'main'
|
||||
paths:
|
||||
description:
|
||||
'Paths to json file bundle size report or folder containing bundles'
|
||||
required: true
|
||||
default: '/'
|
||||
onlyDiff:
|
||||
description: 'Report only different sizes'
|
||||
required: false
|
||||
default: 'false'
|
||||
filter:
|
||||
description: 'Regex filter based on file path'
|
||||
required: false
|
||||
unit:
|
||||
description: 'Size unit'
|
||||
required: false
|
||||
default: 'KB'
|
||||
|
||||
# Comment inputs
|
||||
comment:
|
||||
description: 'Post comment'
|
||||
required: false
|
||||
default: 'true'
|
||||
header:
|
||||
description: 'Comment header'
|
||||
required: false
|
||||
default: 'Bundle size report'
|
||||
append:
|
||||
description: 'Append comment'
|
||||
required: false
|
||||
default: 'false'
|
||||
ghToken:
|
||||
description: 'Github token'
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
# Checkout branch to compare to [required]
|
||||
- name: Checkout base branch
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ inputs.branch }}
|
||||
path: br-base
|
||||
token: ${{ inputs.ghToken }}
|
||||
|
||||
# Generate the bundle size difference report [required]
|
||||
- name: Generate report
|
||||
id: bundleSize
|
||||
uses: nejcm/bundle-size-reporter-action@v1.4.1
|
||||
with:
|
||||
paths: ${{ inputs.paths }}
|
||||
onlyDiff: ${{ inputs.onlyDiff }}
|
||||
filter: ${{ inputs.filter }}
|
||||
unit: ${{ inputs.unit }}
|
||||
|
||||
# Post github action summary
|
||||
- name: Post summary
|
||||
if: ${{ steps.bundleSize.outputs.hasDifferences == 'true' }} # post only in case of changes
|
||||
run: |
|
||||
echo '${{ steps.bundleSize.outputs.summary }}' >> $GITHUB_STEP_SUMMARY
|
||||
shell: bash
|
||||
|
||||
# Post github action comment
|
||||
- name: Post comment
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
if: ${{ steps.bundleSize.outputs.hasDifferences == 'true' }} # post only in case of changes
|
||||
with:
|
||||
number: ${{ github.event.pull_request.number }}
|
||||
header: ${{ inputs.header }}
|
||||
append: ${{ inputs.append }}
|
||||
message: '${{ steps.bundleSize.outputs.summary }}'
|
||||
GITHUB_TOKEN: ${{ inputs.ghToken }}
|
||||
18
.github/workflows/checks.yml
vendored
18
.github/workflows/checks.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Checks
|
||||
on:
|
||||
push:
|
||||
pull_request_target:
|
||||
types: [synchronize]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Check if PRs have conflicts
|
||||
uses: eps1lon/actions-label-merge-conflict@v3
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
with:
|
||||
dirtyLabel: "merge-conflicts"
|
||||
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"
|
||||
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '37 4 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
100
.github/workflows/codeql.yml
vendored
100
.github/workflows/codeql.yml
vendored
@@ -1,100 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '20 7 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||
# or others). This is typically only required for manual builds.
|
||||
# - name: Setup runtime (example)
|
||||
# uses: actions/setup-example@v1
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
129
.github/workflows/deploy-docs.yml
vendored
129
.github/workflows/deploy-docs.yml
vendored
@@ -1,129 +0,0 @@
|
||||
# GitHub Actions workflow for deploying MkDocs documentation to Cloudflare Pages
|
||||
# This workflow builds and deploys your MkDocs site when changes are pushed to main
|
||||
name: Deploy MkDocs Documentation
|
||||
|
||||
on:
|
||||
# Trigger on push to main branch
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master # Also support master branch
|
||||
# Only run when docs files change
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- 'README.md' # README is synced to docs/index.md
|
||||
- 'mkdocs.yml'
|
||||
- 'requirements-docs.txt'
|
||||
- '.github/workflows/deploy-docs.yml'
|
||||
- 'scripts/fix-mkdocs-structure.ts'
|
||||
|
||||
# Allow manual triggering from Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Run on pull requests for preview deployments
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- 'README.md' # README is synced to docs/index.md
|
||||
- 'mkdocs.yml'
|
||||
- 'requirements-docs.txt'
|
||||
- '.github/workflows/deploy-docs.yml'
|
||||
- 'scripts/fix-mkdocs-structure.ts'
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
name: Build and Deploy MkDocs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
# Required permissions for deployment
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write # For PR preview comments
|
||||
id-token: write # For OIDC authentication (if needed)
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for git info and mkdocs-git-revision-date plugin
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.14'
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'requirements-docs.txt'
|
||||
|
||||
- name: Install MkDocs and Dependencies
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements-docs.txt
|
||||
env:
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||
|
||||
# Setup pnpm before fixing docs structure
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
# Setup Node.js with pnpm
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'pnpm'
|
||||
|
||||
# Install Node.js dependencies for the TypeScript script
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
- name: Fix Documentation Structure
|
||||
run: |
|
||||
# Fix duplicate navigation entries by moving overview pages to index.md
|
||||
pnpm run chore:fix-mkdocs-structure
|
||||
|
||||
- name: Build MkDocs Site
|
||||
run: |
|
||||
# Build with strict mode but allow expected warnings
|
||||
mkdocs build --verbose || {
|
||||
EXIT_CODE=$?
|
||||
# Check if the only issue is expected warnings
|
||||
if mkdocs build 2>&1 | grep -E "WARNING.*(README|not found)" && \
|
||||
[ $(mkdocs build 2>&1 | grep -c "ERROR") -eq 0 ]; then
|
||||
echo "✅ Build succeeded with expected warnings"
|
||||
mkdocs build --verbose
|
||||
else
|
||||
echo "❌ Build failed with unexpected errors"
|
||||
exit $EXIT_CODE
|
||||
fi
|
||||
}
|
||||
|
||||
- name: Fix HTML Links
|
||||
run: |
|
||||
# Remove .md extensions from links in generated HTML
|
||||
pnpm tsx ./scripts/fix-html-links.ts site
|
||||
|
||||
- name: Validate Built Site
|
||||
run: |
|
||||
# Basic validation that important files exist
|
||||
test -f site/index.html || (echo "ERROR: site/index.html not found" && exit 1)
|
||||
test -f site/sitemap.xml || (echo "ERROR: site/sitemap.xml not found" && exit 1)
|
||||
test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1)
|
||||
echo "✅ Site validation passed"
|
||||
|
||||
- name: Deploy
|
||||
uses: ./.github/actions/deploy-to-cloudflare-pages
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
with:
|
||||
project_name: "trilium-docs"
|
||||
comment_body: "📚 Documentation preview is ready"
|
||||
production_url: "https://docs.triliumnotes.org"
|
||||
deploy_dir: "site"
|
||||
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
128
.github/workflows/dev.yml
vendored
128
.github/workflows/dev.yml
vendored
@@ -1,128 +0,0 @@
|
||||
name: Dev
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GHCR_REGISTRY: ghcr.io
|
||||
DOCKERHUB_REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ github.repository}}
|
||||
TEST_TAG: ${{ github.repository}}:test
|
||||
|
||||
permissions:
|
||||
pull-requests: write # for PR comments
|
||||
|
||||
jobs:
|
||||
test_dev:
|
||||
name: Test development
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: Run the unit tests
|
||||
run: pnpm run test:all
|
||||
|
||||
build_docker:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test_dev
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Update build info
|
||||
run: pnpm run chore:update-build-info
|
||||
- name: Trigger client build
|
||||
run: pnpm client:build
|
||||
- name: Send client bundle stats to RelativeCI
|
||||
if: false
|
||||
uses: relative-ci/agent-action@v3
|
||||
with:
|
||||
webpackStatsFile: ./apps/client/dist/webpack-stats.json
|
||||
key: ${{ secrets.RELATIVE_CI_CLIENT_KEY }}
|
||||
- name: Trigger server build
|
||||
run: pnpm run server:build
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: apps/server
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
test_docker:
|
||||
name: Check Docker build
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build_docker
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: Dockerfile.alpine
|
||||
- dockerfile: Dockerfile
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Update build info
|
||||
run: pnpm run chore:update-build-info
|
||||
- name: Trigger build
|
||||
run: pnpm server:build
|
||||
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and export to Docker
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: apps/server
|
||||
file: apps/server/${{ matrix.dockerfile }}
|
||||
load: true
|
||||
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:
|
||||
container: trilium_local
|
||||
wait-time: 50
|
||||
require-status: running
|
||||
require-healthy: true
|
||||
|
||||
# Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
|
||||
- name: Print entire log
|
||||
if: always()
|
||||
run: journalctl -u docker CONTAINER_NAME=trilium_local --no-pager
|
||||
53
.github/workflows/docker.yaml
vendored
Normal file
53
.github/workflows/docker.yaml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Publish Docker image
|
||||
on:
|
||||
push:
|
||||
tags: [v*]
|
||||
jobs:
|
||||
push_to_registries:
|
||||
name: Push Docker image to multiple registries
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
zadam/trilium
|
||||
ghcr.io/zadam/trilium
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}-latest
|
||||
type=match,pattern=(\d+.\d+).\d+\-beta,enable=${{ endsWith(github.ref, 'beta') }},group=1,suffix=-latest
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
install: true
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to GitHub Docker Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create server-package.json
|
||||
run: cat package.json | grep -v electron > server-package.json
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v2.7.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
cache-from: type=registry,ref=zadam/trilium:buildcache
|
||||
cache-to: type=registry,ref=zadam/trilium:buildcache,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
307
.github/workflows/main-docker.yml
vendored
307
.github/workflows/main-docker.yml
vendored
@@ -1,307 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "feature/update**"
|
||||
- "feature/server_esm**"
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "bin/**"
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GHCR_REGISTRY: ghcr.io
|
||||
DOCKERHUB_REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ github.repository}}
|
||||
TEST_TAG: ${{ github.repository}}:test
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
test_docker:
|
||||
name: Check Docker build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: Dockerfile.alpine
|
||||
- dockerfile: Dockerfile
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
|
||||
- name: Run the TypeScript build
|
||||
run: pnpm run server:build
|
||||
|
||||
- name: Build and export to Docker
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: apps/server
|
||||
file: apps/server/${{ matrix.dockerfile }}
|
||||
load: true
|
||||
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 ./apps/server/spec/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:
|
||||
container: trilium_local
|
||||
wait-time: 50
|
||||
require-status: running
|
||||
require-healthy: true
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpm --filter=server-e2e e2e
|
||||
|
||||
- name: Upload Playwright trace
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: Playwright trace (${{ matrix.dockerfile }})
|
||||
path: test-output/playwright/output
|
||||
|
||||
- uses: actions/upload-artifact@v5
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: Playwright report (${{ matrix.dockerfile }})
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
# Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
|
||||
- name: Print entire log
|
||||
if: always()
|
||||
run: |
|
||||
journalctl -u docker CONTAINER_NAME=trilium_local --no-pager
|
||||
|
||||
build:
|
||||
name: Build Docker images
|
||||
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
|
||||
- dockerfile: Dockerfile
|
||||
platform: linux/arm/v8
|
||||
image: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.image }}
|
||||
needs:
|
||||
- test_docker
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Update build info
|
||||
run: pnpm run chore:update-build-info
|
||||
|
||||
- name: Run the TypeScript build
|
||||
run: pnpm run server:build
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
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: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
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
|
||||
with:
|
||||
context: apps/server
|
||||
file: apps/server/${{ matrix.dockerfile }}
|
||||
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@v5
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
name: Merge manifest lists
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
- name: Set IMAGE_NAME to lowercase
|
||||
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
|
||||
- name: Set TEST_TAG to lowercase
|
||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
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: |
|
||||
# Extract the branch or tag name from the ref
|
||||
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
|
||||
|
||||
# Create and push the manifest list with both the branch/tag name and the commit SHA
|
||||
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 ' *)
|
||||
|
||||
# If the ref is a tag, also tag the image as stable as this is part of a 'release'
|
||||
# and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc...
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* && ! "${REF_NAME}" =~ - ]]; then
|
||||
# First create stable tags
|
||||
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 }}
|
||||
docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
|
||||
130
.github/workflows/nightly.yml
vendored
130
.github/workflows/nightly.yml
vendored
@@ -1,130 +0,0 @@
|
||||
name: Nightly Release
|
||||
on:
|
||||
# This can be used to automatically publish nightlies at UTC nighttime
|
||||
schedule:
|
||||
- cron: "0 2 * * *" # run at 2 AM UTC
|
||||
# This can be used to allow manually triggering nightlies from the web interface
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- renovate/electron-forge*
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/build-electron/*
|
||||
- .github/workflows/nightly.yml
|
||||
- forge.config.ts
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GITHUB_RELEASE_ID: 179589950
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
nightly-electron:
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
name: Deploy nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
os:
|
||||
- name: macos
|
||||
image: macos-latest
|
||||
shell: bash
|
||||
forge_platform: darwin
|
||||
- name: linux
|
||||
image: ubuntu-22.04
|
||||
shell: bash
|
||||
forge_platform: linux
|
||||
- name: windows
|
||||
image: win-signing
|
||||
shell: cmd
|
||||
forge_platform: win32
|
||||
runs-on: ${{ matrix.os.image }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Update nightly version
|
||||
run: npm run chore:ci-update-nightly-version
|
||||
- name: Run the build
|
||||
uses: ./.github/actions/build-electron
|
||||
with:
|
||||
os: ${{ matrix.os.name }}
|
||||
arch: ${{ matrix.arch }}
|
||||
shell: ${{ matrix.os.shell }}
|
||||
forge_platform: ${{ matrix.os.forge_platform }}
|
||||
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 }}
|
||||
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
|
||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2.4.1
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
make_latest: false
|
||||
prerelease: true
|
||||
draft: false
|
||||
fail_on_unmatched_files: true
|
||||
files: apps/desktop/upload/*.*
|
||||
tag_name: nightly
|
||||
name: Nightly Build
|
||||
|
||||
- name: Publish artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }}
|
||||
path: apps/desktop/upload
|
||||
|
||||
nightly-server:
|
||||
if: github.repository == ${{ vars.REPO_MAIN }}
|
||||
name: Deploy server nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
include:
|
||||
- arch: x64
|
||||
runs-on: ubuntu-22.04
|
||||
- arch: arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Run the build
|
||||
uses: ./.github/actions/build-server
|
||||
with:
|
||||
os: linux
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2.4.1
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
with:
|
||||
make_latest: false
|
||||
prerelease: true
|
||||
draft: false
|
||||
fail_on_unmatched_files: true
|
||||
files: upload/*.*
|
||||
tag_name: nightly
|
||||
name: Nightly Build
|
||||
41
.github/workflows/playwright.yml
vendored
41
.github/workflows/playwright.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: playwright
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "apps/website/**"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
filter: tree:0
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: pnpm exec playwright install --with-deps
|
||||
|
||||
- run: pnpm --filter server-e2e e2e
|
||||
|
||||
- name: Upload test report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: e2e report
|
||||
path: apps/server-e2e/test-output
|
||||
20
.github/workflows/release-winget.yml
vendored
20
.github/workflows/release-winget.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Release to winget
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Git tag to release from'
|
||||
type: string
|
||||
required: true
|
||||
jobs:
|
||||
release-winget:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Publish to WinGet
|
||||
uses: vedantmgoyal9/winget-releaser@main
|
||||
with:
|
||||
identifier: TriliumNext.Notes
|
||||
token: ${{ secrets.WINGET_PAT }}
|
||||
release-tag: ${{ github.event.inputs.release_tag || github.event.release.tag_name }}
|
||||
139
.github/workflows/release.yml
vendored
139
.github/workflows/release.yml
vendored
@@ -1,139 +0,0 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
permissions:
|
||||
contents: write
|
||||
discussions: write
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
make-electron:
|
||||
name: Make Electron
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
os:
|
||||
- name: macos
|
||||
image: macos-latest
|
||||
shell: bash
|
||||
forge_platform: darwin
|
||||
- name: linux
|
||||
image: ubuntu-22.04
|
||||
shell: bash
|
||||
forge_platform: linux
|
||||
- name: windows
|
||||
image: win-signing
|
||||
shell: cmd
|
||||
forge_platform: win32
|
||||
# Exclude ARM64 Linux from default matrix to use native runner
|
||||
exclude:
|
||||
- arch: arm64
|
||||
os:
|
||||
name: linux
|
||||
# Add ARM64 Linux with native ubuntu-24.04-arm runner for better-sqlite3 compatibility
|
||||
include:
|
||||
- arch: arm64
|
||||
os:
|
||||
name: linux
|
||||
image: ubuntu-24.04-arm
|
||||
shell: bash
|
||||
forge_platform: linux
|
||||
runs-on: ${{ matrix.os.image }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'pnpm'
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run the build
|
||||
uses: ./.github/actions/build-electron
|
||||
with:
|
||||
os: ${{ matrix.os.name }}
|
||||
arch: ${{ matrix.arch }}
|
||||
shell: ${{ matrix.os.shell }}
|
||||
forge_platform: ${{ matrix.os.forge_platform }}
|
||||
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 }}
|
||||
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
|
||||
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
|
||||
|
||||
- name: Upload the artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }}
|
||||
path: apps/desktop/upload/*.*
|
||||
|
||||
build_server:
|
||||
name: Build Linux Server
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
include:
|
||||
- arch: x64
|
||||
runs-on: ubuntu-22.04
|
||||
- arch: arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Run the build
|
||||
uses: ./.github/actions/build-server
|
||||
with:
|
||||
os: linux
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
- name: Upload the artifact
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: release-server-linux-${{ matrix.arch }}
|
||||
path: upload/*.*
|
||||
|
||||
publish_release:
|
||||
name: Publish release
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- make-electron
|
||||
- build_server
|
||||
steps:
|
||||
- run: mkdir upload
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
sparse-checkout: |
|
||||
docs/Release Notes
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
merge-multiple: true
|
||||
pattern: release-*
|
||||
path: upload
|
||||
|
||||
- name: Publish stable release
|
||||
uses: softprops/action-gh-release@v2.4.1
|
||||
with:
|
||||
draft: false
|
||||
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md
|
||||
fail_on_unmatched_files: true
|
||||
files: upload/*.*
|
||||
discussion_category_name: Releases
|
||||
make_latest: ${{ !contains(github.ref, 'rc') }}
|
||||
prerelease: ${{ contains(github.ref, 'rc') }}
|
||||
token: ${{ secrets.RELEASE_PAT }}
|
||||
11
.github/workflows/unblock_signing.yml
vendored
11
.github/workflows/unblock_signing.yml
vendored
@@ -1,11 +0,0 @@
|
||||
name: Unblock signing
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unblock-win-signing:
|
||||
runs-on: win-signing
|
||||
steps:
|
||||
- run: |
|
||||
cat ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
|
||||
rm ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
|
||||
51
.github/workflows/website.yml
vendored
51
.github/workflows/website.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Deploy website
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/website/**"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/website/**"
|
||||
|
||||
release:
|
||||
types: [ released ]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build & deploy website
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write # For PR preview comments
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --filter website --frozen-lockfile
|
||||
|
||||
- name: Build the website
|
||||
run: pnpm website:build
|
||||
|
||||
- name: Deploy
|
||||
uses: ./.github/actions/deploy-to-cloudflare-pages
|
||||
with:
|
||||
project_name: "trilium-homepage"
|
||||
comment_body: "📚 Website preview is ready"
|
||||
production_url: "https://triliumnotes.org"
|
||||
deploy_dir: "apps/website/dist"
|
||||
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
63
.gitignore
vendored
63
.gitignore
vendored
@@ -1,51 +1,16 @@
|
||||
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
/.cache
|
||||
|
||||
# compiled output
|
||||
dist
|
||||
tmp
|
||||
out-tsc
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
src/public/app-dist/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
vite.config.*.timestamp*
|
||||
vitest.config.*.timestamp*
|
||||
test-output
|
||||
|
||||
apps/*/data*
|
||||
apps/*/out
|
||||
upload
|
||||
|
||||
.rollup.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
/result
|
||||
.svelte-kit
|
||||
|
||||
# docs
|
||||
site/
|
||||
*.db
|
||||
config.ini
|
||||
cert.key
|
||||
cert.crt
|
||||
server-package.json
|
||||
.idea/httpRequests/
|
||||
data/
|
||||
data-test/
|
||||
tmp/
|
||||
.eslintcache
|
||||
|
||||
15
.gitpod.dockerfile
Normal file
15
.gitpod.dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
RUN sudo apt-get update \
|
||||
&& sudo apt-get install -yq --no-install-recommends \
|
||||
libpng16-16 \
|
||||
libpng-dev \
|
||||
pkg-config \
|
||||
autoconf \
|
||||
libtool \
|
||||
build-essential \
|
||||
nasm \
|
||||
libx11-dev \
|
||||
libxkbfile-dev \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
|
||||
11
.gitpod.yml
Normal file
11
.gitpod.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
image:
|
||||
file: .gitpod.dockerfile
|
||||
|
||||
tasks:
|
||||
- before: nvm install 18.18.2 && nvm use 18.18.2
|
||||
init: npm install
|
||||
command: npm run start-server
|
||||
|
||||
ports:
|
||||
- port: 8080
|
||||
onOpen: open-preview
|
||||
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
_
|
||||
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
#npx lint-staged
|
||||
6
.idea/.gitignore
generated
vendored
Normal file
6
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Default ignored files
|
||||
/workspace.xml
|
||||
|
||||
# Datasource local storage ignored files
|
||||
/dataSources.local.xml
|
||||
/dataSources/
|
||||
13
.idea/codeStyles/Project.xml
generated
Normal file
13
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</value>
|
||||
</option>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="USE_EXPLICIT_JS_EXTENSION" value="TRUE" />
|
||||
</JSCodeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
12
.idea/dataSources.xml
generated
Normal file
12
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="document.db" uuid="2a4ac1e6-b828-4a2a-8e4a-3f59f10aff26">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/data/document.db</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
4
.idea/encodings.xml
generated
Normal file
4
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||
</project>
|
||||
15
.idea/git_toolbox_prj.xml
generated
Normal file
15
.idea/git_toolbox_prj.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxProjectSettings">
|
||||
<option name="commitMessageIssueKeyValidationOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
<option name="commitMessageValidationEnabledOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
11
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||
<option name="processCode" value="true" />
|
||||
<option name="processLiterals" value="true" />
|
||||
<option name="processComments" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/jsLibraryMappings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/jsLinters/eslint.xml
generated
Normal file
6
.idea/jsLinters/eslint.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EslintConfiguration">
|
||||
<option name="fix-on-save" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/jsLinters/jslint.xml
generated
Normal file
9
.idea/jsLinters/jslint.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JSLintConfiguration">
|
||||
<option devel="true" />
|
||||
<option es6="true" />
|
||||
<option maxerr="50" />
|
||||
<option node="true" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/misc.xml
generated
Normal file
8
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" default="true" project-jdk-name="openjdk-16" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/trilium.iml" filepath="$PROJECT_DIR$/trilium.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/sqldialects.xml
generated
Normal file
7
.idea/sqldialects.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$" dialect="SQLite" />
|
||||
<file url="PROJECT" dialect="SQLite" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
13
.prettierrc.js
Normal file
13
.prettierrc.js
Normal file
@@ -0,0 +1,13 @@
|
||||
//https://prettier.io/docs/en/options.html
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'none',
|
||||
singleQuote: true,
|
||||
printWidth: 100,
|
||||
tabWidth: 4,
|
||||
useTabs: false,
|
||||
quoteProps: "as-needed",
|
||||
bracketSpacing: true,
|
||||
arrowParens: "avoid"
|
||||
// htmlWhitespaceSensitivity: 'ignore',
|
||||
};
|
||||
11
.vscode/extensions.json
vendored
11
.vscode/extensions.json
vendored
@@ -1,15 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"lokalise.i18n-ally",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"ms-playwright.playwright",
|
||||
"redhat.vscode-yaml",
|
||||
"tobermory.es6-string-html",
|
||||
"vitest.explorer",
|
||||
"yzhang.markdown-all-in-one",
|
||||
"svelte.svelte-vscode",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
"esbenp.prettier-vscode",
|
||||
]
|
||||
}
|
||||
|
||||
36
.vscode/i18n-ally-custom-framework.yml
vendored
36
.vscode/i18n-ally-custom-framework.yml
vendored
@@ -1,36 +0,0 @@
|
||||
# An array of strings which contain Language Ids defined by VS Code
|
||||
# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers
|
||||
languageIds:
|
||||
- javascript
|
||||
- typescript
|
||||
- typescriptreact
|
||||
- html
|
||||
|
||||
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
usageMatchRegex:
|
||||
# The following example shows how to detect `t("your.i18n.keys")`
|
||||
# the `{key}` will be placed by a proper keypath matching regex,
|
||||
# you can ignore it and use your own matching rules as well
|
||||
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
|
||||
- <Trans\s*i18nKey="({key})"[^>]*>
|
||||
|
||||
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
|
||||
# and works like how the i18next framework identifies the namespace scope from the
|
||||
# useTranslation() hook.
|
||||
# You should unescape RegEx strings in order to fit in the YAML file
|
||||
# To help with this, you can use https://www.freeformatter.com/json-escape.html
|
||||
scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
|
||||
|
||||
# An array of strings containing refactor templates.
|
||||
# The "$1" will be replaced by the keypath specified.
|
||||
refactorTemplates:
|
||||
- t("$1")
|
||||
- {t("$1")}
|
||||
- ${t("$1")}
|
||||
- <%= t("$1") %>
|
||||
|
||||
|
||||
# If set to true, only enables this custom framework (will disable all built-in frameworks)
|
||||
monopoly: true
|
||||
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// nodemon should be installed globally, use npm i -g nodemon
|
||||
{
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"name": "nodemon start-server",
|
||||
"program": "${workspaceFolder}/src/www",
|
||||
"request": "launch",
|
||||
"restart": true,
|
||||
"runtimeExecutable": "nodemon",
|
||||
"env": {
|
||||
"TRILIUM_ENV": "dev",
|
||||
"TRILIUM_DATA_DIR": "./data"
|
||||
},
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node",
|
||||
"outputCapture": "std",
|
||||
},
|
||||
]
|
||||
}
|
||||
69
.vscode/settings.json
vendored
69
.vscode/settings.json
vendored
@@ -1,40 +1,33 @@
|
||||
{
|
||||
"editor.formatOnSave": false,
|
||||
"files.eol": "\n",
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.localesPaths": [
|
||||
"apps/server/src/assets/translations",
|
||||
"apps/client/src/translations",
|
||||
"apps/website/public/translations"
|
||||
],
|
||||
"npm.exclude": [
|
||||
"**/dist",
|
||||
],
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "vscode.css-language-features"
|
||||
},
|
||||
"github-actions.workflows.pinned.workflows": [
|
||||
".github/workflows/nightly.yml"
|
||||
],
|
||||
"typescript.validate.enable": true,
|
||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"docs/**/*.html": true,
|
||||
"docs/**/*.png": true,
|
||||
"apps/server/src/assets/doc_notes/**": true,
|
||||
"apps/edit-docs/demo/**": true
|
||||
}
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.format.enable": true,
|
||||
"eslint.probe": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"html",
|
||||
"vue",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc"
|
||||
],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"html",
|
||||
"vue",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc"
|
||||
],
|
||||
"files.eol": "\n",
|
||||
}
|
||||
29
.vscode/snippets.code-snippets
vendored
29
.vscode/snippets.code-snippets
vendored
@@ -1,29 +0,0 @@
|
||||
{
|
||||
// Place your Notes workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
|
||||
"JQuery HTMLElement field": {
|
||||
"scope": "typescript",
|
||||
"prefix": "jqf",
|
||||
"body": ["private $${1:name}!: JQuery<HTMLElement>;"]
|
||||
},
|
||||
"region": {
|
||||
"scope": "css",
|
||||
"prefix": "region",
|
||||
"body": ["/* #region ${1:name} */\n$0\n/* #endregion */"]
|
||||
}
|
||||
}
|
||||
152
CLAUDE.md
152
CLAUDE.md
@@ -1,152 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using pnpm, with multiple applications and shared packages.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Setup
|
||||
- `pnpm install` - Install all dependencies
|
||||
- `corepack enable` - Enable pnpm if not available
|
||||
|
||||
### Running Applications
|
||||
- `pnpm run server:start` - Start development server (http://localhost:8080)
|
||||
- `pnpm run server:start-prod` - Run server in production mode
|
||||
|
||||
### Building
|
||||
- `pnpm run client:build` - Build client application
|
||||
- `pnpm run server:build` - Build server application
|
||||
- `pnpm run electron:build` - Build desktop application
|
||||
|
||||
### Testing
|
||||
- `pnpm test:all` - Run all tests (parallel + sequential)
|
||||
- `pnpm test:parallel` - Run tests that can run in parallel
|
||||
- `pnpm test:sequential` - Run tests that must run sequentially (server, ckeditor5-mermaid, ckeditor5-math)
|
||||
- `pnpm coverage` - Generate coverage reports
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Monorepo Structure
|
||||
- **apps/**: Runnable applications
|
||||
- `client/` - Frontend application (shared by server and desktop)
|
||||
- `server/` - Node.js server with web interface
|
||||
- `desktop/` - Electron desktop application
|
||||
- `web-clipper/` - Browser extension for saving web content
|
||||
- Additional tools: `db-compare`, `dump-db`, `edit-docs`
|
||||
|
||||
- **packages/**: Shared libraries
|
||||
- `commons/` - Shared interfaces and utilities
|
||||
- `ckeditor5/` - Custom rich text editor with Trilium-specific plugins
|
||||
- `codemirror/` - Code editor customizations
|
||||
- `highlightjs/` - Syntax highlighting
|
||||
- Custom CKEditor plugins: `ckeditor5-admonition`, `ckeditor5-footnotes`, `ckeditor5-math`, `ckeditor5-mermaid`
|
||||
|
||||
### Core Architecture Patterns
|
||||
|
||||
#### Three-Layer Cache System
|
||||
- **Becca** (Backend Cache): Server-side entity cache (`apps/server/src/becca/`)
|
||||
- **Froca** (Frontend Cache): Client-side mirror of backend data (`apps/client/src/services/froca.ts`)
|
||||
- **Shaca** (Share Cache): Optimized cache for shared/published notes (`apps/server/src/share/`)
|
||||
|
||||
#### Entity System
|
||||
Core entities are defined in `apps/server/src/becca/entities/`:
|
||||
- `BNote` - Notes with content and metadata
|
||||
- `BBranch` - Hierarchical relationships between notes (allows multiple parents)
|
||||
- `BAttribute` - Key-value metadata attached to notes
|
||||
- `BRevision` - Note version history
|
||||
- `BOption` - Application configuration
|
||||
|
||||
#### Widget-Based UI
|
||||
Frontend uses a widget system (`apps/client/src/widgets/`):
|
||||
- `BasicWidget` - Base class for all UI components
|
||||
- `NoteContextAwareWidget` - Widgets that respond to note changes
|
||||
- `RightPanelWidget` - Widgets displayed in the right panel
|
||||
- Type-specific widgets in `type_widgets/` directory
|
||||
|
||||
#### API Architecture
|
||||
- **Internal API**: REST endpoints in `apps/server/src/routes/api/`
|
||||
- **ETAPI**: External API for third-party integrations (`apps/server/src/etapi/`)
|
||||
- **WebSocket**: Real-time synchronization (`apps/server/src/services/ws.ts`)
|
||||
|
||||
### Key Files for Understanding Architecture
|
||||
|
||||
1. **Application Entry Points**:
|
||||
- `apps/server/src/main.ts` - Server startup
|
||||
- `apps/client/src/desktop.ts` - Client initialization
|
||||
|
||||
2. **Core Services**:
|
||||
- `apps/server/src/becca/becca.ts` - Backend data management
|
||||
- `apps/client/src/services/froca.ts` - Frontend data synchronization
|
||||
- `apps/server/src/services/backend_script_api.ts` - Scripting API
|
||||
|
||||
3. **Database Schema**:
|
||||
- `apps/server/src/assets/db/schema.sql` - Core database structure
|
||||
|
||||
4. **Configuration**:
|
||||
- `package.json` - Project dependencies and scripts
|
||||
|
||||
## Note Types and Features
|
||||
|
||||
Trilium supports multiple note types, each with specialized widgets:
|
||||
- **Text**: Rich text with CKEditor5 (markdown import/export)
|
||||
- **Code**: Syntax-highlighted code editing with CodeMirror
|
||||
- **File**: Binary file attachments
|
||||
- **Image**: Image display with editing capabilities
|
||||
- **Canvas**: Drawing/diagramming with Excalidraw
|
||||
- **Mermaid**: Diagram generation
|
||||
- **Relation Map**: Visual note relationship mapping
|
||||
- **Web View**: Embedded web pages
|
||||
- **Doc/Book**: Hierarchical documentation structure
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Testing Strategy
|
||||
- Server tests run sequentially due to shared database
|
||||
- Client tests can run in parallel
|
||||
- E2E tests use Playwright for both server and desktop apps
|
||||
- Build validation tests check artifact integrity
|
||||
|
||||
### Scripting System
|
||||
Trilium provides powerful user scripting capabilities:
|
||||
- Frontend scripts run in browser context
|
||||
- Backend scripts run in Node.js context with full API access
|
||||
- Script API documentation available in `docs/Script API/`
|
||||
|
||||
### Internationalization
|
||||
- Translation files in `apps/client/src/translations/`
|
||||
- Supported languages: English, German, Spanish, French, Romanian, Chinese
|
||||
|
||||
### Security Considerations
|
||||
- Per-note encryption with granular protected sessions
|
||||
- CSRF protection for API endpoints
|
||||
- OpenID and TOTP authentication support
|
||||
- Sanitization of user-generated content
|
||||
|
||||
## Common Development Tasks
|
||||
|
||||
### Adding New Note Types
|
||||
1. Create widget in `apps/client/src/widgets/type_widgets/`
|
||||
2. Register in `apps/client/src/services/note_types.ts`
|
||||
3. Add backend handling in `apps/server/src/services/notes.ts`
|
||||
|
||||
### Extending Search
|
||||
- Search expressions handled in `apps/server/src/services/search/`
|
||||
- Add new search operators in search context files
|
||||
|
||||
### Custom CKEditor Plugins
|
||||
- Create new package in `packages/` following existing plugin structure
|
||||
- Register in `packages/ckeditor5/src/plugins.ts`
|
||||
|
||||
### Database Migrations
|
||||
- Add migration scripts in `apps/server/src/migrations/`
|
||||
- Update schema in `apps/server/src/assets/db/schema.sql`
|
||||
|
||||
## Build System Notes
|
||||
- Uses pnpm for monorepo management
|
||||
- Vite for fast development builds
|
||||
- ESBuild for production optimization
|
||||
- pnpm workspaces for dependency management
|
||||
- Docker support with multi-stage builds
|
||||
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!!
|
||||
FROM node:18.18.2-alpine
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Bundle app source
|
||||
COPY . .
|
||||
|
||||
COPY server-package.json package.json
|
||||
|
||||
# Install app dependencies
|
||||
RUN set -x \
|
||||
&& apk add --no-cache --virtual .build-dependencies \
|
||||
autoconf \
|
||||
automake \
|
||||
g++ \
|
||||
gcc \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
libpng-dev \
|
||||
python3 \
|
||||
&& npm install \
|
||||
&& apk del .build-dependencies \
|
||||
&& npm run webpack \
|
||||
&& npm prune --omit=dev \
|
||||
&& 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
|
||||
|
||||
# Some setup tools need to be kept
|
||||
RUN apk add --no-cache su-exec shadow
|
||||
|
||||
# Add application user and setup proper volume permissions
|
||||
RUN adduser -s /bin/false node; exit 0
|
||||
|
||||
# Start the application
|
||||
EXPOSE 8080
|
||||
CMD [ "./start-docker.sh" ]
|
||||
|
||||
HEALTHCHECK --start-period=10s CMD exec su-exec node node docker_healthcheck.js
|
||||
82
README-ZH_CN.md
Normal file
82
README-ZH_CN.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Trilium Notes
|
||||
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
Trilium Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://github.com/zadam/trilium/wiki/Screenshot-tour)以快速了解:
|
||||
|
||||

|
||||
|
||||
Ukraine is currently suffering from Russian aggression, please consider donating to [one of these charities](https://old.reddit.com/r/ukraine/comments/s6g5un/want_to_support_ukraine_heres_a_list_of_charities/).
|
||||
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="600"/>
|
||||
|
||||
## 特性
|
||||
|
||||
* 笔记可以排列成任意深的树。单个笔记可以放在树中的多个位置(请参阅[克隆](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||
* 丰富的所见即所得笔记编辑功能,包括带有 Markdown [自动格式化功能的](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)表格,图像和[数学](https://github.com/zadam/trilium/wiki/Text-notes#math-support)
|
||||
* 支持编辑[使用源代码的笔记](https://github.com/zadam/trilium/wiki/Code-notes),包括语法高亮显示
|
||||
* 笔记之间快速[导航](https://github.com/zadam/trilium/wiki/Note-navigation),全文搜索和[笔记聚焦](https://github.com/zadam/trilium/wiki/Note-hoisting)
|
||||
* 无缝[笔记版本控制](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||
* 笔记[属性](https://github.com/zadam/trilium/wiki/Attributes)可用于笔记组织,查询和高级[脚本编写](https://github.com/zadam/trilium/wiki/Scripts)
|
||||
* [同步](https://github.com/zadam/trilium/wiki/Synchronization)与自托管同步服务器
|
||||
* 有一个[第三方提供的同步服务器托管服务](https://trilium.cc/paid-hosting)
|
||||
* 公开地[分享](https://github.com/zadam/trilium/wiki/Sharing)(发布)笔记到互联网
|
||||
* 具有按笔记粒度的强大的[笔记加密](https://github.com/zadam/trilium/wiki/Protected-notes)
|
||||
* 使用自带的 Excalidraw 来绘制图表(笔记类型“画布”)
|
||||
* [关系图](https://github.com/zadam/trilium/wiki/Relation-map)和[链接图](https://github.com/zadam/trilium/wiki/Link-map),用于可视化笔记及其关系
|
||||
* [脚本](https://github.com/zadam/trilium/wiki/Scripts) - 请参阅[高级功能展示](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
||||
* 在拥有超过 10 万条笔记时仍能保持良好的可用性和性能
|
||||
* 针对智能手机和平板电脑进行优化的[用于移动设备的前端](https://github.com/zadam/trilium/wiki/Mobile-frontend)
|
||||
* [夜间主题](https://github.com/zadam/trilium/wiki/Themes)
|
||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) 和 [Markdown 导入导出](https://github.com/zadam/trilium/wiki/Markdown)功能
|
||||
* 使用[网页剪藏](https://github.com/zadam/trilium/wiki/Web-clipper)轻松保存互联网上的内容
|
||||
|
||||
## 构建
|
||||
|
||||
Trilium 可以用作桌面应用程序(Linux 和 Windows)或服务器(Linux)上托管的 Web 应用程序。虽然有 macOS 版本的桌面应用程序,但它[不受支持](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support)。
|
||||
|
||||
* 如果要在桌面上使用 Trilium,请从[最新版本](https://github.com/zadam/trilium/releases/latest)下载适用于您平台的二进制版本,解压缩该软件包并运行`trilium`可执行文件。
|
||||
* 如果要在服务器上安装 Trilium,请参考[此页面](https://github.com/zadam/trilium/wiki/Server-installation)。
|
||||
* 当前仅支持(测试过)最近发布的 Chrome 和 Firefox 浏览器。
|
||||
|
||||
Trilium 也提供 Flatpak:
|
||||
|
||||
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
|
||||
|
||||
## 文档
|
||||
|
||||
[有关文档页面的完整列表,请参见 Wiki。](https://github.com/zadam/trilium/wiki/)
|
||||
|
||||
* [Wiki 的中文翻译版本](https://github.com/baddate/trilium/wiki/)
|
||||
|
||||
您还可以阅读[个人知识库模式](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base),以获取有关如何使用 Trilium 的灵感。
|
||||
|
||||
## 贡献
|
||||
|
||||
使用基于浏览器的开发环境
|
||||
|
||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||
|
||||
或者克隆本仓库到本地,并运行
|
||||
|
||||
```
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
## 致谢
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - 市面上最好的所见即所得编辑器,拥有互动性强且聆听能力强的团队
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - 一个非常丰富的关于树的库,强大到没有对手。没有它,Trilium Notes 将不会如此。
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - 支持大量语言的代码编辑器
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - 强大的可视化连接库。用于[关系图](https://github.com/zadam/trilium/wiki/Relation-map)和[链接图](https://github.com/zadam/trilium/wiki/Link-map)
|
||||
|
||||
## 捐赠
|
||||
|
||||
你可以通过 GitHub Sponsors,[PayPal](https://paypal.me/za4am) 或者比特币 (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2) 来捐赠。
|
||||
|
||||
## 许可证
|
||||
|
||||
本程序是自由软件:你可以再发布本软件和/或修改本软件,只要你遵循 Free Software Foundation 发布的 GNU Affero General Public License 的第三版或者任何(由你选择)更晚的版本。
|
||||
83
README.ja.md
Normal file
83
README.ja.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Trilium Notes
|
||||
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
|
||||
Trilium Notes は、大規模な個人知識ベースの構築に焦点を当てた、階層型ノートアプリケーションです。概要は[スクリーンショット](https://github.com/zadam/trilium/wiki/Screenshot-tour)をご覧ください:
|
||||
|
||||
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
|
||||
|
||||
ウクライナは現在、ロシアの侵略から自国を守っています。[ウクライナ軍や人道的な慈善団体への寄付](https://standforukraine.com/)をご検討ください。
|
||||
|
||||
<p float="left">
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
|
||||
</p>
|
||||
|
||||
## 🎁 特徴
|
||||
|
||||
* ノートは、任意の深さのツリーに配置できます。単一のノートをツリー内の複数の場所に配置できます ([cloning](https://github.com/zadam/trilium/wiki/Cloning-notes) を参照)
|
||||
* マークダウン[オートフォーマット](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)による、表、画像、[数学](https://github.com/zadam/trilium/wiki/Text-notes#math-support)などの豊富な WYSIWYG ノート編集機能
|
||||
* シンタックスハイライトを含む[ソースコード付きノート](https://github.com/zadam/trilium/wiki/Code-notes)の編集をサポート
|
||||
* [ノート間のナビゲーション](https://github.com/zadam/trilium/wiki/Note-navigation)、全文検索、[ノートホイスト](https://github.com/zadam/trilium/wiki/Note-hoisting)が高速かつ簡単に行えます
|
||||
* シームレスな[ノートのバージョン管理](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||
* ノート[属性](https://github.com/zadam/trilium/wiki/Attributes)は、ノート整理、クエリ、高度な[スクリプト](https://github.com/zadam/trilium/wiki/Scripts)に使用できます
|
||||
* 自己ホスト型同期サーバーとの[同期](https://github.com/zadam/trilium/wiki/Synchronization)
|
||||
* [同期サーバーをホストするサードパーティ・サービス](https://trilium.cc/paid-hosting)があります
|
||||
* 公開インターネットへのノートの[共有](https://github.com/zadam/trilium/wiki/Sharing)(公開)
|
||||
* ノートごとの粒度を持つ強力な[ノート暗号化](https://github.com/zadam/trilium/wiki/Protected-notes)
|
||||
* 組み込みの Excalidraw を使用した図のスケッチ (ノート タイプ"キャンバス")
|
||||
* ノートとその関係を可視化するための[関係図](https://github.com/zadam/trilium/wiki/Relation-map)と[リンクマップ](https://github.com/zadam/trilium/wiki/Link-map)
|
||||
* [スクリプティング](https://github.com/zadam/trilium/wiki/Scripts) - [高度なショーケース](https://github.com/zadam/trilium/wiki/Advanced-showcases)を参照
|
||||
* 自動化のための [REST API](https://github.com/zadam/trilium/wiki/ETAPI)
|
||||
* ユーザビリティとパフォーマンスの両方で 100 000 ノート以上に拡張可能
|
||||
* スマートフォンとタブレット向けのタッチ最適化[モバイルフロントエンド](https://github.com/zadam/trilium/wiki/Mobile-frontend)
|
||||
* [ナイトテーマ](https://github.com/zadam/trilium/wiki/Themes)
|
||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) と [Markdown のインポートとエクスポート](https://github.com/zadam/trilium/wiki/Markdown)
|
||||
* Web コンテンツを簡単に保存するための [Web クリッパー](https://github.com/zadam/trilium/wiki/Web-clipper)
|
||||
|
||||
サードパーティのテーマ、スクリプト、プラグインなどは、 [awesome-trilium](https://github.com/Nriver/awesome-trilium) をチェックしてください。
|
||||
|
||||
## 🏗 ビルド
|
||||
|
||||
Trilium は、デスクトップアプリケーション(Linux、Windows)またはサーバー上でホストされるウェブアプリケーション(Linux)として提供されます。 Mac OS のデスクトップビルドも利用可能ですが、 [unsupported](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support) となっています。
|
||||
|
||||
* デスクトップで Trilium を使用したい場合は、 [latest release](https://github.com/zadam/trilium/releases/latest) からお使いのプラットフォームのバイナリリリースをダウンロードし、パッケージを解凍して ``trilium`` の実行ファイルを実行してください。
|
||||
* サーバーに Trilium をインストールする場合は、[このページ](https://github.com/zadam/trilium/wiki/Server-installation)に従ってください。
|
||||
* 現在、対応(動作確認)しているブラウザは、最近の Chrome と Firefox のみです。
|
||||
|
||||
Trilium は Flatpak としても提供されます:
|
||||
|
||||
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
|
||||
|
||||
## 📝 ドキュメント
|
||||
|
||||
[ドキュメントページの全リストはwikiをご覧ください。](https://github.com/zadam/trilium/wiki/)
|
||||
|
||||
また、[個人的な知識基盤のパターン](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base)を読むと、 Trilium の使い方のヒントを得ることができます。
|
||||
|
||||
## 💻 コントリビュート
|
||||
|
||||
ブラウザベースの開発環境を使用
|
||||
|
||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||
|
||||
または、ローカルにクローンして実行
|
||||
```
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
## 📢 シャウトアウト
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - 市場で最高の WYSIWYG エディター、非常にインタラクティブで聞き上手なチーム
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - 真の競争相手がいない、非常に機能豊富なツリーライブラリです。 Trilium Notes は、これなしでは成り立たないでしょう。
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - 膨大な数の言語をサポートするコードエディタ
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - 競合のないビジュアルコネクティビティライブラリです。[関係図](https://github.com/zadam/trilium/wiki/Relation-map)、[リンク図](https://github.com/zadam/trilium/wiki/Link-map)で使用。
|
||||
|
||||
## 🤝 サポート
|
||||
|
||||
GitHub スポンサー、[PayPal](https://paypal.me/za4am)もしくは Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2) にて Trilium をサポートすることができます。
|
||||
|
||||
## 🔑 ライセンス
|
||||
|
||||
このプログラムはフリーソフトウェアです:フリーソフトウェア財団が発行した GNU Affero General Public License のバージョン3、またはそれ以降のバージョンのいずれかに従って、再配布および/または改変することができます。
|
||||
231
README.md
231
README.md
@@ -1,218 +1,83 @@
|
||||
<div align="center">
|
||||
<sup>Special thanks to:</sup><br />
|
||||
<a href="https://go.warp.dev/Trilium" target="_blank">
|
||||
<img alt="Warp sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-03.png"><br />
|
||||
Warp, built for coding with multiple AI agents<br />
|
||||
</a>
|
||||
<sup>Available for macOS, Linux and Windows</sup>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
# Trilium Notes
|
||||
|
||||
 
|
||||

|
||||

|
||||
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) [](https://hosted.weblate.org/engage/trilium/)
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
|
||||
[English](./README.md) | [Chinese (Simplified)](./docs/README-ZH_CN.md) | [Chinese (Traditional)](./docs/README-ZH_TW.md) | [Russian](./docs/README-ru.md) | [Japanese](./docs/README-ja.md) | [Italian](./docs/README-it.md) | [Spanish](./docs/README-es.md)
|
||||
Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases. See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
|
||||
|
||||
Trilium Notes is a free and open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
|
||||
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
|
||||
|
||||
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for quick overview:
|
||||
Ukraine is currently defending itself from Russian aggression, please consider [donating to Ukrainian Army or humanitarian charities](https://standforukraine.com/).
|
||||
|
||||
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
|
||||
|
||||
## ⏬ Download
|
||||
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) – stable version, recommended for most users.
|
||||
- [Nightly build](https://github.com/TriliumNext/Trilium/releases/tag/nightly) – unstable development version, updated daily with the latest features and fixes.
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
**Visit our comprehensive documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)**
|
||||
|
||||
Our documentation is available in multiple formats:
|
||||
- **Online Documentation**: Browse the full documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)
|
||||
- **In-App Help**: Press `F1` within Trilium to access the same documentation directly in the application
|
||||
- **GitHub**: Navigate through the [User Guide](./docs/User%20Guide/User%20Guide/) in this repository
|
||||
|
||||
### Quick Links
|
||||
- [Getting Started Guide](https://docs.triliumnotes.org/)
|
||||
- [Installation Instructions](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
|
||||
- [Docker Setup](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
|
||||
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
|
||||
- [Basic Concepts and Features](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
|
||||
- [Patterns of Personal Knowledge Base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
|
||||
<p float="left">
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
|
||||
</p>
|
||||
|
||||
## 🎁 Features
|
||||
|
||||
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes))
|
||||
* Rich WYSIWYG note editor including e.g. tables, images and [math](https://triliumnext.github.io/Docs/Wiki/text-notes) with markdown [autoformat](https://triliumnext.github.io/Docs/Wiki/text-notes#autoformat)
|
||||
* Support for editing [notes with source code](https://triliumnext.github.io/Docs/Wiki/code-notes), including syntax highlighting
|
||||
* Fast and easy [navigation between notes](https://triliumnext.github.io/Docs/Wiki/note-navigation), full text search and [note hoisting](https://triliumnext.github.io/Docs/Wiki/note-hoisting)
|
||||
* Seamless [note versioning](https://triliumnext.github.io/Docs/Wiki/note-revisions)
|
||||
* Note [attributes](https://triliumnext.github.io/Docs/Wiki/attributes) can be used for note organization, querying and advanced [scripting](https://triliumnext.github.io/Docs/Wiki/scripts)
|
||||
* UI available in English, German, Spanish, French, Romanian, and Chinese (simplified and traditional)
|
||||
* Direct [OpenID and TOTP integration](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Server%20Installation/Multi-Factor%20Authentication.md) for more secure login
|
||||
* [Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization) with self-hosted sync server
|
||||
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||
* Rich WYSIWYG note editing including e.g. tables, images and [math](https://github.com/zadam/trilium/wiki/Text-notes#math-support) with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)
|
||||
* Support for editing [notes with source code](https://github.com/zadam/trilium/wiki/Code-notes), including syntax highlighting
|
||||
* Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation), full text search and [note hoisting](https://github.com/zadam/trilium/wiki/Note-hoisting)
|
||||
* Seamless [note versioning](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||
* Note [attributes](https://github.com/zadam/trilium/wiki/Attributes) can be used for note organization, querying and advanced [scripting](https://github.com/zadam/trilium/wiki/Scripts)
|
||||
* [Synchronization](https://github.com/zadam/trilium/wiki/Synchronization) with self-hosted sync server
|
||||
* there's a [3rd party service for hosting synchronisation server](https://trilium.cc/paid-hosting)
|
||||
* [Sharing](https://triliumnext.github.io/Docs/Wiki/sharing) (publishing) notes to public internet
|
||||
* Strong [note encryption](https://triliumnext.github.io/Docs/Wiki/protected-notes) with per-note granularity
|
||||
* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type "canvas")
|
||||
* [Relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/link-map) for visualizing notes and their relations
|
||||
* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/)
|
||||
* [Geo maps](./docs/User%20Guide/User%20Guide/Note%20Types/Geo%20Map.md) with location pins and GPX tracks
|
||||
* [Scripting](https://triliumnext.github.io/Docs/Wiki/scripts) - see [Advanced showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases)
|
||||
* [REST API](https://triliumnext.github.io/Docs/Wiki/etapi) for automation
|
||||
* [Sharing](https://github.com/zadam/trilium/wiki/Sharing) (publishing) notes to public internet
|
||||
* Strong [note encryption](https://github.com/zadam/trilium/wiki/Protected-notes) with per-note granularity
|
||||
* Sketching diagrams with built-in Excalidraw (note type "canvas")
|
||||
* [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) and [link maps](https://github.com/zadam/trilium/wiki/Link-map) for visualizing notes and their relations
|
||||
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
||||
* [REST API](https://github.com/zadam/trilium/wiki/ETAPI) for automation
|
||||
* Scales well in both usability and performance upwards of 100 000 notes
|
||||
* Touch optimized [mobile frontend](https://triliumnext.github.io/Docs/Wiki/mobile-frontend) for smartphones and tablets
|
||||
* Built-in [dark theme](https://triliumnext.github.io/Docs/Wiki/themes), support for user themes
|
||||
* [Evernote](https://triliumnext.github.io/Docs/Wiki/evernote-import) and [Markdown import & export](https://triliumnext.github.io/Docs/Wiki/markdown)
|
||||
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/web-clipper) for easy saving of web content
|
||||
* Customizable UI (sidebar buttons, user-defined widgets, ...)
|
||||
* [Metrics](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics.md), along with a [Grafana Dashboard](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics/grafana-dashboard.json)
|
||||
* Touch optimized [mobile frontend](https://github.com/zadam/trilium/wiki/Mobile-frontend) for smartphones and tablets
|
||||
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
|
||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
|
||||
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) for easy saving of web content
|
||||
|
||||
✨ Check out the following third-party resources/communities for more TriliumNext related goodies:
|
||||
Check out [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||
|
||||
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
||||
## 🏗 Builds
|
||||
|
||||
## ❓Why TriliumNext?
|
||||
Trilium is provided as either desktop application (Linux and Windows) or web application hosted on your server (Linux). Mac OS desktop build is available, but it is [unsupported](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
|
||||
|
||||
The original Trilium developer ([Zadam](https://github.com/zadam)) has graciously given the Trilium repository to the community project which resides at https://github.com/TriliumNext
|
||||
* If you want to use Trilium on the desktop, download binary release for your platform from [latest release](https://github.com/zadam/trilium/releases/latest), unzip the package and run ```trilium``` executable.
|
||||
* If you want to install Trilium on server, follow [this page](https://github.com/zadam/trilium/wiki/Server-installation).
|
||||
* Currently only recent Chrome and Firefox are supported (tested) browsers.
|
||||
|
||||
### ⬆️Migrating from Zadam/Trilium?
|
||||
Trilium is also provided as a Flatpak:
|
||||
|
||||
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Trilium instance. Simply [install TriliumNext/Trilium](#-installation) as usual and it will use your existing database.
|
||||
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
|
||||
|
||||
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext/Trilium have their sync versions incremented which prevents direct migration.
|
||||
## 📝 Documentation
|
||||
|
||||
## 💬 Discuss with us
|
||||
|
||||
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!
|
||||
|
||||
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions.)
|
||||
- The `General` Matrix room is also bridged to [XMPP](xmpp:discuss@trilium.thisgreat.party?join)
|
||||
- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For asynchronous discussions.)
|
||||
- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug reports and feature requests.)
|
||||
|
||||
## 🏗 Installation
|
||||
|
||||
### Windows / MacOS
|
||||
|
||||
Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package and run the `trilium` executable.
|
||||
|
||||
### Linux
|
||||
|
||||
If your distribution is listed in the table below, use your distribution's package.
|
||||
|
||||
[](https://repology.org/project/triliumnext/versions)
|
||||
|
||||
You may also download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package and run the `trilium` executable.
|
||||
|
||||
TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
|
||||
|
||||
### Browser (any OS)
|
||||
|
||||
If you use a server installation (see below), you can directly access the web interface (which is almost identical to the desktop app).
|
||||
|
||||
Currently only the latest versions of Chrome & Firefox are supported (and tested).
|
||||
|
||||
### Mobile
|
||||
|
||||
To use TriliumNext on a mobile device, you can use a mobile web browser to access the mobile interface of a server installation (see below).
|
||||
|
||||
See issue https://github.com/TriliumNext/Trilium/issues/4962 for more information on mobile app support.
|
||||
|
||||
If you prefer a native Android app, you can use [TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid).
|
||||
Report bugs and missing features at [their repository](https://github.com/FliegendeWurst/TriliumDroid).
|
||||
Note: It is best to disable automatic updates on your server installation (see below) when using TriliumDroid since the sync version must match between Trilium and TriliumDroid.
|
||||
|
||||
### Server
|
||||
|
||||
To install TriliumNext on your own server (including via Docker from [Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
|
||||
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)
|
||||
|
||||
You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.
|
||||
|
||||
## 💻 Contribute
|
||||
|
||||
### Translations
|
||||
Use a browser based dev environment
|
||||
|
||||
If you are a native speaker, help us translate Trilium by heading over to our [Weblate page](https://hosted.weblate.org/engage/trilium/).
|
||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||
|
||||
Here's the language coverage we have so far:
|
||||
|
||||
[](https://hosted.weblate.org/engage/trilium/)
|
||||
|
||||
### Code
|
||||
|
||||
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
pnpm run server:start
|
||||
Or clone locally and run
|
||||
```
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
### Documentation
|
||||
## 📢 Shoutouts
|
||||
|
||||
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
pnpm edit-docs:edit-docs
|
||||
```
|
||||
|
||||
### Building the Executable
|
||||
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
||||
```shell
|
||||
git clone https://github.com/TriliumNext/Trilium.git
|
||||
cd Trilium
|
||||
pnpm install
|
||||
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
|
||||
```
|
||||
|
||||
For more details, see the [development docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
|
||||
|
||||
### Developer Documentation
|
||||
|
||||
Please view the [documentation guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) for details. If you have more questions, feel free to reach out via the links described in the "Discuss with us" section above.
|
||||
|
||||
## 👏 Shoutouts
|
||||
|
||||
* [zadam](https://github.com/zadam) for the original concept and implementation of the application.
|
||||
* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the application icon.
|
||||
* [nriver](https://github.com/nriver) for his work on internationalization.
|
||||
* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas.
|
||||
* [antoniotejada](https://github.com/nriver) for the original syntax highlight widget.
|
||||
* [Dosu](https://dosu.dev/) for providing us with the automated responses to GitHub issues and discussions.
|
||||
* [Tabler Icons](https://tabler.io/icons) for the system tray icons.
|
||||
|
||||
Trilium would not be possible without the technologies behind it:
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - the visual editor behind text notes. We are grateful for being offered a set of the premium features.
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with support for huge amount of languages.
|
||||
* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite whiteboard used in Canvas notes.
|
||||
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the mind map functionality.
|
||||
* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical maps.
|
||||
* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive table used in collections.
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library without real competition.
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library. Used in [relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map.html) and [link maps](https://triliumnext.github.io/Docs/Wiki/note-map.html#link-map)
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - very feature rich tree library without real competition. Trilium Notes would not be the same without it.
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with support for huge amount of languages
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library without competition. Used in [relation maps](https://github.com/zadam/trilium/wiki/Relation-map) and [link maps](https://github.com/zadam/trilium/wiki/Link-map)
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
Trilium is built and maintained with [hundreds of hours of work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your support keeps it open-source, improves features, and covers costs such as hosting.
|
||||
|
||||
Consider supporting the main developer ([eliandoran](https://github.com/eliandoran)) of the application via:
|
||||
|
||||
- [GitHub Sponsors](https://github.com/sponsors/eliandoran)
|
||||
- [PayPal](https://paypal.me/eliandoran)
|
||||
- [Buy Me a Coffee](https://buymeacoffee.com/eliandoran)
|
||||
|
||||
You can support Trilium using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
|
||||
|
||||
## 🔑 License
|
||||
|
||||
Copyright 2017-2025 zadam, Elian Doran, and other contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
68
README.ru.md
Normal file
68
README.ru.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Trilium Notes
|
||||
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
Trilium Notes – это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://github.com/zadam/trilium/wiki/Screenshot-tour):
|
||||
|
||||

|
||||
|
||||
Ukraine is currently suffering from Russian aggression, please consider donating to [one of these charities](https://old.reddit.com/r/ukraine/comments/s6g5un/want_to_support_ukraine_heres_a_list_of_charities/).
|
||||
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="600"/>
|
||||
|
||||
## Возможности
|
||||
|
||||
* Заметки можно расположить в виде дерева произвольной глубины. Отдельную заметку можно разместить в нескольких местах дерева (см. [клонирование](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||
* Продвинутый визуальный редактор (WYSIWYG) позволяет работать с таблицами, изображениями, [формулами](https://github.com/zadam/trilium/wiki/Text-notes#math-support) и разметкой markdown, имеет [автоформатирование](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)
|
||||
* Редактирование [заметок с исходным кодом](https://github.com/zadam/trilium/wiki/Code-notes), включая подсветку синтаксиса
|
||||
* Быстрая и простая [навигация между заметками](https://github.com/zadam/trilium/wiki/Note-navigation), полнотекстовый поиск и [выделение заметок](https://github.com/zadam/trilium/wiki/Note-hoisting) в отдельный блок
|
||||
* Бесшовное [версионирование заметки](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||
* Специальные [атрибуты](https://github.com/zadam/trilium/wiki/Attributes) позволяют гибко организовать структуру, используются для поиска и продвинутого [скриптинга](https://github.com/zadam/trilium/wiki/Scripts)
|
||||
* [Синхронизация](https://github.com/zadam/trilium/wiki/Synchronization) заметок со своим сервером
|
||||
* Надёжное [шифрование](https://github.com/zadam/trilium/wiki/Protected-notes) с детализацией по каждой заметке
|
||||
* [Карты связей](https://github.com/zadam/trilium/wiki/Relation-map) и [карты ссылок](https://github.com/zadam/trilium/wiki/Link-map) для визуализации их взяимосвязей
|
||||
* [Скрипты](https://github.com/zadam/trilium/wiki/Scripts) - см. [продвинутые примеры](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
||||
* Хорошо масштабируется, как по удобству использования, так и по производительности до 100000 заметок
|
||||
* Оптимизированный [мобильный фронтенд](https://github.com/zadam/trilium/wiki/Mobile-frontend) смартфонов и планшетов
|
||||
* [Темная тема](https://github.com/zadam/trilium/wiki/Themes)
|
||||
* Импорт и экпорт [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) и данных в [markdown](https://github.com/zadam/trilium/wiki/Markdown) формате
|
||||
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) для удобного сохранения веб-контента
|
||||
|
||||
## Сборки
|
||||
|
||||
Trilium предоставляется в виде десктопного приложения (Linux и Windows) или веб-приложения, размещенного на вашем сервере (Linux). Доступна сборка Mac OS, но она [не поддерживается](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
|
||||
|
||||
* Если вы хотите использовать Trilium на десктопе, скачайте архив для своей платформы со страницы [релизов](https://github.com/zadam/trilium/releases/latest), распакуйте и запустите исполняемый файл ```trilium```.
|
||||
* Если вы хотите установить Trilium на сервере, следуйте этой [инструкции](https://github.com/zadam/trilium/wiki/Server-installation).
|
||||
* В данный момент поддерживаются (протестированы) последние версии браузеров Chrome и Firefox.
|
||||
|
||||
## Документация
|
||||
|
||||
[Полный список страниц документации доступен в Wiki.](https://github.com/zadam/trilium/wiki/)
|
||||
|
||||
Вы также можете ознакомиться с [шаблонами персональных баз знаний](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base), чтобы получить представление о том, как можно использовать Trilium.
|
||||
|
||||
## Участвуйте в разработке
|
||||
|
||||
Используйте онлайн среду разработки в браузере
|
||||
|
||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||
|
||||
Или склонируйте на своё устройство и запустите
|
||||
```
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
## Благодарности
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - лучший WYSIWYG редактор, очень активная и внимательная команда.
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - многофункциональная библиотека для создания древовидных структур. Вне конкуренции. Без него Trilium Notes не были бы таким.
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - редактор кода с поддержкой огромного количество языков.
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - библиотека для визуализации связей. Вне конкуренции. Используется в [картах связей](https://github.com/zadam/trilium/wiki/Relation-map) и [картах ссылок](https://github.com/zadam/trilium/wiki/Link-map).
|
||||
|
||||
## Лицензия
|
||||
|
||||
Эта программа является бесплатным программным обеспечением: вы можете распространять и/или изменять ее в соответствии с условиями GNU Affero General Public License, опубликованной Free Software Foundation, либо версии 3 Лицензии, либо (по вашему выбору) любой более поздней версии.
|
||||
@@ -10,4 +10,4 @@ Description above is a general rule and may be altered on case by case basis.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
You can report low severity vulnerabilities as GitHub issues, more severe vulnerabilities should be reported to the email [contact@eliandoran.me](mailto:contact@eliandoran.me)
|
||||
You can report low severity vulnerabilities as GitHub issues, more severe vulnerabilities should be reported to the email zadam.apps@gmail.com
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import anonymizationService from "../src/services/anonymization.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
fs.writeFileSync(path.resolve(__dirname, "tpl", "anonymize-database.sql"), anonymizationService.getFullAnonymizationScript());
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/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
|
||||
|
||||
if ! command -v icnsutil &> /dev/null; then
|
||||
echo "This tool requires icnsutil to be installed in order to generate macOS icons."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
script_dir=$(realpath $(dirname $0))
|
||||
cd "${script_dir}/../images/app-icons"
|
||||
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
|
||||
declare -a sizes=("16" "32" "512" "1024")
|
||||
for size in "${sizes[@]}"; do
|
||||
inkscape -w $size -h $size "../icon-color.svg" -o "./png/${size}x${size}.png"
|
||||
done
|
||||
|
||||
mkdir -p fakeapp.app
|
||||
npx iconsur set fakeapp.app -l -i "png/1024x1024.png" -o "mac/1024x1024.png" -s 0.8
|
||||
declare -a sizes=("16x16" "32x32" "128x128" "512x512")
|
||||
for size in "${sizes[@]}"; do
|
||||
magick "mac/1024x1024.png" -resize "${size}" "mac/${size}.png"
|
||||
done
|
||||
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"
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Usage: tsx ./generate_document.ts 1000
|
||||
* will create 1000 new notes and some clones into the current document.db
|
||||
*/
|
||||
|
||||
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 "../src/becca/entity_constructor.js";
|
||||
|
||||
const noteCount = parseInt(process.argv[2]);
|
||||
|
||||
if (!noteCount) {
|
||||
console.error(`Please enter number of notes as program parameter.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const notes = ["root"];
|
||||
|
||||
function getRandomNoteId() {
|
||||
const index = Math.floor(Math.random() * notes.length);
|
||||
|
||||
return notes[index];
|
||||
}
|
||||
|
||||
async function start() {
|
||||
for (let i = 0; i < noteCount; i++) {
|
||||
const title = loremIpsum.loremIpsum({
|
||||
count: 1,
|
||||
units: "sentences",
|
||||
sentenceLowerBound: 1,
|
||||
sentenceUpperBound: 10
|
||||
});
|
||||
|
||||
const paragraphCount = Math.floor(Math.random() * Math.random() * 100);
|
||||
const content = loremIpsum.loremIpsum({
|
||||
count: paragraphCount,
|
||||
units: "paragraphs",
|
||||
sentenceLowerBound: 1,
|
||||
sentenceUpperBound: 15,
|
||||
paragraphLowerBound: 3,
|
||||
paragraphUpperBound: 10,
|
||||
format: "html"
|
||||
});
|
||||
|
||||
const { note } = noteService.createNewNote({
|
||||
parentNoteId: getRandomNoteId(),
|
||||
title,
|
||||
content,
|
||||
type: "text"
|
||||
});
|
||||
|
||||
console.log(`Created note ${i}: ${title}`);
|
||||
|
||||
if (Math.random() < 0.04) {
|
||||
const noteIdToClone = note.noteId;
|
||||
const parentNoteId = getRandomNoteId();
|
||||
const prefix = Math.random() > 0.8 ? "prefix" : "";
|
||||
|
||||
const result = await cloningService.cloneNoteToBranch(noteIdToClone, parentNoteId, prefix);
|
||||
|
||||
console.log(`Cloning ${i}:`, result.success ? "succeeded" : "FAILED");
|
||||
}
|
||||
|
||||
// does not have to be for the current note
|
||||
await attributeService.createAttribute({
|
||||
noteId: getRandomNoteId(),
|
||||
type: "label",
|
||||
name: "label",
|
||||
value: "value",
|
||||
isInheritable: Math.random() > 0.1 // 10% are inheritable
|
||||
});
|
||||
|
||||
await attributeService.createAttribute({
|
||||
noteId: getRandomNoteId(),
|
||||
type: "relation",
|
||||
name: "relation",
|
||||
value: getRandomNoteId(),
|
||||
isInheritable: Math.random() > 0.1 // 10% are inheritable
|
||||
});
|
||||
|
||||
note.saveRevision();
|
||||
|
||||
notes.push(note.noteId);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// @TriliumNextTODO sqlInit.dbReady never seems to resolve so program hangs
|
||||
// see https://github.com/TriliumNext/Trilium/issues/1020
|
||||
sqlInit.dbReady.then(cls.wrap(start)).catch((err) => console.error(err));
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
if [[ $# -eq 0 ]] ; then
|
||||
echo "Missing argument of new version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Missing command: jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$1
|
||||
|
||||
if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
|
||||
then
|
||||
echo "Version ${VERSION} isn't in format X.Y.Z"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
echo "There are uncommitted changes"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Releasing Trilium $VERSION"
|
||||
|
||||
jq '.version = "'$VERSION'"' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
git add package.json
|
||||
|
||||
npm run chore:update-build-info
|
||||
|
||||
git add src/services/build.ts
|
||||
|
||||
TAG=v$VERSION
|
||||
|
||||
echo "Committing package.json version change"
|
||||
|
||||
git commit -m "chore(release): $VERSION"
|
||||
git push
|
||||
|
||||
echo "Tagging commit with $TAG"
|
||||
|
||||
git tag $TAG
|
||||
git push origin $TAG
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Create PO files to make easier the labor of translation.
|
||||
#
|
||||
# Info:
|
||||
# https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|
||||
# https://docs.translatehouse.org/projects/translate-toolkit/en/latest/commands/json2po.html
|
||||
#
|
||||
# Dependencies:
|
||||
# jq
|
||||
# translate-toolkit
|
||||
# python-wcwidth
|
||||
#
|
||||
# Created by @hasecilu
|
||||
#
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
number_of_keys() {
|
||||
[ -f "$1" ] && jq 'path(..) | select(length == 2) | .[1]' "$1" | wc -l || echo "0"
|
||||
}
|
||||
|
||||
stats() {
|
||||
# Print the number of existing strings on the JSON files for each locale
|
||||
s=$(number_of_keys "${paths[0]}/en/server.json")
|
||||
c=$(number_of_keys "${paths[1]}/en/translation.json")
|
||||
echo "| locale | server strings | client strings |"
|
||||
echo "|--------|----------------|----------------|"
|
||||
echo "| en | ${s} | ${c} |"
|
||||
echo "|--------|----------------|----------------|"
|
||||
for locale in "${locales[@]}"; do
|
||||
s=$(number_of_keys "${paths[0]}/${locale}/server.json")
|
||||
c=$(number_of_keys "${paths[1]}/${locale}/translation.json")
|
||||
n1=$(((8 - ${#locale}) / 2))
|
||||
n2=$((n1 == 1 ? n1 + 1 : n1))
|
||||
echo "|$(printf "%${n1}s")${locale}$(printf "%${n2}s")| ${s} | ${c} |"
|
||||
done
|
||||
}
|
||||
|
||||
update_1() {
|
||||
# Update PO files from English and localized JSON files as source
|
||||
# NOTE: if you want a new language you need to first create the JSON files
|
||||
# on their corresponding place with `{}` as content to avoid error on `json2po`
|
||||
local locales=("$@")
|
||||
for path in "${paths[@]}"; do
|
||||
for locale in "${locales[@]}"; do
|
||||
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
update_2() {
|
||||
# Recover translation from PO files to localized JSON files
|
||||
local locales=("$@")
|
||||
for path in "${paths[@]}"; do
|
||||
for locale in "${locales[@]}"; do
|
||||
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
help() {
|
||||
echo -e "\nDescription:"
|
||||
echo -e "\tCreate PO files to make easier the labor of translation"
|
||||
echo -e "\nUsage:"
|
||||
echo -e "\t./translation.sh [--stats] [--update1 <OPT_LOCALE>] [--update2 <OPT_LOCALE>]"
|
||||
echo -e "\nFlags:"
|
||||
echo -e " --clear\n\tClear all po-* directories"
|
||||
echo -e " --stats\n\tPrint the number of existing strings on the JSON files for each locale"
|
||||
echo -e " --update1 <LOCALE>\n\tUpdate PO files from English and localized JSON files as source"
|
||||
echo -e " --update2 <LOCALE>\n\tRecover translation from PO files to localized JSON files"
|
||||
}
|
||||
|
||||
# Main function ------------------------------------------------------------------------------------
|
||||
|
||||
# Get script directory to set file path relative to it
|
||||
file_path="$(
|
||||
cd -- "$(dirname "${0}")" >/dev/null 2>&1 || exit
|
||||
pwd -P
|
||||
)"
|
||||
paths=(
|
||||
"${file_path}/../../apps/server/src/assets/translations/"
|
||||
"${file_path}/../../apps/client/src/translations/"
|
||||
)
|
||||
locales=(cn de es fr pt_br ro tw)
|
||||
|
||||
if [ $# -eq 1 ]; then
|
||||
if [ "$1" == "--clear" ]; then
|
||||
for path in "${paths[@]}"; do
|
||||
for locale in "${locales[@]}"; do
|
||||
[ -d "${path}/po-${locale}" ] && rm -r "${path}/po-${locale}"
|
||||
done
|
||||
done
|
||||
elif [ "$1" == "--stats" ]; then
|
||||
stats
|
||||
elif [ "$1" == "--update1" ]; then
|
||||
update_1 "${locales[@]}"
|
||||
elif [ "$1" == "--update2" ]; then
|
||||
update_2 "${locales[@]}"
|
||||
else
|
||||
help
|
||||
fi
|
||||
elif [ $# -eq 2 ]; then
|
||||
if [ "$1" == "--update1" ]; then
|
||||
update_1 "$2"
|
||||
elif [ "$1" == "--update2" ]; then
|
||||
update_2 "$2"
|
||||
else
|
||||
help
|
||||
fi
|
||||
else
|
||||
help
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 383 B |
@@ -1,39 +0,0 @@
|
||||
#!/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 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 356 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 357 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 387 B |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 734 B |
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,51 +0,0 @@
|
||||
import eslint from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
// consider using rules below, once we have a full TS codebase and can be more strict
|
||||
// tseslint.configs.strictTypeChecked,
|
||||
// tseslint.configs.stylisticTypeChecked,
|
||||
// tseslint.configs.recommendedTypeChecked,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
plugins: {
|
||||
"simple-import-sort": simpleImportSort
|
||||
}
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// add rule overrides here
|
||||
"no-undef": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_"
|
||||
}
|
||||
],
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"build/*",
|
||||
"dist/*",
|
||||
"docs/*",
|
||||
"demo/*",
|
||||
"src/public/app-dist/*",
|
||||
"src/public/app/doc_notes/*"
|
||||
]
|
||||
}
|
||||
);
|
||||
@@ -1,47 +0,0 @@
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
|
||||
// eslint config just for formatting rules
|
||||
// potentially to be merged with the linting rules into one single config,
|
||||
// once we have fixed the majority of lint errors
|
||||
|
||||
// Go to https://eslint.style/rules/default/${rule_without_prefix} to check the rule details
|
||||
export const stylisticRules = {
|
||||
"@stylistic/indent": [ "error", 4 ],
|
||||
"@stylistic/quotes": [ "error", "double", { avoidEscape: true, allowTemplateLiterals: "always" } ],
|
||||
"@stylistic/semi": [ "error", "always" ],
|
||||
"@stylistic/quote-props": [ "error", "consistent-as-needed" ],
|
||||
"@stylistic/max-len": [ "error", { code: 100 } ],
|
||||
"@stylistic/comma-dangle": [ "error", "never" ],
|
||||
"@stylistic/linebreak-style": [ "error", "unix" ],
|
||||
"@stylistic/array-bracket-spacing": [ "error", "always" ],
|
||||
"@stylistic/object-curly-spacing": [ "error", "always" ],
|
||||
"@stylistic/padded-blocks": [ "error", { classes: "always" } ]
|
||||
};
|
||||
|
||||
export default [
|
||||
{
|
||||
files: [ "**/*.{js,ts,mjs,cjs}" ],
|
||||
languageOptions: {
|
||||
parser: tsParser
|
||||
},
|
||||
plugins: {
|
||||
"@stylistic": stylistic
|
||||
},
|
||||
rules: {
|
||||
...stylisticRules
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"build/*",
|
||||
"dist/*",
|
||||
"docs/*",
|
||||
"demo/*",
|
||||
// TriliumNextTODO: check if we want to format packages here as well - for now skipping it
|
||||
"packages/*",
|
||||
"src/public/app-dist/*",
|
||||
"src/public/app/doc_notes/*"
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -1,17 +0,0 @@
|
||||
import { test as setup, expect } from "@playwright/test";
|
||||
|
||||
const authFile = "playwright/.auth/user.json";
|
||||
|
||||
const ROOT_URL = "http://localhost:8082";
|
||||
const LOGIN_PASSWORD = "demo1234";
|
||||
|
||||
// Reference: https://playwright.dev/docs/auth#basic-shared-account-in-all-tests
|
||||
|
||||
setup("authenticate", async ({ page }) => {
|
||||
await page.goto(ROOT_URL);
|
||||
await expect(page).toHaveURL(`${ROOT_URL}/login`);
|
||||
|
||||
await page.getByRole("textbox", { name: "Password" }).fill(LOGIN_PASSWORD);
|
||||
await page.getByRole("button", { name: "Login" }).click();
|
||||
await page.context().storageState({ path: authFile });
|
||||
});
|
||||
@@ -1,9 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("Can duplicate note with broken links", async ({ page }) => {
|
||||
await page.goto(`http://localhost:8082/#2VammGGdG6Ie`);
|
||||
await page.locator(".tree-wrapper .fancytree-active").getByText("Note map").click({ button: "right" });
|
||||
await page.getByText("Duplicate subtree").click();
|
||||
await expect(page.locator(".toast-body")).toBeHidden();
|
||||
await expect(page.locator(".tree-wrapper").getByText("Note map (dup)")).toBeVisible();
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("has title", async ({ page }) => {
|
||||
await page.goto("https://playwright.dev/");
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/Playwright/);
|
||||
});
|
||||
|
||||
test("get started link", async ({ page }) => {
|
||||
await page.goto("https://playwright.dev/");
|
||||
|
||||
// Click the get started link.
|
||||
await page.getByRole("link", { name: "Get started" }).click();
|
||||
|
||||
// Expects page to have a heading with the name of Installation.
|
||||
await expect(page.getByRole("heading", { name: "Installation" })).toBeVisible();
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
|
||||
test("Native Title Bar not displayed on web", async ({ page }) => {
|
||||
await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsAppearance");
|
||||
await expect(page.getByRole("heading", { name: "Theme" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Native Title Bar (requires" })).toBeHidden();
|
||||
});
|
||||
|
||||
test("Tray settings not displayed on web", async ({ page }) => {
|
||||
await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsOther");
|
||||
await expect(page.getByRole("heading", { name: "Note Erasure Timeout" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Tray" })).toBeHidden();
|
||||
});
|
||||
|
||||
test("Spellcheck settings not displayed on web", async ({ page }) => {
|
||||
await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsSpellcheck");
|
||||
await expect(page.getByRole("heading", { name: "Spell Check" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Tray" })).toBeHidden();
|
||||
await expect(page.getByText("These options apply only for desktop builds")).toBeVisible();
|
||||
await expect(page.getByText("Enable spellcheck")).toBeHidden();
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
|
||||
test("Renders on desktop", async ({ page, context }) => {
|
||||
await page.goto("http://localhost:8082");
|
||||
await expect(page.locator(".tree")).toContainText("Trilium Integration Test");
|
||||
});
|
||||
|
||||
test("Renders on mobile", async ({ page, context }) => {
|
||||
await context.addCookies([
|
||||
{
|
||||
url: "http://localhost:8082",
|
||||
name: "trilium-device",
|
||||
value: "mobile"
|
||||
}
|
||||
]);
|
||||
await page.goto("http://localhost:8082");
|
||||
await expect(page.locator(".tree")).toContainText("Trilium Integration Test");
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const expectedVersion = "0.90.3";
|
||||
|
||||
test("Displays update badge when there is a version available", async ({ page }) => {
|
||||
await page.goto("http://localhost:8080");
|
||||
await page.getByRole("button", { name: "" }).click();
|
||||
await page.getByText(`Version ${expectedVersion} is available,`).click();
|
||||
|
||||
const page1 = await page.waitForEvent("popup");
|
||||
expect(page1.url()).toBe(`https://github.com/TriliumNext/Trilium/releases/tag/v${expectedVersion}`);
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"main": "./electron-main.js",
|
||||
"bin": {
|
||||
"trilium": "src/main.js"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"server:start-safe": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nodemon src/main.ts",
|
||||
"server:start-no-dir": "cross-env TRILIUM_ENV=dev nodemon src/main.ts",
|
||||
"server:start-test": "npm run server:switch && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts",
|
||||
"server:qstart": "npm run server:switch && npm run server:start",
|
||||
"server:switch": "rimraf ./node_modules/better-sqlite3 && npm install",
|
||||
"electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev TRILIUM_PORT=37742 electron --inspect=5858 .",
|
||||
"electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
|
||||
"electron:start-nix-no-dir": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev TRILIUM_PORT=37742 nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
|
||||
"electron:start-prod-no-dir": "npm run build:prepare-dist && cross-env TRILIUM_ENV=prod electron --inspect=5858 .",
|
||||
"electron:start-prod-nix": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
|
||||
"electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
|
||||
"electron:qstart": "npm run electron:switch && npm run electron:start",
|
||||
"electron:switch": "electron-rebuild",
|
||||
"docs:build": "typedoc",
|
||||
"test": "npm run client:test && npm run server:test",
|
||||
"client:test": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app",
|
||||
"client:coverage": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app --coverage",
|
||||
"test:playwright": "playwright test --workers 1",
|
||||
"test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"test:integration-mem-db": "cross-env nodemon src/main.ts",
|
||||
"test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
|
||||
"dev:watch-dist": "tsx ./bin/watch-dist.ts",
|
||||
"dev:format-check": "eslint -c eslint.format.config.js .",
|
||||
"dev:format-fix": "eslint -c eslint.format.config.js . --fix",
|
||||
"dev:linter-check": "eslint .",
|
||||
"dev:linter-fix": "eslint . --fix",
|
||||
"chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
|
||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.56.1",
|
||||
"@stylistic/eslint-plugin": "5.5.0",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/node": "22.18.12",
|
||||
"@types/yargs": "17.0.34",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"eslint": "9.38.0",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"esm": "3.2.25",
|
||||
"jsdoc": "4.0.5",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"tslib": "2.8.1",
|
||||
"typedoc": "0.28.14",
|
||||
"typedoc-plugin-missing-exports": "4.1.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"appdmg": "0.6.6"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import etapi from "../support/etapi.js";
|
||||
/* TriliumNextTODO: port to Vitest
|
||||
etapi.describeEtapi("app_info", () => {
|
||||
it("get", async () => {
|
||||
const appInfo = await etapi.getEtapi("app-info");
|
||||
expect(appInfo.clipperProtocolVersion).toEqual("1.0");
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -1,10 +0,0 @@
|
||||
import etapi from "../support/etapi.js";
|
||||
|
||||
/* TriliumNextTODO: port to Vitest
|
||||
etapi.describeEtapi("backup", () => {
|
||||
it("create", async () => {
|
||||
const response = await etapi.putEtapiContent("backup/etapi_test");
|
||||
expect(response.status).toEqual(204);
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -1,26 +0,0 @@
|
||||
import etapi from "../support/etapi.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
/* TriliumNextTODO: port to Vitest
|
||||
etapi.describeEtapi("import", () => {
|
||||
// temporarily skip this test since test-export.zip is missing
|
||||
xit("import", async () => {
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const zipFileBuffer = fs.readFileSync(path.resolve(scriptDir, "test-export.zip"));
|
||||
|
||||
const response = await etapi.postEtapiContent("notes/root/import", zipFileBuffer);
|
||||
expect(response.status).toEqual(201);
|
||||
|
||||
const { note, branch } = await response.json();
|
||||
|
||||
expect(note.title).toEqual("test-export");
|
||||
expect(branch.parentNoteId).toEqual("root");
|
||||
|
||||
const content = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
|
||||
expect(content).toContain("test export content");
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -1,103 +0,0 @@
|
||||
import crypto from "crypto";
|
||||
import etapi from "../support/etapi.js";
|
||||
|
||||
/* TriliumNextTODO: port to Vitest
|
||||
etapi.describeEtapi("notes", () => {
|
||||
it("create", async () => {
|
||||
const { note, branch } = await etapi.postEtapi("create-note", {
|
||||
parentNoteId: "root",
|
||||
type: "text",
|
||||
title: "Hello World!",
|
||||
content: "Content",
|
||||
prefix: "Custom prefix"
|
||||
});
|
||||
|
||||
expect(note.title).toEqual("Hello World!");
|
||||
expect(branch.parentNoteId).toEqual("root");
|
||||
expect(branch.prefix).toEqual("Custom prefix");
|
||||
|
||||
const rNote = await etapi.getEtapi(`notes/${note.noteId}`);
|
||||
expect(rNote.title).toEqual("Hello World!");
|
||||
|
||||
const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
|
||||
expect(rContent).toEqual("Content");
|
||||
|
||||
const rBranch = await etapi.getEtapi(`branches/${branch.branchId}`);
|
||||
expect(rBranch.parentNoteId).toEqual("root");
|
||||
expect(rBranch.prefix).toEqual("Custom prefix");
|
||||
});
|
||||
|
||||
it("patch", async () => {
|
||||
const { note } = await etapi.postEtapi("create-note", {
|
||||
parentNoteId: "root",
|
||||
type: "text",
|
||||
title: "Hello World!",
|
||||
content: "Content"
|
||||
});
|
||||
|
||||
await etapi.patchEtapi(`notes/${note.noteId}`, {
|
||||
title: "new title",
|
||||
type: "code",
|
||||
mime: "text/apl",
|
||||
dateCreated: "2000-01-01 12:34:56.999+0200",
|
||||
utcDateCreated: "2000-01-01 10:34:56.999Z"
|
||||
});
|
||||
|
||||
const rNote = await etapi.getEtapi(`notes/${note.noteId}`);
|
||||
expect(rNote.title).toEqual("new title");
|
||||
expect(rNote.type).toEqual("code");
|
||||
expect(rNote.mime).toEqual("text/apl");
|
||||
expect(rNote.dateCreated).toEqual("2000-01-01 12:34:56.999+0200");
|
||||
expect(rNote.utcDateCreated).toEqual("2000-01-01 10:34:56.999Z");
|
||||
});
|
||||
|
||||
it("update content", async () => {
|
||||
const { note } = await etapi.postEtapi("create-note", {
|
||||
parentNoteId: "root",
|
||||
type: "text",
|
||||
title: "Hello World!",
|
||||
content: "Content"
|
||||
});
|
||||
|
||||
await etapi.putEtapiContent(`notes/${note.noteId}/content`, "new content");
|
||||
|
||||
const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
|
||||
expect(rContent).toEqual("new content");
|
||||
});
|
||||
|
||||
it("create / update binary content", async () => {
|
||||
const { note } = await etapi.postEtapi("create-note", {
|
||||
parentNoteId: "root",
|
||||
type: "file",
|
||||
title: "Hello World!",
|
||||
content: "ZZZ"
|
||||
});
|
||||
|
||||
const updatedContent = crypto.randomBytes(16);
|
||||
|
||||
await etapi.putEtapiContent(`notes/${note.noteId}/content`, updatedContent);
|
||||
|
||||
const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).arrayBuffer();
|
||||
expect(Buffer.from(new Uint8Array(rContent))).toEqual(updatedContent);
|
||||
});
|
||||
|
||||
it("delete note", async () => {
|
||||
const { note } = await etapi.postEtapi("create-note", {
|
||||
parentNoteId: "root",
|
||||
type: "text",
|
||||
title: "Hello World!",
|
||||
content: "Content"
|
||||
});
|
||||
|
||||
await etapi.deleteEtapi(`notes/${note.noteId}`);
|
||||
|
||||
const resp = await etapi.getEtapiResponse(`notes/${note.noteId}`);
|
||||
expect(resp.status).toEqual(404);
|
||||
|
||||
const error = await resp.json();
|
||||
expect(error.status).toEqual(404);
|
||||
expect(error.code).toEqual("NOTE_NOT_FOUND");
|
||||
expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
|
||||
});
|
||||
});
|
||||
*/
|
||||
@@ -1,152 +0,0 @@
|
||||
import { describe, beforeAll, afterAll } from "vitest";
|
||||
|
||||
let etapiAuthToken: string | undefined;
|
||||
|
||||
const getEtapiAuthorizationHeader = (): string => "Basic " + Buffer.from(`etapi:${etapiAuthToken}`).toString("base64");
|
||||
|
||||
const PORT: string = "9999";
|
||||
const HOST: string = "http://localhost:" + PORT;
|
||||
|
||||
type SpecDefinitionsFunc = () => void;
|
||||
|
||||
function describeEtapi(description: string, specDefinitions: SpecDefinitionsFunc): void {
|
||||
describe(description, () => {
|
||||
beforeAll(async () => {});
|
||||
|
||||
afterAll(() => {});
|
||||
|
||||
specDefinitions();
|
||||
});
|
||||
}
|
||||
|
||||
async function getEtapiResponse(url: string): Promise<Response> {
|
||||
return await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getEtapi(url: string): Promise<any> {
|
||||
const response = await getEtapiResponse(url);
|
||||
return await processEtapiResponse(response);
|
||||
}
|
||||
|
||||
async function getEtapiContent(url: string): Promise<Response> {
|
||||
const response = await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
}
|
||||
});
|
||||
|
||||
checkStatus(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function postEtapi(url: string, data: Record<string, unknown> = {}): Promise<any> {
|
||||
const response = await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
return await processEtapiResponse(response);
|
||||
}
|
||||
|
||||
async function postEtapiContent(url: string, data: BodyInit): Promise<Response> {
|
||||
const response = await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
},
|
||||
body: data
|
||||
});
|
||||
|
||||
checkStatus(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function putEtapi(url: string, data: Record<string, unknown> = {}): Promise<any> {
|
||||
const response = await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
return await processEtapiResponse(response);
|
||||
}
|
||||
|
||||
async function putEtapiContent(url: string, data?: BodyInit): Promise<Response> {
|
||||
const response = await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
},
|
||||
body: data
|
||||
});
|
||||
|
||||
checkStatus(response);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function patchEtapi(url: string, data: Record<string, unknown> = {}): Promise<any> {
|
||||
const response = await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
return await processEtapiResponse(response);
|
||||
}
|
||||
|
||||
async function deleteEtapi(url: string): Promise<any> {
|
||||
const response = await fetch(`${HOST}/etapi/${url}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
Authorization: getEtapiAuthorizationHeader()
|
||||
}
|
||||
});
|
||||
return await processEtapiResponse(response);
|
||||
}
|
||||
|
||||
async function processEtapiResponse(response: Response): Promise<any> {
|
||||
const text = await response.text();
|
||||
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
throw new Error(`ETAPI error ${response.status}: ${text}`);
|
||||
}
|
||||
|
||||
return text?.trim() ? JSON.parse(text) : null;
|
||||
}
|
||||
|
||||
function checkStatus(response: Response): void {
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
throw new Error(`ETAPI error ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
describeEtapi,
|
||||
getEtapi,
|
||||
getEtapiResponse,
|
||||
getEtapiContent,
|
||||
postEtapi,
|
||||
postEtapiContent,
|
||||
putEtapi,
|
||||
putEtapiContent,
|
||||
patchEtapi,
|
||||
deleteEtapi
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"declaration": false,
|
||||
"sourceMap": true,
|
||||
"outDir": "./build",
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"lib": ["ES2023"],
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["./src/public/app/**/*"],
|
||||
"files": [
|
||||
"./src/public/app/types.d.ts",
|
||||
"./src/public/app/types-lib.d.ts",
|
||||
"./src/types.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"entryPoints": [
|
||||
"src/services/backend_script_entrypoint.ts",
|
||||
"src/public/app/services/frontend_script_entrypoint.ts"
|
||||
],
|
||||
"plugin": [
|
||||
"typedoc-plugin-missing-exports"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "html",
|
||||
"path": "./docs/Script API"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# The development license key for premium CKEditor features.
|
||||
# Note: This key must only be used for the Trilium Notes project.
|
||||
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3ODcyNzA0MDAsImp0aSI6IjkyMWE1MWNlLTliNDMtNGRlMC1iOTQwLTc5ZjM2MDBkYjg1NyIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOiJ0cmlsaXVtIiwiZmVhdHVyZXMiOlsiVFJJTElVTSJdLCJ2YyI6ImU4YzRhMjBkIn0.hny77p-U4-jTkoqbwPytrEar5ylGCWBN7Ez3SlB8i6_mJCBIeCSTOlVQk_JMiOEq3AGykUMHzWXzjdMFwgniOw
|
||||
VITE_CKEDITOR_ENABLE_INSPECTOR=false
|
||||
@@ -1 +0,0 @@
|
||||
VITE_CKEDITOR_ENABLE_INSPECTOR=false
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript"
|
||||
},
|
||||
"target": "es2016"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [
|
||||
...baseConfig
|
||||
];
|
||||
@@ -1,83 +0,0 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.99.3",
|
||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"author": {
|
||||
"name": "Trilium Notes Team",
|
||||
"email": "contact@eliandoran.me",
|
||||
"url": "https://github.com/TriliumNext/Trilium"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
|
||||
"test": "vitest",
|
||||
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/js": "9.38.0",
|
||||
"@excalidraw/excalidraw": "0.18.0",
|
||||
"@fullcalendar/core": "6.1.19",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
"@fullcalendar/interaction": "6.1.19",
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/multimonth": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@maplibre/maplibre-gl-leaflet": "0.1.3",
|
||||
"@mermaid-js/layout-elk": "0.2.0",
|
||||
"@mind-elixir/node-menu": "5.0.0",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@triliumnext/codemirror": "workspace:*",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/highlightjs": "workspace:*",
|
||||
"@triliumnext/share-theme": "workspace:*",
|
||||
"@triliumnext/split.js": "workspace:*",
|
||||
"autocomplete.js": "0.38.1",
|
||||
"bootstrap": "5.3.8",
|
||||
"boxicons": "2.1.4",
|
||||
"color": "5.0.2",
|
||||
"dayjs": "1.11.18",
|
||||
"dayjs-plugin-utc": "0.1.2",
|
||||
"debounce": "2.2.0",
|
||||
"draggabilly": "3.0.0",
|
||||
"force-graph": "1.51.0",
|
||||
"globals": "16.4.0",
|
||||
"i18next": "25.6.0",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
"jsplumb": "2.15.6",
|
||||
"katex": "0.16.25",
|
||||
"knockout": "3.5.1",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "16.4.1",
|
||||
"mermaid": "11.12.0",
|
||||
"mind-elixir": "5.3.4",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.27.2",
|
||||
"react-i18next": "16.2.0",
|
||||
"reveal.js": "5.2.1",
|
||||
"svg-pan-zoom": "3.6.2",
|
||||
"tabulator-tables": "6.3.1",
|
||||
"vanilla-js-wheel-zoom": "9.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
||||
"@preact/preset-vite": "2.10.2",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/jquery": "3.5.33",
|
||||
"@types/leaflet": "1.9.21",
|
||||
"@types/leaflet-gpx": "1.3.8",
|
||||
"@types/mark.js": "8.11.12",
|
||||
"@types/reveal.js": "5.2.1",
|
||||
"@types/tabulator-tables": "6.3.0",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"happy-dom": "20.0.8",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.4"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
|
||||
export default `assets/v${packageJson.version}`;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "Trilium Notes",
|
||||
"short_name": "Trilium",
|
||||
"description": "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.",
|
||||
"theme_color": "#333333",
|
||||
"background_color": "#1F1F1F",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"display_override": [
|
||||
"window-controls-overlay"
|
||||
],
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,713 +0,0 @@
|
||||
import froca from "../services/froca.js";
|
||||
import RootCommandExecutor from "./root_command_executor.js";
|
||||
import Entrypoints from "./entrypoints.js";
|
||||
import options from "../services/options.js";
|
||||
import utils, { hasTouchBar } from "../services/utils.js";
|
||||
import zoomComponent from "./zoom.js";
|
||||
import TabManager from "./tab_manager.js";
|
||||
import Component from "./component.js";
|
||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||
import linkService, { type ViewScope } from "../services/link.js";
|
||||
import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js";
|
||||
import MainTreeExecutors from "./main_tree_executors.js";
|
||||
import toast from "../services/toast.js";
|
||||
import ShortcutComponent from "./shortcut_component.js";
|
||||
import { t, initLocale } from "../services/i18n.js";
|
||||
import type NoteDetailWidget from "../widgets/note_detail.js";
|
||||
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
|
||||
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
|
||||
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
|
||||
import type LoadResults from "../services/load_results.js";
|
||||
import type { Attribute } from "../services/attribute_parser.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
|
||||
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||
import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js";
|
||||
import type { NativeImage, TouchBar } from "electron";
|
||||
import TouchBarComponent from "./touch_bar.js";
|
||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||
import type CodeMirror from "@triliumnext/codemirror";
|
||||
import { StartupChecks } from "./startup_checks.js";
|
||||
import type { CreateNoteOpts } from "../services/note_create.js";
|
||||
import { ColumnComponent } from "tabulator-tables";
|
||||
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
|
||||
import type RootContainer from "../widgets/containers/root_container.js";
|
||||
import { SqlExecuteResults } from "@triliumnext/commons";
|
||||
|
||||
interface Layout {
|
||||
getRootWidget: (appContext: AppContext) => RootContainer;
|
||||
}
|
||||
|
||||
export interface BeforeUploadListener extends Component {
|
||||
beforeUnloadEvent(): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for the data/arguments for a given command (see {@link CommandMappings}).
|
||||
*/
|
||||
export interface CommandData {
|
||||
ntxId?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a set of commands that are triggered from the context menu, providing information such as the selected note.
|
||||
*/
|
||||
export interface ContextMenuCommandData extends CommandData {
|
||||
node: Fancytree.FancytreeNode;
|
||||
notePath?: string;
|
||||
noteId?: string;
|
||||
selectedOrActiveBranchIds: string[];
|
||||
selectedOrActiveNoteIds?: string[];
|
||||
}
|
||||
|
||||
export interface NoteCommandData extends CommandData {
|
||||
notePath?: string | null;
|
||||
hoistedNoteId?: string | null;
|
||||
viewScope?: ViewScope;
|
||||
}
|
||||
|
||||
export interface ExecuteCommandData<T> extends CommandData {
|
||||
resolve: (data: T) => void;
|
||||
}
|
||||
|
||||
export interface NoteSwitchedContext {
|
||||
noteContext: NoteContext;
|
||||
notePath: string | null | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The keys represent the different commands that can be triggered via {@link AppContext#triggerCommand} (first argument), and the values represent the data or arguments definition of the given command. All data for commands must extend {@link CommandData}.
|
||||
*/
|
||||
export type CommandMappings = {
|
||||
"api-log-messages": CommandData;
|
||||
focusTree: CommandData;
|
||||
focusOnTitle: CommandData;
|
||||
focusOnDetail: CommandData;
|
||||
searchNotes: CommandData & {
|
||||
searchString?: string;
|
||||
ancestorNoteId?: string | null;
|
||||
};
|
||||
closeTocCommand: CommandData;
|
||||
closeHlt: CommandData;
|
||||
showLaunchBarSubtree: CommandData;
|
||||
showHiddenSubtree: CommandData;
|
||||
showSQLConsoleHistory: CommandData;
|
||||
logout: CommandData;
|
||||
switchToMobileVersion: CommandData;
|
||||
switchToDesktopVersion: CommandData;
|
||||
showRevisions: CommandData & {
|
||||
noteId?: string | null;
|
||||
};
|
||||
showLlmChat: CommandData;
|
||||
createAiChat: CommandData;
|
||||
showOptions: CommandData & {
|
||||
section: string;
|
||||
};
|
||||
showExportDialog: CommandData & {
|
||||
notePath: string;
|
||||
defaultType: "single" | "subtree";
|
||||
};
|
||||
showDeleteNotesDialog: CommandData & {
|
||||
branchIdsToDelete: string[];
|
||||
callback: (value: ResolveOptions) => void;
|
||||
forceDeleteAllClones: boolean;
|
||||
};
|
||||
showConfirmDeleteNoteBoxWithNoteDialog: ConfirmWithTitleOptions;
|
||||
openedFileUpdated: CommandData & {
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
lastModifiedMs?: number;
|
||||
filePath: string;
|
||||
};
|
||||
focusAndSelectTitle: CommandData & {
|
||||
isNewNote?: boolean;
|
||||
};
|
||||
showPromptDialog: PromptDialogOptions;
|
||||
showInfoDialog: ConfirmWithMessageOptions;
|
||||
showConfirmDialog: ConfirmWithMessageOptions;
|
||||
showRecentChanges: CommandData & { ancestorNoteId: string };
|
||||
showImportDialog: CommandData & { noteId: string };
|
||||
openNewNoteSplit: NoteCommandData;
|
||||
openInWindow: NoteCommandData;
|
||||
openInPopup: CommandData & { noteIdOrPath: string; };
|
||||
openNoteInNewTab: CommandData;
|
||||
openNoteInNewSplit: CommandData;
|
||||
openNoteInNewWindow: CommandData;
|
||||
openAboutDialog: CommandData;
|
||||
hideFloatingButtons: {};
|
||||
hideLeftPane: CommandData;
|
||||
showCpuArchWarning: CommandData;
|
||||
showLeftPane: CommandData;
|
||||
showAttachments: CommandData;
|
||||
showSearchHistory: CommandData;
|
||||
showShareSubtree: CommandData;
|
||||
hoistNote: CommandData & { noteId: string };
|
||||
leaveProtectedSession: CommandData;
|
||||
enterProtectedSession: CommandData;
|
||||
noteContextReorder: CommandData & {
|
||||
ntxIdsInOrder: string[];
|
||||
oldMainNtxId?: string | null;
|
||||
newMainNtxId?: string | null;
|
||||
};
|
||||
openInTab: ContextMenuCommandData;
|
||||
openNoteInSplit: ContextMenuCommandData;
|
||||
openNoteInPopup: ContextMenuCommandData;
|
||||
toggleNoteHoisting: ContextMenuCommandData;
|
||||
insertNoteAfter: ContextMenuCommandData;
|
||||
insertChildNote: ContextMenuCommandData;
|
||||
delete: ContextMenuCommandData;
|
||||
editNoteTitle: {};
|
||||
protectSubtree: ContextMenuCommandData;
|
||||
unprotectSubtree: ContextMenuCommandData;
|
||||
openBulkActionsDialog:
|
||||
| ContextMenuCommandData
|
||||
| {
|
||||
selectedOrActiveNoteIds?: string[];
|
||||
};
|
||||
editBranchPrefix: ContextMenuCommandData;
|
||||
convertNoteToAttachment: ContextMenuCommandData;
|
||||
duplicateSubtree: ContextMenuCommandData;
|
||||
expandSubtree: ContextMenuCommandData;
|
||||
collapseSubtree: ContextMenuCommandData;
|
||||
sortChildNotes: ContextMenuCommandData;
|
||||
copyNotePathToClipboard: ContextMenuCommandData;
|
||||
recentChangesInSubtree: ContextMenuCommandData;
|
||||
cutNotesToClipboard: ContextMenuCommandData;
|
||||
copyNotesToClipboard: ContextMenuCommandData;
|
||||
pasteNotesFromClipboard: ContextMenuCommandData;
|
||||
pasteNotesAfterFromClipboard: ContextMenuCommandData;
|
||||
moveNotesTo: ContextMenuCommandData;
|
||||
cloneNotesTo: ContextMenuCommandData;
|
||||
deleteNotes: ContextMenuCommandData;
|
||||
importIntoNote: ContextMenuCommandData;
|
||||
exportNote: ContextMenuCommandData;
|
||||
searchInSubtree: CommandData & { notePath: string; };
|
||||
moveNoteUp: ContextMenuCommandData;
|
||||
moveNoteDown: ContextMenuCommandData;
|
||||
moveNoteUpInHierarchy: ContextMenuCommandData;
|
||||
moveNoteDownInHierarchy: ContextMenuCommandData;
|
||||
selectAllNotesInParent: ContextMenuCommandData;
|
||||
|
||||
createNoteIntoInbox: CommandData;
|
||||
|
||||
addNoteLauncher: ContextMenuCommandData;
|
||||
addScriptLauncher: ContextMenuCommandData;
|
||||
addWidgetLauncher: ContextMenuCommandData;
|
||||
addSpacerLauncher: ContextMenuCommandData;
|
||||
moveLauncherToVisible: ContextMenuCommandData;
|
||||
moveLauncherToAvailable: ContextMenuCommandData;
|
||||
resetLauncher: ContextMenuCommandData;
|
||||
|
||||
executeInActiveNoteDetailWidget: CommandData & {
|
||||
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
|
||||
};
|
||||
executeWithTextEditor: CommandData &
|
||||
ExecuteCommandData<CKTextEditor> & {
|
||||
callback?: GetTextEditorCallback;
|
||||
};
|
||||
executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirror>;
|
||||
/**
|
||||
* Called upon when attempting to retrieve the content element of a {@link NoteContext}.
|
||||
* Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}.
|
||||
*/
|
||||
executeWithContentElement: CommandData & ExecuteCommandData<JQuery<HTMLElement>>;
|
||||
executeWithTypeWidget: CommandData & ExecuteCommandData<TypeWidget | null>;
|
||||
addTextToActiveEditor: CommandData & {
|
||||
text: string;
|
||||
};
|
||||
/** Works only in the electron context menu. */
|
||||
replaceMisspelling: CommandData;
|
||||
|
||||
importMarkdownInline: CommandData;
|
||||
showPasswordNotSet: CommandData;
|
||||
showProtectedSessionPasswordDialog: CommandData;
|
||||
showUploadAttachmentsDialog: CommandData & { noteId: string };
|
||||
showIncludeNoteDialog: CommandData & { textTypeWidget: EditableTextTypeWidget };
|
||||
showAddLinkDialog: CommandData & { textTypeWidget: EditableTextTypeWidget, text: string };
|
||||
closeProtectedSessionPasswordDialog: CommandData;
|
||||
copyImageReferenceToClipboard: CommandData;
|
||||
copyImageToClipboard: CommandData;
|
||||
updateAttributesList: {
|
||||
attributes: Attribute[];
|
||||
};
|
||||
|
||||
addNewLabel: CommandData;
|
||||
addNewRelation: CommandData;
|
||||
addNewLabelDefinition: CommandData;
|
||||
addNewRelationDefinition: CommandData;
|
||||
|
||||
cloneNoteIdsTo: CommandData & {
|
||||
noteIds: string[];
|
||||
};
|
||||
moveBranchIdsTo: CommandData & {
|
||||
branchIds: string[];
|
||||
};
|
||||
/** Sets the active {@link Screen} (e.g. to toggle the tree sidebar). It triggers the {@link EventMappings.activeScreenChanged} event, but only if the provided <em>screen</em> is different than the current one. */
|
||||
setActiveScreen: CommandData & {
|
||||
screen: Screen;
|
||||
};
|
||||
closeTab: CommandData;
|
||||
closeToc: CommandData;
|
||||
closeOtherTabs: CommandData;
|
||||
closeRightTabs: CommandData;
|
||||
closeAllTabs: CommandData;
|
||||
reopenLastTab: CommandData;
|
||||
moveTabToNewWindow: CommandData;
|
||||
copyTabToNewWindow: CommandData;
|
||||
closeActiveTab: CommandData & {
|
||||
$el: JQuery<HTMLElement>;
|
||||
};
|
||||
setZoomFactorAndSave: {
|
||||
zoomFactor: string;
|
||||
};
|
||||
|
||||
reEvaluateRightPaneVisibility: CommandData;
|
||||
runActiveNote: CommandData;
|
||||
scrollContainerToCommand: CommandData & {
|
||||
position: number;
|
||||
};
|
||||
scrollToEnd: CommandData;
|
||||
closeThisNoteSplit: CommandData;
|
||||
moveThisNoteSplit: CommandData & { isMovingLeft: boolean };
|
||||
jumpToNote: CommandData;
|
||||
commandPalette: CommandData;
|
||||
|
||||
// Keyboard shortcuts
|
||||
backInNoteHistory: CommandData;
|
||||
forwardInNoteHistory: CommandData;
|
||||
forceSaveRevision: CommandData;
|
||||
scrollToActiveNote: CommandData;
|
||||
quickSearch: CommandData;
|
||||
collapseTree: CommandData;
|
||||
createNoteAfter: CommandData;
|
||||
createNoteInto: CommandData;
|
||||
addNoteAboveToSelection: CommandData;
|
||||
addNoteBelowToSelection: CommandData;
|
||||
openNewTab: CommandData;
|
||||
activateNextTab: CommandData;
|
||||
activatePreviousTab: CommandData;
|
||||
openNewWindow: CommandData;
|
||||
toggleTray: CommandData;
|
||||
firstTab: CommandData;
|
||||
secondTab: CommandData;
|
||||
thirdTab: CommandData;
|
||||
fourthTab: CommandData;
|
||||
fifthTab: CommandData;
|
||||
sixthTab: CommandData;
|
||||
seventhTab: CommandData;
|
||||
eigthTab: CommandData;
|
||||
ninthTab: CommandData;
|
||||
lastTab: CommandData;
|
||||
showNoteSource: CommandData;
|
||||
showSQLConsole: CommandData;
|
||||
showBackendLog: CommandData;
|
||||
showCheatsheet: CommandData;
|
||||
showHelp: CommandData;
|
||||
addLinkToText: CommandData;
|
||||
followLinkUnderCursor: CommandData;
|
||||
insertDateTimeToText: CommandData;
|
||||
pasteMarkdownIntoText: CommandData;
|
||||
cutIntoNote: CommandData;
|
||||
addIncludeNoteToText: CommandData;
|
||||
editReadOnlyNote: CommandData;
|
||||
toggleRibbonTabClassicEditor: CommandData;
|
||||
toggleRibbonTabBasicProperties: CommandData;
|
||||
toggleRibbonTabBookProperties: CommandData;
|
||||
toggleRibbonTabFileProperties: CommandData;
|
||||
toggleRibbonTabImageProperties: CommandData;
|
||||
toggleRibbonTabOwnedAttributes: CommandData;
|
||||
toggleRibbonTabInheritedAttributes: CommandData;
|
||||
toggleRibbonTabPromotedAttributes: CommandData;
|
||||
toggleRibbonTabNoteMap: CommandData;
|
||||
toggleRibbonTabNoteInfo: CommandData;
|
||||
toggleRibbonTabNotePaths: CommandData;
|
||||
toggleRibbonTabSimilarNotes: CommandData;
|
||||
toggleRightPane: CommandData;
|
||||
printActiveNote: CommandData;
|
||||
exportAsPdf: CommandData;
|
||||
openNoteExternally: CommandData;
|
||||
openNoteCustom: CommandData;
|
||||
renderActiveNote: CommandData;
|
||||
unhoist: CommandData;
|
||||
reloadFrontendApp: CommandData;
|
||||
openDevTools: CommandData;
|
||||
findInText: CommandData;
|
||||
toggleLeftPane: CommandData;
|
||||
toggleFullscreen: CommandData;
|
||||
zoomOut: CommandData;
|
||||
zoomIn: CommandData;
|
||||
zoomReset: CommandData;
|
||||
copyWithoutFormatting: CommandData;
|
||||
|
||||
// Geomap
|
||||
deleteFromMap: { noteId: string };
|
||||
|
||||
toggleZenMode: CommandData;
|
||||
|
||||
updateAttributeList: CommandData & { attributes: Attribute[] };
|
||||
saveAttributes: CommandData;
|
||||
reloadAttributes: CommandData;
|
||||
refreshNoteList: CommandData & { noteId: string };
|
||||
|
||||
refreshResults: {};
|
||||
refreshSearchDefinition: {};
|
||||
|
||||
geoMapCreateChildNote: CommandData;
|
||||
|
||||
// Table view
|
||||
addNewRow: CommandData & {
|
||||
customOpts: CreateNoteOpts;
|
||||
parentNotePath?: string;
|
||||
};
|
||||
addNewTableColumn: CommandData & {
|
||||
columnToEdit?: ColumnComponent;
|
||||
referenceColumn?: ColumnComponent;
|
||||
direction?: "before" | "after";
|
||||
type?: "label" | "relation";
|
||||
};
|
||||
deleteTableColumn: CommandData & {
|
||||
columnToDelete?: ColumnComponent;
|
||||
};
|
||||
|
||||
buildTouchBar: CommandData & {
|
||||
TouchBar: typeof TouchBar;
|
||||
buildIcon(name: string): NativeImage;
|
||||
};
|
||||
refreshTouchBar: CommandData;
|
||||
reloadTextEditor: CommandData;
|
||||
chooseNoteType: CommandData & {
|
||||
callback: ChooseNoteTypeCallback
|
||||
}
|
||||
};
|
||||
|
||||
type EventMappings = {
|
||||
initialRenderComplete: {};
|
||||
frocaReloaded: {};
|
||||
setLeftPaneVisibility: {
|
||||
leftPaneVisible: boolean | null;
|
||||
}
|
||||
protectedSessionStarted: {};
|
||||
notesReloaded: {
|
||||
noteIds: string[];
|
||||
};
|
||||
refreshIncludedNote: {
|
||||
noteId: string;
|
||||
};
|
||||
apiLogMessages: {
|
||||
noteId: string;
|
||||
messages: string[];
|
||||
};
|
||||
entitiesReloaded: {
|
||||
loadResults: LoadResults;
|
||||
};
|
||||
addNewLabel: CommandData;
|
||||
addNewRelation: CommandData;
|
||||
sqlQueryResults: CommandData & {
|
||||
results: SqlExecuteResults;
|
||||
};
|
||||
readOnlyTemporarilyDisabled: {
|
||||
noteContext: NoteContext;
|
||||
};
|
||||
/** Triggered when the {@link CommandMappings.setActiveScreen} command is invoked. */
|
||||
activeScreenChanged: {
|
||||
activeScreen: Screen;
|
||||
};
|
||||
activeContextChanged: {
|
||||
noteContext: NoteContext;
|
||||
};
|
||||
beforeNoteSwitch: {
|
||||
noteContext: NoteContext;
|
||||
};
|
||||
beforeNoteContextRemove: {
|
||||
ntxIds: string[];
|
||||
};
|
||||
noteSwitched: NoteSwitchedContext;
|
||||
noteSwitchedAndActivated: NoteSwitchedContext;
|
||||
setNoteContext: {
|
||||
noteContext: NoteContext;
|
||||
};
|
||||
reEvaluateHighlightsListWidgetVisibility: {
|
||||
noteId: string | undefined;
|
||||
};
|
||||
reEvaluateTocWidgetVisibility: {
|
||||
noteId: string | undefined;
|
||||
};
|
||||
showHighlightsListWidget: {
|
||||
noteId: string;
|
||||
};
|
||||
showTocWidget: {
|
||||
noteId: string;
|
||||
};
|
||||
showSearchError: {
|
||||
error: string;
|
||||
};
|
||||
searchRefreshed: { ntxId?: string | null };
|
||||
hoistedNoteChanged: {
|
||||
noteId: string;
|
||||
ntxId: string | null;
|
||||
};
|
||||
contextsReopened: {
|
||||
ntxId?: string;
|
||||
mainNtxId: string | null;
|
||||
tabPosition: number;
|
||||
afterNtxId?: string;
|
||||
};
|
||||
noteDetailRefreshed: {
|
||||
ntxId?: string | null;
|
||||
};
|
||||
noteContextReorder: {
|
||||
oldMainNtxId: string;
|
||||
newMainNtxId: string;
|
||||
ntxIdsInOrder: string[];
|
||||
};
|
||||
newNoteContextCreated: {
|
||||
noteContext: NoteContext;
|
||||
};
|
||||
noteContextRemoved: {
|
||||
ntxIds: string[];
|
||||
};
|
||||
exportSvg: { ntxId: string | null | undefined; };
|
||||
exportPng: { ntxId: string | null | undefined; };
|
||||
geoMapCreateChildNote: {
|
||||
ntxId: string | null | undefined; // TODO: deduplicate ntxId
|
||||
};
|
||||
tabReorder: {
|
||||
ntxIdsInOrder: string[];
|
||||
};
|
||||
refreshNoteList: {
|
||||
noteId: string;
|
||||
};
|
||||
noteTypeMimeChanged: { noteId: string };
|
||||
zenModeChanged: { isEnabled: boolean };
|
||||
relationMapCreateChildNote: { ntxId: string | null | undefined };
|
||||
relationMapResetPanZoom: { ntxId: string | null | undefined };
|
||||
relationMapResetZoomIn: { ntxId: string | null | undefined };
|
||||
relationMapResetZoomOut: { ntxId: string | null | undefined };
|
||||
activeNoteChanged: {};
|
||||
showAddLinkDialog: {
|
||||
textTypeWidget: EditableTextTypeWidget;
|
||||
text: string;
|
||||
};
|
||||
showIncludeDialog: {
|
||||
textTypeWidget: EditableTextTypeWidget;
|
||||
};
|
||||
openBulkActionsDialog: {
|
||||
selectedOrActiveNoteIds: string[];
|
||||
};
|
||||
cloneNoteIdsTo: {
|
||||
noteIds: string[];
|
||||
};
|
||||
refreshData: { ntxId: string | null | undefined };
|
||||
};
|
||||
|
||||
export type EventListener<T extends EventNames> = {
|
||||
[key in T as `${key}Event`]: (data: EventData<T>) => void;
|
||||
};
|
||||
|
||||
export type CommandListener<T extends CommandNames> = {
|
||||
[key in T as `${key}Command`]: (data: CommandListenerData<T>) => void;
|
||||
};
|
||||
|
||||
export type CommandListenerData<T extends CommandNames> = CommandMappings[T];
|
||||
|
||||
type CommandAndEventMappings = CommandMappings & EventMappings;
|
||||
type EventOnlyNames = keyof EventMappings;
|
||||
export type EventNames = CommandNames | EventOnlyNames;
|
||||
export type EventData<T extends EventNames> = CommandAndEventMappings[T];
|
||||
|
||||
/**
|
||||
* This type is a discriminated union which contains all the possible commands that can be triggered via {@link AppContext.triggerCommand}.
|
||||
*/
|
||||
export type CommandNames = keyof CommandMappings;
|
||||
|
||||
type FilterByValueType<T, ValueType> = { [K in keyof T]: T[K] extends ValueType ? K : never }[keyof T];
|
||||
|
||||
/**
|
||||
* Generic which filters {@link CommandNames} to provide only those commands that take in as data the desired implementation of {@link CommandData}. Mostly useful for contextual menu, to enforce consistency in the commands.
|
||||
*/
|
||||
export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMappings, FilterByValueType<CommandMappings, T>>;
|
||||
|
||||
export class AppContext extends Component {
|
||||
isMainWindow: boolean;
|
||||
components: Component[];
|
||||
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
|
||||
tabManager!: TabManager;
|
||||
layout?: Layout;
|
||||
noteTreeWidget?: NoteTreeWidget;
|
||||
|
||||
lastSearchString?: string;
|
||||
|
||||
constructor(isMainWindow: boolean) {
|
||||
super();
|
||||
|
||||
this.isMainWindow = isMainWindow;
|
||||
// non-widget/layout components needed for the application
|
||||
this.components = [];
|
||||
this.beforeUnloadListeners = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called as soon as possible, before the creation of any components since this method is in charge of initializing the locale. Any attempts to read translation before this method is called will result in `undefined`.
|
||||
*/
|
||||
async earlyInit() {
|
||||
await options.initializedPromise;
|
||||
await initLocale();
|
||||
}
|
||||
|
||||
setLayout(layout: Layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
async start() {
|
||||
this.initComponents();
|
||||
this.renderWidgets();
|
||||
|
||||
await froca.initializedPromise;
|
||||
|
||||
this.tabManager.loadTabs();
|
||||
|
||||
const bundleService = (await import("../services/bundle.js")).default;
|
||||
setTimeout(() => bundleService.executeStartupBundles(), 2000);
|
||||
}
|
||||
|
||||
initComponents() {
|
||||
this.tabManager = new TabManager();
|
||||
|
||||
this.components = [
|
||||
this.tabManager,
|
||||
new RootCommandExecutor(),
|
||||
new Entrypoints(),
|
||||
new MainTreeExecutors(),
|
||||
new ShortcutComponent(),
|
||||
new StartupChecks()
|
||||
];
|
||||
|
||||
if (utils.isMobile()) {
|
||||
this.components.push(new MobileScreenSwitcherExecutor());
|
||||
}
|
||||
|
||||
for (const component of this.components) {
|
||||
this.child(component);
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
this.child(zoomComponent);
|
||||
}
|
||||
|
||||
if (hasTouchBar) {
|
||||
this.child(new TouchBarComponent());
|
||||
}
|
||||
}
|
||||
|
||||
renderWidgets() {
|
||||
if (!this.layout) {
|
||||
throw new Error("Missing layout.");
|
||||
}
|
||||
|
||||
const rootWidget = this.layout.getRootWidget(this);
|
||||
const $renderedWidget = rootWidget.render();
|
||||
|
||||
keyboardActionsService.updateDisplayedShortcuts($renderedWidget);
|
||||
|
||||
$("body").append($renderedWidget);
|
||||
|
||||
$renderedWidget.on("click", "[data-trigger-command]", function () {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commandName = $(this).attr("data-trigger-command");
|
||||
const $component = $(this).closest(".component");
|
||||
const component = $component.prop("component");
|
||||
|
||||
component.triggerCommand(commandName, { $el: $(this) });
|
||||
});
|
||||
|
||||
this.child(rootWidget as Component);
|
||||
|
||||
this.triggerEvent("initialRenderComplete", {});
|
||||
}
|
||||
|
||||
triggerEvent<K extends EventNames>(name: K, data: EventData<K>) {
|
||||
return this.handleEvent(name, data);
|
||||
}
|
||||
|
||||
triggerCommand<K extends CommandNames>(name: K, _data?: CommandMappings[K]) {
|
||||
const data = _data || {};
|
||||
for (const executor of this.components) {
|
||||
const fun = (executor as any)[`${name}Command`];
|
||||
|
||||
if (fun) {
|
||||
return executor.callMethod(fun, data);
|
||||
}
|
||||
}
|
||||
|
||||
// this might hint at error, but sometimes this is used by components which are at different places
|
||||
// in the component tree to communicate with each other
|
||||
console.debug(`Unhandled command ${name}, converting to event.`);
|
||||
|
||||
return this.triggerEvent(name, data as CommandAndEventMappings[K]);
|
||||
}
|
||||
|
||||
getComponentByEl(el: HTMLElement) {
|
||||
return $(el).closest("[data-component-id]").prop("component");
|
||||
}
|
||||
|
||||
addBeforeUnloadListener(obj: BeforeUploadListener | (() => boolean)) {
|
||||
if (typeof WeakRef !== "function") {
|
||||
// older browsers don't support WeakRef
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof obj === "object") {
|
||||
this.beforeUnloadListeners.push(new WeakRef<BeforeUploadListener>(obj));
|
||||
} else {
|
||||
this.beforeUnloadListeners.push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const appContext = new AppContext(window.glob.isMainWindow);
|
||||
|
||||
// we should save all outstanding changes before the page/app is closed
|
||||
$(window).on("beforeunload", () => {
|
||||
let allSaved = true;
|
||||
|
||||
appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter((wr) => typeof wr === "function" || !!wr.deref());
|
||||
|
||||
for (const listener of appContext.beforeUnloadListeners) {
|
||||
if (typeof listener === "object") {
|
||||
const component = listener.deref();
|
||||
|
||||
if (!component) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!component.beforeUnloadEvent()) {
|
||||
console.log(`Component ${component.componentId} is not finished saving its state.`);
|
||||
allSaved = false;
|
||||
}
|
||||
} else {
|
||||
if (!listener()) {
|
||||
allSaved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allSaved) {
|
||||
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
|
||||
return "some string";
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on("hashchange", function () {
|
||||
const { notePath, ntxId, viewScope, searchString } = linkService.parseNavigationStateFromUrl(window.location.href);
|
||||
|
||||
if (notePath || ntxId) {
|
||||
appContext.tabManager.switchToNoteContext(ntxId, notePath, viewScope);
|
||||
} else if (searchString) {
|
||||
appContext.triggerCommand("searchNotes", { searchString });
|
||||
}
|
||||
});
|
||||
|
||||
export default appContext;
|
||||
@@ -1,165 +0,0 @@
|
||||
import utils from "../services/utils.js";
|
||||
import type { CommandMappings, CommandNames, EventData, EventNames } from "./app_context.js";
|
||||
|
||||
type EventHandler = ((data: any) => void);
|
||||
|
||||
/**
|
||||
* Abstract class for all components in the Trilium's frontend.
|
||||
*
|
||||
* Contains also event implementation with following properties:
|
||||
* - event / command distribution is synchronous which among others mean that events are well-ordered - event
|
||||
* which was sent out first will also be processed first by the component
|
||||
* - execution of the event / command is asynchronous - each component executes the event on its own without regard for
|
||||
* other components.
|
||||
* - although the execution is async, we are collecting all the promises, and therefore it is possible to wait until the
|
||||
* event / command is executed in all components - by simply awaiting the `triggerEvent()`.
|
||||
*/
|
||||
export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
|
||||
$widget!: JQuery<HTMLElement>;
|
||||
componentId: string;
|
||||
children: ChildT[];
|
||||
initialized: Promise<void> | null;
|
||||
parent?: TypedComponent<any>;
|
||||
_position!: number;
|
||||
private listeners: Record<string, EventHandler[]> | null = {};
|
||||
|
||||
constructor() {
|
||||
this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`;
|
||||
this.children = [];
|
||||
this.initialized = null;
|
||||
}
|
||||
|
||||
get sanitizedClassName() {
|
||||
// webpack mangles names and sometimes uses unsafe characters
|
||||
return this.constructor.name.replace(/[^A-Z0-9]/gi, "_");
|
||||
}
|
||||
|
||||
get position() {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
set position(newPosition: number) {
|
||||
this._position = newPosition;
|
||||
}
|
||||
|
||||
setParent(parent: TypedComponent<any>) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
child(...components: ChildT[]) {
|
||||
for (const component of components) {
|
||||
component.setParent(this);
|
||||
|
||||
this.children.push(component);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
handleEvent<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null | undefined {
|
||||
try {
|
||||
const callMethodPromise = this.initialized ? this.initialized.then(() => this.callMethod((this as any)[`${name}Event`], data)) : this.callMethod((this as any)[`${name}Event`], data);
|
||||
|
||||
const childrenPromise = this.handleEventInChildren(name, data);
|
||||
|
||||
// don't create promises if not needed (optimization)
|
||||
return callMethodPromise && childrenPromise ? Promise.all([callMethodPromise, childrenPromise]) : callMethodPromise || childrenPromise;
|
||||
} catch (e: any) {
|
||||
console.error(`Handling of event '${name}' failed in ${this.constructor.name} with error ${e.message} ${e.stack}`);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
triggerEvent<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown> | undefined | null {
|
||||
return this.parent?.triggerEvent(name, data);
|
||||
}
|
||||
|
||||
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
// Handle React children.
|
||||
if (this.listeners?.[name]) {
|
||||
for (const listener of this.listeners[name]) {
|
||||
listener(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle legacy children.
|
||||
for (const child of this.children) {
|
||||
const ret = child.handleEvent(name, data) as Promise<void>;
|
||||
|
||||
if (ret) {
|
||||
promises.push(ret);
|
||||
}
|
||||
}
|
||||
|
||||
// don't create promises if not needed (optimization)
|
||||
return promises.length > 0 ? Promise.all(promises) : null;
|
||||
}
|
||||
|
||||
triggerCommand<K extends CommandNames>(name: K, data?: CommandMappings[K]): Promise<unknown> | undefined | null {
|
||||
const fun = (this as any)[`${name}Command`];
|
||||
|
||||
if (fun) {
|
||||
return this.callMethod(fun, data);
|
||||
} else if (this.parent) {
|
||||
return this.parent.triggerCommand(name, data);
|
||||
}
|
||||
}
|
||||
|
||||
callMethod(fun: (arg: unknown) => Promise<unknown>, data: unknown) {
|
||||
if (typeof fun !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
const promise = fun.call(this, data);
|
||||
|
||||
const took = Date.now() - startTime;
|
||||
|
||||
if (glob.isDev && took > 20) {
|
||||
// measuring only sync handlers
|
||||
console.log(`Call to ${fun.name} in ${this.componentId} took ${took}ms`);
|
||||
}
|
||||
|
||||
if (glob.isDev && promise) {
|
||||
return utils.timeLimit(promise, 20000, `Time limit failed on ${this.constructor.name} with ${fun.name}`);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
registerHandler<T extends EventNames>(name: T, handler: EventHandler) {
|
||||
if (!this.listeners) {
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
if (!this.listeners[name]) {
|
||||
this.listeners[name] = [];
|
||||
}
|
||||
|
||||
if (this.listeners[name].includes(handler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listeners[name].push(handler);
|
||||
}
|
||||
|
||||
removeHandler<T extends EventNames>(name: T, handler: EventHandler) {
|
||||
if (!this.listeners?.[name]?.includes(handler)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.listeners[name] = this.listeners[name]
|
||||
.filter(listener => listener !== handler);
|
||||
|
||||
if (!this.listeners[name].length) {
|
||||
delete this.listeners[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class Component extends TypedComponent<Component> {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user