Compare commits

...

72 Commits

Author SHA1 Message Date
arch
f25507efeb
Merge 5d5fd2079a110a41c89f5616df5c5277fe67bb4f into a867a25d5f09e410d0c73b7fbeab18b4641f484a 2025-12-02 12:11:35 +00:00
Elian Doran
a867a25d5f
fix(markdown): show 'import successful' when canceling markdown import (#7899)
Some checks failed
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
Deploy Documentation / Build and Deploy Documentation (push) Has been cancelled
Deploy website / Build & deploy website (push) Has been cancelled
2025-12-02 12:11:25 +00:00
Elian Doran
10910ac2ed
fix(backlinks): list not refreshing & not reacting to all changes 2025-12-02 13:30:37 +02:00
Elian Doran
514f5a0c81
chore(markdown_import): don't display anything on empty input 2025-12-02 12:26:47 +02:00
Elian Doran
6b18ed6477
refactor(markdown_import): deduplicate submit 2025-12-02 12:24:22 +02:00
Elian Doran
e07b6cc409
Merge remote-tracking branch 'origin/main' into fix/markdown
; Conflicts:
;	apps/client/src/stylesheets/style.css
2025-12-02 12:20:01 +02:00
Elian Doran
5ae67fda7f
fix(ckeditor/math): math can be inserted in code blocks (closes #7913) 2025-12-02 12:13:40 +02:00
Elian Doran
1a03c0ca9f
fix(collections/calendar): events not updating sometimes 2025-12-02 10:56:58 +02:00
Elian Doran
dc211b4d00
fix(ckeditor): markdown icon broken due to a workaround 2025-12-02 10:27:13 +02:00
Elian Doran
2e767ffde1
fix(ribbon): formatting toolbar not showing up if focus on edited notes is active 2025-12-02 10:13:54 +02:00
Elian Doran
9f74a54c0d
fix(react/type_widgets): revision causes jump at start of editor (closes #7912) 2025-12-02 10:06:08 +02:00
Elian Doran
a7870495ac
docs(user): mention split focus 2025-12-02 09:44:20 +02:00
Elian Doran
a8ec323ea8
docs(user): document split functionality on mobile 2025-12-02 09:42:38 +02:00
Elian Doran
4b7d243406
Prototype/mobile split (#7906) 2025-12-02 07:35:34 +00:00
Elian Doran
1855588270
chore(client): fix missing argument in context menu 2025-12-02 09:02:11 +02:00
Elian Doran
e545e0c3a5
Translations update from Hosted Weblate (#7919) 2025-12-02 06:50:19 +00:00
Hosted Weblate
b24a0f1595
Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2025-12-02 06:48:51 +00:00
Elian Doran
3f2bfc3050
docs: add comprehensive AI coding agent instructions for copilot-instructions (#7917) 2025-12-02 06:48:40 +00:00
Elian Doran
66f100d534
chore(i18n/client): remove two unused messages 2025-12-02 08:46:53 +02:00
Elian Doran
e641d5ba0f
Translations update from Hosted Weblate (#7911) 2025-12-02 06:46:23 +00:00
Marble127
6f8cebf954
Translated using Weblate (Dutch)
Currently translated at 26.2% (31 of 118 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/nl/
2025-12-02 07:26:27 +01:00
Marble127
2d2f2457cb
Translated using Weblate (Dutch)
Currently translated at 3.2% (53 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/nl/
2025-12-02 07:26:26 +01:00
Marble127
68fa243af4
Translated using Weblate (Dutch)
Currently translated at 12.8% (50 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/nl/
2025-12-02 07:26:25 +01:00
Marble127
9cb133c8a6
Translated using Weblate (Dutch)
Currently translated at 20.3% (31 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nl/
2025-12-02 07:26:25 +01:00
green
33f6c75917
Translated using Weblate (Japanese)
Currently translated at 100.0% (1639 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-02 07:26:24 +01:00
Lluís Forns
49acd5e8c2
Translated using Weblate (Catalan)
Currently translated at 6.8% (112 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ca/
2025-12-02 07:26:23 +01:00
Elian Doran
85266adf20
Translated using Weblate (Spanish)
Currently translated at 99.6% (1633 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2025-12-02 07:26:22 +01:00
Elian Doran
27edea8380
Translated using Weblate (French)
Currently translated at 98.3% (1612 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/fr/
2025-12-02 07:26:22 +01:00
Elian Doran
dccddb8d43
Translated using Weblate (Portuguese)
Currently translated at 94.6% (1551 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt/
2025-12-02 07:26:21 +01:00
Elian Doran
5f9d7a223a
Translated using Weblate (Arabic)
Currently translated at 64.0% (1049 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ar/
2025-12-02 07:26:20 +01:00
Elian Doran
29b71262d6
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (1636 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-02 07:26:20 +01:00
Lluís Forns
0cfcbcf5df
Translated using Weblate (Catalan)
Currently translated at 1.6% (2 of 118 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ca/
2025-12-02 07:26:19 +01:00
Elian Doran
e1258384d8
Translated using Weblate (Polish)
Currently translated at 31.1% (511 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-02 07:26:18 +01:00
Lluís Forns
8837eddd40
Translated using Weblate (Catalan)
Currently translated at 18.2% (71 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ca/
2025-12-02 07:26:17 +01:00
Lluís Forns
f919d46e9a
Translated using Weblate (Catalan)
Currently translated at 2.6% (4 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ca/
2025-12-02 07:26:16 +01:00
Elian Doran
98094fac63
Translated using Weblate (Italian)
Currently translated at 99.7% (1635 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-12-02 07:26:15 +01:00
Elian Doran
f94ccee252
Translated using Weblate (German)
Currently translated at 99.8% (1637 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2025-12-02 07:26:14 +01:00
Elian Doran
7c4b338539
Translated using Weblate (Russian)
Currently translated at 97.3% (1595 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-02 07:26:14 +01:00
Elian Doran
d88431f9e3
Translated using Weblate (Ukrainian)
Currently translated at 97.1% (1593 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/
2025-12-02 07:26:13 +01:00
Elian Doran
5f86068489
Translated using Weblate (Portuguese (Brazil))
Currently translated at 96.8% (1587 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt_BR/
2025-12-02 07:26:12 +01:00
Elian Doran
36bc226674
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.7% (1635 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-02 07:26:12 +01:00
Hosted Weblate
7947745218
Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2025-12-02 07:26:11 +01:00
Hosted Weblate
19697aabfb
Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2025-12-02 07:26:10 +01:00
Elian Doran
87af23598d
Translated using Weblate (Romanian)
Currently translated at 99.5% (1631 of 1639 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ro/
2025-12-02 07:26:09 +01:00
Hosted Weblate
4ed7966a5a
Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2025-12-02 07:26:08 +01:00
Giovi
8d86b5c7e6
Translated using Weblate (Italian)
Currently translated at 100.0% (1638 of 1638 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-12-02 07:26:07 +01:00
Elian Doran
cdf9458962
chore(deps): update dependency express to v5.2.0 [security] (#7915) 2025-12-02 06:25:40 +00:00
Lucas
83df37c2d1
Update .github/copilot-instructions.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-01 16:25:09 -08:00
Lucas
bc7b3165c8
Update .github/copilot-instructions.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-01 16:25:02 -08:00
Lucas
c332e9764a
Update .github/copilot-instructions.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-01 16:24:50 -08:00
lzinga
004d48d36c docs: add comprehensive AI coding agent instructions for copilot-instructions 2025-12-01 16:15:43 -08:00
Elian Doran
28b263a445
chore(mobile/split): address requested changes
Some checks failed
Checks / main (push) Has been cancelled
2025-12-01 23:51:12 +02:00
Elian Doran
e0e9310907
feat(mobile/split): rephase open in note split if a split is already open 2025-12-01 23:46:29 +02:00
renovate[bot]
68b9159b2b
chore(deps): update dependency express to v5.2.0 [security] 2025-12-01 21:38:46 +00:00
Elian Doran
f5940cbf70
feat(mobile/split): hide split button if split is already open 2025-12-01 23:36:15 +02:00
Elian Doran
bfb143bb51
Merge remote-tracking branch 'origin/main' into prototype/mobile_split 2025-12-01 23:27:58 +02:00
Elian Doran
9682df6240
feat(mobile/split): collapse inactive split when keyboard is opened
Some checks are pending
Checks / main (push) Waiting to run
2025-12-01 15:48:18 +02:00
Elian Doran
a718908385
fix(mobile/split): link context menu opening in the wrong split 2025-12-01 15:23:57 +02:00
Elian Doran
309fbab2e6
feat(mobile/split): hide sidebar button for secondary item 2025-11-30 23:34:37 +02:00
Elian Doran
99da145d65
fix(mobile/split): swapping between splits 2025-11-30 23:32:29 +02:00
SiriusXT
2163334c4f fix(css): the import-markdown icon was displayed as a black square 2025-11-30 15:27:33 +08:00
SiriusXT
f5d180af6b fix(markdown): show 'import successful' when canceling markdown import 2025-11-30 15:27:08 +08:00
Elian Doran
1d1639e5e1
feat(mobile/split): limit to maximum two splits per tab 2025-11-29 21:27:27 +02:00
Elian Doran
6ab05fdb76
chore(mobile/split): improve obtaining of the note context 2025-11-29 21:17:03 +02:00
Elian Doran
91ae8c0aaf
feat(mobile/split): add a border between splits 2025-11-29 21:14:03 +02:00
Elian Doran
fcb69c0190
fix(mobile/split): errors due to split 2025-11-29 21:12:55 +02:00
Elian Doran
2cf6fe4352
feat(mobile/split): option to close split 2025-11-29 21:11:16 +02:00
Elian Doran
e3a2623a53
feat(mobile/split): layout horizontally on tablet 2025-11-29 21:06:11 +02:00
Elian Doran
e0c0a423c1
feat(mobile/split): layout vertically on mobile 2025-11-29 21:04:22 +02:00
Elian Doran
fd99246c49
feat(mobile/split): add an option to create new split 2025-11-29 21:04:13 +02:00
Elian Doran
d247edd870
feat(mobile/split): add split container to layout 2025-11-29 21:02:53 +02:00
x1arch
5d5fd2079a Fix share access to attachments for notes protected by login:password 2025-11-21 19:52:22 +00:00
66 changed files with 1925 additions and 1344 deletions

334
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,334 @@
# Trilium Notes - AI Coding Agent Instructions
## Project Overview
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. Built as a TypeScript monorepo using pnpm, it implements a three-layer caching architecture (Becca/Froca/Shaca) with a widget-based UI system and supports extensive user scripting capabilities.
## Essential Architecture Patterns
### Three-Layer Cache System (Critical to Understand)
- **Becca** (`apps/server/src/becca/`): Server-side entity cache, primary data source
- **Froca** (`apps/client/src/services/froca.ts`): Client-side mirror synchronized via WebSocket
- **Shaca** (`apps/server/src/share/`): Optimized cache for public/shared notes
**Key insight**: Never bypass these caches with direct DB queries. Always use `becca.notes[noteId]`, `froca.getNote()`, or equivalent cache methods.
### Entity Relationship Model
Notes use a **multi-parent tree** via branches:
- `BNote` - The note content and metadata
- `BBranch` - Tree relationships (one note can have multiple parents via cloning)
- `BAttribute` - Key-value metadata attached to notes (labels and relations)
### Entity Change System & Sync
Every entity modification (notes, branches, attributes) creates an `EntityChange` record that drives synchronization:
```typescript
// Entity changes are automatically tracked
note.title = "New Title";
note.save(); // Creates EntityChange record with changeId
// Sync protocol via WebSocket
ws.sendMessage({ type: 'sync-pull-in-progress', ... });
```
**Critical**: This is why you must use Becca/Froca methods instead of direct DB writes - they create the change tracking records needed for sync.
### Entity Lifecycle & Events
The event system (`apps/server/src/services/events.ts`) broadcasts entity lifecycle events:
```typescript
// Subscribe to events in widgets or services
eventService.subscribe('noteChanged', ({ noteId }) => {
// React to note changes
});
// Common events: noteChanged, branchChanged, attributeChanged, noteDeleted
// Widget method: entitiesReloadedEvent({loadResults}) for handling reloads
```
**Becca loader priorities**: Events are emitted in order (notes → branches → attributes) during initial load to ensure referential integrity.
### TaskContext for Long Operations
Use `TaskContext` for operations with progress reporting (imports, exports, bulk operations):
```typescript
const taskContext = new TaskContext("task-id", "import", "Import Notes");
taskContext.increaseProgressCount();
// WebSocket messages: { type: 'taskProgressCount', taskId, taskType, data, progressCount }
**Pattern**: All long-running operations (delete note trees, export, import) use TaskContext to send WebSocket updates to the frontend.
### Protected Session Handling
Protected notes require an active encryption session:
```typescript
// Always check before accessing protected content
if (note.isContentAvailable()) {
const content = note.getContent(); // Safe
} else {
const title = note.getTitleOrProtected(); // Returns "[protected]"
}
// Protected session management
protectedSessionService.isProtectedSessionAvailable() // Check session
protectedSessionService.startProtectedSession() // After password entry
```
**Session timeout**: Protected sessions expire after inactivity. The encryption key is kept in memory only.
### Attribute Inheritance Patterns
Attributes can be inherited through three mechanisms:
```typescript
// 1. Standard inheritance (#hidePromotedAttributes ~hidePromotedAttributes)
note.getInheritableAttributes() // Walks up parent tree
// 2. Child prefix inheritance (child:label copies to children)
parentNote.setLabel("child:icon", "book") // All children inherit this
// 3. Template relation inheritance (#template=templateNoteId)
note.setRelation("template", templateNoteId)
note.getInheritedAttributes() // Includes template's inheritable attributes
```
**Cycle prevention**: Inheritance tracking prevents infinite loops when notes reference each other.
### Widget-Based UI Architecture
All UI components extend from widget base classes (`apps/client/src/widgets/`):
```typescript
// Right panel widget (sidebar)
class MyWidget extends RightPanelWidget {
get position() { return 100; } // Order in panel
get parentWidget() { return 'right-pane'; }
isEnabled() { return this.note && this.note.hasLabel('myLabel'); }
async refreshWithNote(note) { /* Update UI */ }
}
// Note-aware widget (responds to note changes)
class MyNoteWidget extends NoteContextAwareWidget {
async refreshWithNote(note) { /* Refresh when note changes */ }
async entitiesReloadedEvent({loadResults}) { /* Handle entity updates */ }
}
```
**Important**: Widgets use jQuery (`this.$widget`) for DOM manipulation. Don't mix React patterns here.
## Development Workflow
### Running & Testing
```bash
# From root directory
pnpm install # Install dependencies
corepack enable # Enable pnpm if not available
pnpm server:start # Dev server (http://localhost:8080)
pnpm server:start-prod # Production mode server
pnpm desktop:start # Desktop app development
pnpm server:test spec/etapi/search.spec.ts # Run specific test
pnpm test:parallel # Client tests (can run parallel)
pnpm test:sequential # Server tests (sequential due to shared DB)
pnpm test:all # All tests (parallel + sequential)
pnpm coverage # Generate coverage reports
pnpm typecheck # Type check all projects
```
### Building
```bash
pnpm client:build # Build client application
pnpm server:build # Build server application
pnpm desktop:build # Build desktop application
```
### Test Organization
- **Server tests** (`apps/server/spec/`): Must run sequentially (shared database state)
- **Client tests** (`apps/client/src/`): Can run in parallel
- **E2E tests** (`apps/server-e2e/`): Use Playwright for integration testing
- **ETAPI tests** (`apps/server/spec/etapi/`): External API contract tests
**Pattern**: When adding new API endpoints, add tests in `spec/etapi/` following existing patterns (see `search.spec.ts`).
### Monorepo Navigation
```
apps/
client/ # Frontend (shared by server & desktop)
server/ # Node.js backend with REST API
desktop/ # Electron wrapper
web-clipper/ # Browser extension for saving web content
db-compare/ # Database comparison tool
dump-db/ # Database export utility
edit-docs/ # Documentation editing tools
packages/
commons/ # Shared types and utilities
ckeditor5/ # Custom rich text editor with Trilium-specific plugins
codemirror/ # Code editor integration
highlightjs/ # Syntax highlighting
share-theme/ # Theme for shared/published notes
ckeditor5-admonition/ # Admonition blocks plugin
ckeditor5-footnotes/ # Footnotes plugin
ckeditor5-math/ # Math equations plugin
ckeditor5-mermaid/ # Mermaid diagrams plugin
```
**Filter commands**: Use `pnpm --filter server test` to run commands in specific packages.
## Critical Code Patterns
### ETAPI Backwards Compatibility
When adding query parameters to ETAPI endpoints (`apps/server/src/etapi/`), maintain backwards compatibility by checking if new params exist before changing response format.
**Pattern**: ETAPI consumers expect specific response shapes. Always check for breaking changes.
### Frontend-Backend Communication
- **REST API**: `apps/server/src/routes/api/` - Internal endpoints (no auth required when `noAuthentication=true`)
- **ETAPI**: `apps/server/src/etapi/` - External API with authentication
- **WebSocket**: Real-time sync via `apps/server/src/services/ws.ts`
**Auth note**: ETAPI uses basic auth with tokens. Internal API endpoints trust the frontend.
### Database Migrations
- Add scripts in `apps/server/src/migrations/YYMMDD_HHMM__description.sql`
- Update schema in `apps/server/src/assets/db/schema.sql`
- Never bypass Becca cache after migrations
## Common Pitfalls
1. **Never bypass the cache layers** - Always use `becca.notes[noteId]`, `froca.getNote()`, or equivalent cache methods. Direct database queries will cause sync issues between Becca/Froca/Shaca and won't create EntityChange records needed for synchronization.
2. **Protected notes require session check** - Before accessing `note.title` or `note.getContent()` on protected notes, check `note.isContentAvailable()` or use `note.getTitleOrProtected()` which handles this automatically.
3. **Widget lifecycle matters** - Override `refreshWithNote()` for note changes, `doRenderBody()` for initial render, `entitiesReloadedEvent()` for entity updates. Widgets use jQuery (`this.$widget`) - don't mix React patterns.
4. **Tests run differently** - Server tests must run sequentially (shared database state), client tests can run in parallel. Use `pnpm test:sequential` for backend, `pnpm test:parallel` for frontend.
5. **ETAPI requires authentication** - ETAPI endpoints use basic auth with tokens. Internal API endpoints (`apps/server/src/routes/api/`) trust the frontend when `noAuthentication=true`.
6. **Search expressions are evaluated in memory** - The search service loads all matching notes, scores them in JavaScript, then sorts. You cannot add SQL-level LIMIT/OFFSET without losing scoring functionality.
7. **Documentation edits have rules** - `docs/Script API/` is auto-generated (never edit directly). `docs/User Guide/` should be edited via `pnpm edit-docs:edit-docs`, not manually. Only `docs/Developer Guide/` and `docs/Release Notes/` are safe for direct Markdown editing.
8. **pnpm workspace filtering** - Use `pnpm --filter server <command>` or shorthand `pnpm server:test` defined in root `package.json`. Note the `--filter` syntax, not `-F` or other shortcuts.
9. **Event subscription cleanup** - When subscribing to events in widgets, unsubscribe in `cleanup()` or `doDestroy()` to prevent memory leaks.
10. **Attribute inheritance can be complex** - When checking for labels/relations, use `note.getOwnedAttribute()` for direct attributes or `note.getAttribute()` for inherited ones. Don't assume attributes are directly on the note.
## TypeScript Configuration
- **Project references**: Monorepo uses TypeScript project references (`tsconfig.json`)
- **Path mapping**: Use relative imports, not path aliases
- **Build order**: `pnpm typecheck` builds all projects in dependency order
- **Build system**: Uses Vite for fast development, ESBuild for production optimization
- **Patches**: Custom patches in `patches/` directory for CKEditor and other dependencies
## Key Files for Context
- `apps/server/src/becca/entities/bnote.ts` - Note entity methods
- `apps/client/src/services/froca.ts` - Frontend cache API
- `apps/server/src/services/search/services/search.ts` - Search implementation
- `apps/server/src/routes/routes.ts` - API route registration
- `apps/client/src/widgets/basic_widget.ts` - Widget base class
- `apps/server/src/main.ts` - Server startup entry point
- `apps/client/src/desktop.ts` - Client initialization
- `apps/server/src/services/backend_script_api.ts` - Scripting API
- `apps/server/src/assets/db/schema.sql` - Database schema
## Note Types and Features
Trilium supports multiple note types with specialized widgets in `apps/client/src/widgets/type_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
### Collections
Notes can be marked with the `#collection` label to enable collection view modes. Collections support multiple view types:
- **List**: Standard list view
- **Grid**: Card/grid layout
- **Calendar**: Calendar-based view
- **Table**: Tabular data view
- **GeoMap**: Geographic map view
- **Board**: Kanban-style board
- **Presentation**: Slideshow presentation mode
View types are configured via `#viewType` label (e.g., `#viewType=table`). Each view mode stores its configuration in a separate attachment (e.g., `table.json`). Collections are organized separately from regular note type templates in the note creation menu.
## 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
- Remember: scoring happens in-memory, not at database level
### Custom CKEditor Plugins
- Create new package in `packages/` following existing plugin structure
- Register in `packages/ckeditor5/src/plugins.ts`
- See `ckeditor5-admonition`, `ckeditor5-footnotes`, `ckeditor5-math`, `ckeditor5-mermaid` for examples
### Database Migrations
- Add migration scripts in `apps/server/src/migrations/YYMMDD_HHMM__description.sql`
- Update schema in `apps/server/src/assets/db/schema.sql`
- Never bypass Becca cache after migrations
## Security & Features
### Security Considerations
- Per-note encryption with granular protected sessions
- CSRF protection for API endpoints
- OpenID and TOTP authentication support
- Sanitization of user-generated content
### Scripting System
Trilium provides powerful user scripting capabilities:
- **Frontend scripts**: Run in browser context with UI access
- **Backend scripts**: Run in Node.js context with full API access
- Script API documentation in `docs/Script API/`
- Backend API available via `api` object in script context
### Internationalization
- Translation files in `apps/client/src/translations/`
- Use translation system via `t()` function
- Automatic pluralization: Add `_other` suffix to translation keys (e.g., `item` and `item_other` for singular/plural)
## Testing Conventions
```typescript
// ETAPI test pattern
describe("etapi/feature", () => {
beforeAll(async () => {
config.General.noAuthentication = false;
app = await buildApp();
token = await login(app);
});
it("should test feature", async () => {
const response = await supertest(app)
.get("/etapi/notes?search=test")
.auth(USER, token, { type: "basic" })
.expect(200);
expect(response.body.results).toBeDefined();
});
});
```
## Questions to Verify Understanding
Before implementing significant changes, confirm:
- Is this touching the cache layer? (Becca/Froca/Shaca must stay in sync via EntityChange records)
- Does this change API response shape? (Check backwards compatibility for ETAPI)
- Are you adding search features? (Understand expression-based architecture and in-memory scoring first)
- Is this a new widget? (Know which base class and lifecycle methods to use)
- Does this involve protected notes? (Check `isContentAvailable()` before accessing content)
- Is this a long-running operation? (Use TaskContext for progress reporting)
- Are you working with attributes? (Understand inheritance patterns: direct, child-prefix, template)

2
.gitignore vendored
View File

@ -8,6 +8,7 @@ out-tsc
# dependencies
node_modules
.pnpm-store
# IDEs and editors
/.idea
@ -18,6 +19,7 @@ node_modules
*.launch
.settings/
*.sublime-workspace
.devcontainer
# misc
/.sass-cache

View File

@ -146,6 +146,21 @@ Here's the language coverage we have so far:
### Code
General (OS / docker / podman, etc.) dependencies:
Debian
```
apt update
apt install -y build-essential python3 make g++ libsqlite3-dev
corepack enable
```
Alpine
```
apk add --no-cache build-base python3 python3-dev sqlite-dev
corepack enable
```
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
@ -154,6 +169,10 @@ pnpm install
pnpm run server:start
```
> If you faced with some problems, try to delete all `node_modules` and `.pnpm-store` folders, not only from the root, from every directory, like `apps/{app_name}/node_modules`and `/packages/{package_name}/node_modules` and then reinstall it by the `pnpm install`.
Share styles not compiling by default, if you see share page without styles, make `pnpm run server:build` and then run development server.
### Documentation
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:

View File

@ -29,6 +29,7 @@ import type AppContext from "../components/app_context.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
const MOBILE_CSS = `
<style>
@ -142,6 +143,7 @@ export default class MobileLayout {
.id("detail-container")
.class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-7 col-md-8 col-lg-9")
.child(
new SplitNoteContainer(() =>
new NoteWrapperWidget()
.child(
new FlexContainer("row")
@ -172,6 +174,7 @@ export default class MobileLayout {
)
)
)
)
.child(
new FlexContainer("column")
.contentSized()

View File

@ -2,26 +2,32 @@ import { t } from "../services/i18n.js";
import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js";
import appContext, { type CommandNames } from "../components/app_context.js";
import type { ViewScope } from "../services/link.js";
import utils, { isMobile } from "../services/utils.js";
import { getClosestNtxId } from "../widgets/widget_utils.js";
import type { LeafletMouseEvent } from "leaflet";
function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) {
contextMenu.show({
x: e.pageX,
y: e.pageY,
items: getItems(),
selectMenuItemHandler: ({ command }) => handleLinkContextMenuItem(command, notePath, viewScope, hoistedNoteId)
items: getItems(e),
selectMenuItemHandler: ({ command }) => handleLinkContextMenuItem(command, e, notePath, viewScope, hoistedNoteId)
});
}
function getItems(): MenuItem<CommandNames>[] {
function getItems(e: ContextMenuEvent | LeafletMouseEvent): MenuItem<CommandNames>[] {
const ntxId = getNtxId(e);
const isMobileSplitOpen = isMobile() && appContext.tabManager.getNoteContextById(ntxId).getMainContext().getSubContexts().length > 1;
return [
{ title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" },
{ title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" },
{ title: !isMobileSplitOpen ? t("link_context_menu.open_note_in_new_split") : t("link_context_menu.open_note_in_other_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" },
{ title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" },
{ title: t("link_context_menu.open_note_in_popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit" }
];
}
function handleLinkContextMenuItem(command: string | undefined, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEvent | LeafletMouseEvent, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
if (!hoistedNoteId) {
hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId ?? null;
}
@ -29,15 +35,8 @@ function handleLinkContextMenuItem(command: string | undefined, notePath: string
if (command === "openNoteInNewTab") {
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
} else if (command === "openNoteInNewSplit") {
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
if (!subContexts) {
logError("subContexts is null");
return;
}
const { ntxId } = subContexts[subContexts.length - 1];
const ntxId = getNtxId(e);
if (!ntxId) return;
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
} else if (command === "openNoteInNewWindow") {
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
@ -46,6 +45,18 @@ function handleLinkContextMenuItem(command: string | undefined, notePath: string
}
}
function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
if (utils.isDesktop()) {
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
if (!subContexts) return null;
return subContexts[subContexts.length - 1].ntxId;
} else if (e.target instanceof HTMLElement) {
return getClosestNtxId(e.target);
} else {
return null;
}
}
export default {
getItems,
handleLinkContextMenuItem,

View File

@ -1016,11 +1016,9 @@ div[data-notify="container"] {
}
svg.ck-icon {
&.ck-icon_inherit-color {
* {
&.ck-icon_inherit-color path[fill="#333"] {
fill: currentColor;
}
}
&.note-icon {
color: var(--main-text-color);
@ -2596,3 +2594,24 @@ iframe.print-iframe {
/* Workaround: set font weight only if the theme-next is not active */
font-weight: var(--root-background, 800);
}
@media (max-width: 991px) {
body.mobile {
.split-note-container-widget {
flex-direction: column !important;
.note-split {
width: 100%;
}
.note-split.visible + .note-split.visible {
border-top: 1px solid var(--main-border-color);
}
}
#root-widget.virtual-keyboard-opened .note-split:not(:focus-within) {
max-height: 80px;
opacity: 0.4;
}
}
}

View File

@ -201,8 +201,12 @@
},
"zpetne_odkazy": {
"relation": "العلاقة",
"backlink": "{{count}} رابط راجع",
"backlinks": "{{count}} روابط راجعة"
"backlink_zero": "",
"backlink_one": "{{count}} رابط راجع",
"backlink_two": "",
"backlink_few": "",
"backlink_many": "{{count}} روابط راجعة",
"backlink_other": ""
},
"note_icon": {
"category": "الفئة:",

View File

@ -1,7 +1,9 @@
{
"about": {
"title": "Sobre Trilium Notes",
"homepage": "Pàgina principal:"
"homepage": "Pàgina principal:",
"app_version": "Versió de l'aplicació:",
"db_version": "Versió de la base de dades:"
},
"add_link": {
"note": "Nota"

View File

@ -736,9 +736,8 @@
"zoom_out_title": "缩小"
},
"zpetne_odkazy": {
"backlink": "{{count}} 个反链",
"backlinks": "{{count}} 个反链",
"relation": "关系"
"relation": "关系",
"backlink_other": "{{count}} 个反链"
},
"mobile_detail_menu": {
"insert_child_note": "插入子笔记",
@ -1898,9 +1897,7 @@
"indexing_stopped": "索引已停止",
"indexing_in_progress": "索引进行中...",
"last_indexed": "最后索引时间",
"n_notes_queued_0": "{{ count }} 条笔记已加入索引队列",
"note_chat": "笔记聊天",
"notes_indexed_0": "{{ count }} 条笔记已索引",
"sources": "来源",
"start_indexing": "开始索引",
"use_advanced_context": "使用高级上下文",

View File

@ -24,14 +24,6 @@
"message": "Uživatelský skript z poznámky s ID \"{{id}}\" a názvem \"{{title}}\" nemohl být spuštěn z důvodu: \n\n{{message}}"
}
},
"ai_llm": {
"n_notes_queued_0": "{{ count }} poznámka ve frontě k indexaci",
"n_notes_queued_1": "{{ count }} poznámky ve frontě k indexaci",
"n_notes_queued_2": "{{ count }} poznámek ve frontě k indexaci",
"notes_indexed_0": "{{ count }} poznámka indexována",
"notes_indexed_1": "{{ count }} poznámky indexovány",
"notes_indexed_2": "{{ count }} poznámek indexováno"
},
"add_link": {
"add_link": "Přidat odkaz",
"help_on_links": "Nápověda k odkazům",

View File

@ -732,9 +732,9 @@
"zoom_out_title": "Herauszoomen"
},
"zpetne_odkazy": {
"backlink": "{{count}} Rückverlinkung",
"backlinks": "{{count}} Rückverlinkungen",
"relation": "Beziehung"
"relation": "Beziehung",
"backlink_one": "{{count}} Rückverlinkung",
"backlink_other": "{{count}} Rückverlinkungen"
},
"mobile_detail_menu": {
"insert_child_note": "Untergeordnete Notiz einfügen",
@ -1729,10 +1729,6 @@
"help_title": "Zeige mehr Informationen zu diesem Fenster"
},
"ai_llm": {
"n_notes_queued": "{{ count }} Notiz zur Indizierung vorgemerkt",
"n_notes_queued_plural": "{{ count }} Notizen zur Indizierung vorgemerkt",
"notes_indexed": "{{ count }} Notiz indiziert",
"notes_indexed_plural": "{{ count }} Notizen indiziert",
"not_started": "Nicht gestartet",
"title": "KI Einstellungen",
"processed_notes": "Verarbeitete Notizen",

View File

@ -14,11 +14,5 @@
"title": "Κρίσιμο σφάλμα",
"message": "Συνέβη κάποιο κρίσιμο σφάλμα, το οποίο δεν επιτρέπει στην εφαρμογή χρήστη να ξεκινήσει:\n\n{{message}}\n\nΤο πιθανότερο είναι να προκλήθηκε από κάποιο script που απέτυχε απρόοπτα. Δοκιμάστε να ξεκινήσετε την εφαρμογή σε ασφαλή λειτουργία για να λύσετε το πρόβλημα."
}
},
"ai_llm": {
"n_notes_queued": "{{ count }} σημείωση στην ουρά για εύρεση",
"n_notes_queued_plural": "{{ count }} σημειώσεις στην ουρά για εύρεση",
"notes_indexed": "{{ count }} σημείωση με ευρετήριο",
"notes_indexed_plural": "{{ count }} σημειώσεις με ευρετήριο"
}
}

View File

@ -1271,11 +1271,7 @@
"indexing_stopped": "Indexing stopped",
"indexing_in_progress": "Indexing in progress...",
"last_indexed": "Last Indexed",
"n_notes_queued": "{{ count }} note queued for indexing",
"n_notes_queued_plural": "{{ count }} notes queued for indexing",
"note_chat": "Note Chat",
"notes_indexed": "{{ count }} note indexed",
"notes_indexed_plural": "{{ count }} notes indexed",
"sources": "Sources",
"start_indexing": "Start Indexing",
"use_advanced_context": "Use Advanced Context",
@ -1883,6 +1879,7 @@
"link_context_menu": {
"open_note_in_new_tab": "Open note in a new tab",
"open_note_in_new_split": "Open note in a new split",
"open_note_in_other_split": "Open note in the other split",
"open_note_in_new_window": "Open note in a new window",
"open_note_in_popup": "Quick edit"
},

View File

@ -735,9 +735,10 @@
"zoom_out_title": "Alejar"
},
"zpetne_odkazy": {
"backlink": "{{count}} Vínculo de retroceso",
"backlinks": "{{count}} vínculos de retroceso",
"relation": "relación"
"relation": "relación",
"backlink_one": "{{count}} Vínculo de retroceso",
"backlink_many": "",
"backlink_other": "{{count}} vínculos de retroceso"
},
"mobile_detail_menu": {
"insert_child_note": "Insertar subnota",
@ -1262,12 +1263,7 @@
"indexing_stopped": "Indexado detenido",
"indexing_in_progress": "Indexado en progreso...",
"last_indexed": "Último indexado",
"n_notes_queued_0": "{{ count }} nota agregada a la cola para indexar",
"n_notes_queued_1": "{{ count }} notas agregadas a la cola para indexar",
"n_notes_queued_2": "",
"note_chat": "Chat de nota",
"notes_indexed": "{{ count }} nota indexada",
"notes_indexed_plural": "{{ count }} notas indexadas",
"sources": "Fuentes",
"start_indexing": "Comenzar indexado",
"use_advanced_context": "Usar contexto avanzado",

View File

@ -733,9 +733,10 @@
"zoom_out_title": "Zoom arrière"
},
"zpetne_odkazy": {
"backlink": "{{count}} Lien inverse",
"backlinks": "{{count}} Liens inverses",
"relation": "relation"
"relation": "relation",
"backlink_one": "{{count}} Lien inverse",
"backlink_many": "",
"backlink_other": "{{count}} Liens inverses"
},
"mobile_detail_menu": {
"insert_child_note": "Insérer une note enfant",
@ -1766,12 +1767,6 @@
"not_started": "Non démarré",
"title": "Paramètres IA",
"processed_notes": "Notes traitées",
"n_notes_queued_0": "{{ count }} note en attente dindexation",
"n_notes_queued_1": "{{ count }} notes en attente dindexation",
"n_notes_queued_2": "",
"notes_indexed_0": "{{ count }} note indexée",
"notes_indexed_1": "{{ count }} notes indexées",
"notes_indexed_2": "",
"anthropic_url_description": "URL de base pour l'API Anthropic (par défaut : https ://api.anthropic.com)",
"anthropic_model_description": "Modèles Anthropic Claude pour la complétion",
"voyage_settings": "Réglages d'IA Voyage",

View File

@ -324,7 +324,8 @@
"copy-link": "Copia collegamento",
"paste-as-plain-text": "Incolla come testo semplice",
"add-term-to-dictionary": "Aggiungi \"{{term}}\" al dizionario",
"search_online": "Cerca \"{{term}}\" con {{searchEngine}}"
"search_online": "Cerca \"{{term}}\" con {{searchEngine}}",
"search_in_trilium": "Cerca \"{{term}}\" in Trilium"
},
"editing": {
"editor_type": {
@ -614,7 +615,8 @@
"showSQLConsole": "mostra console SQL",
"other": "Altro",
"quickSearch": "concentrati sull'input della ricerca rapida",
"inPageSearch": "ricerca all'interno della pagina"
"inPageSearch": "ricerca all'interno della pagina",
"editShortcuts": "Modifica scorciatoie da tastiera"
},
"i18n": {
"saturday": "Sabato",
@ -638,12 +640,6 @@
"friday": "Venerdì"
},
"ai_llm": {
"n_notes_queued_0": "{{ count }} nota in coda per l'indicizzazione",
"n_notes_queued_1": "{{ count }} note in coda per l'indicizzazione",
"n_notes_queued_2": "{{ count }} note in coda per l'indicizzazione",
"notes_indexed_0": "{{ count }} nota indicizzata",
"notes_indexed_1": "{{ count }} note indicizzate",
"notes_indexed_2": "{{ count }} note indicizzate",
"not_started": "Non iniziato",
"title": "Impostazioni AI",
"processed_notes": "Note elaborate",
@ -1308,9 +1304,10 @@
"zoom_out_title": "Rimpicciolisci"
},
"zpetne_odkazy": {
"backlink": "{{count}} Backlink",
"backlinks": "{{count}} Backlinks",
"relation": "relazione"
"relation": "relazione",
"backlink_one": "{{count}} Backlink",
"backlink_many": "",
"backlink_other": "{{count}} Backlink"
},
"mobile_detail_menu": {
"insert_child_note": "Inserisci nota secondaria",
@ -1346,7 +1343,11 @@
"geo-map": "Mappa geografica",
"board": "Asse",
"presentation": "Presentazione",
"include_archived_notes": "Mostra note archiviate"
"include_archived_notes": "Mostra note archiviate",
"expand_tooltip": "Espande i figli diretti di questa raccolta (a un livello di profondità). Per ulteriori opzioni, premere la freccia a destra.",
"expand_first_level": "Espandi figli diretti",
"expand_nth_level": "Espandi {{depth}} livelli",
"expand_all_levels": "Espandi tutti i livelli"
},
"edited_notes": {
"no_edited_notes_found": "Nessuna nota modificata per questo giorno...",
@ -1852,7 +1853,8 @@
"refresh-saved-search-results": "Aggiorna i risultati della ricerca salvati",
"create-child-note": "Crea nota figlio",
"unhoist": "Sganciare",
"toggle-sidebar": "Attiva/disattiva la barra laterale"
"toggle-sidebar": "Attiva/disattiva la barra laterale",
"dropping-not-allowed": "Non è consentito lasciare appunti in questa posizione."
},
"title_bar_buttons": {
"window-on-top": "Mantieni la finestra in primo piano"
@ -1936,7 +1938,8 @@
"duplicate-launcher": "Duplica il launcher <kbd data-command=\"duplicateSubtree\">"
},
"editable-text": {
"auto-detect-language": "Rilevato automaticamente"
"auto-detect-language": "Rilevato automaticamente",
"keeps-crashing": "Il componente di modifica continua a bloccarsi. Prova a riavviare Trilium. Se il problema persiste, valuta la possibilità di creare una segnalazione di bug."
},
"highlighting": {
"title": "Blocchi di codice",
@ -2101,5 +2104,8 @@
"set-color": "Imposta colore nota",
"set-custom-color": "Imposta colore personalizzato per le note",
"clear-color": "Pulisci colore della nota"
},
"popup-editor": {
"maximize": "Passa all'editor completo"
}
}

View File

@ -1507,9 +1507,7 @@
"indexing_stopped": "インデックス登録を停止しました",
"indexing_in_progress": "インデックス登録中です...",
"last_indexed": "最終インデックス作成日時",
"n_notes_queued_0": "{{ count }} 件のノートがインデックス作成待ちです",
"note_chat": "ノートチャット",
"notes_indexed_0": "{{ count }} 件のノートをインデックスしました",
"sources": "ソース",
"start_indexing": "インデックス作成を開始",
"use_advanced_context": "高度なコンテキストを使用",
@ -1590,9 +1588,8 @@
"this_launcher_doesnt_define_target_note": "このランチャーはターゲットノートを定義していません。"
},
"zpetne_odkazy": {
"backlink": "{{count}} バックリンク",
"backlinks": "{{count}} バックリンク",
"relation": "リレーション"
"relation": "リレーション",
"backlink_other": "{{count}} 個のバックリンク"
},
"mobile_detail_menu": {
"delete_this_note": "このノートを削除",

View File

@ -16,10 +16,12 @@
},
"widget-error": {
"title": "Starten widget mislukt",
"message-unknown": "Onbekende widget kan niet gestart worden omdat:\n\n{{message}}"
"message-unknown": "Onbekende widget kan niet gestart worden omdat:\n\n{{message}}",
"message-custom": "Aangepaste widget van notitie met ID \"{{id}}\", getiteld \"{{title}}\" kon niet worden geïnitialiseerd vanwege:\n\n{{message}}"
},
"bundle-error": {
"title": "Custom script laden mislukt"
"title": "Custom script laden mislukt",
"message": "Script van notitie met ID \"{{id}}\", getiteld \"{{title}}\" kon niet worden uitgevoerd vanwege:\n\n{{message}}"
}
},
"add_link": {
@ -29,14 +31,17 @@
"search_note": "zoek voor notitie op naam",
"link_title_mirrors": "De link titel is hetzelfde als de notitie's huidige titel",
"link_title": "Link titel",
"button_add_link": "Link toevoegen"
"button_add_link": "Link toevoegen",
"link_title_arbitrary": "snelkoppelingsnaam kan willekeurig worden aangepast"
},
"branch_prefix": {
"edit_branch_prefix": "Bewerk branch prefix",
"save": "Opslaan",
"branch_prefix_saved": "Branch prefix is opgeslagen.",
"help_on_tree_prefix": "Help bij boomvoorvoegsel",
"prefix": "Voorvoegsel: "
"prefix": "Voorvoegsel: ",
"edit_branch_prefix_multiple": "Bewerk zijtakvoorvoegsel voor {{count}} zijtakken",
"branch_prefix_saved_multiple": "Vertakkingsvoorvoegsel opgeslagen voor {{count}} vertakkingen."
},
"bulk_actions": {
"bulk_actions": "Bulk acties",

View File

@ -750,9 +750,6 @@
"indexing_stopped": "Indeksowanie zatrzymane",
"indexing_in_progress": "Indeksowanie w trakcie...",
"last_indexed": "Ostatnio zindeksowane",
"n_notes_queued_0": "{{ count }} notatka zakolejkowana do indeksowania",
"n_notes_queued_1": "{{ count }} notatek zakolejkowanych do indeksowania",
"n_notes_queued_2": "{{ count }} notatek zakolejkowanych do indeksowania",
"note_chat": "Czat notatki",
"note_title": "Tytuł notatki",
"error": "Błąd",
@ -860,9 +857,6 @@
"enter_message": "Wpisz swoją wiadomość...",
"error_contacting_provider": "Błąd kontaktu z dostawcą AI. Sprawdź ustawienia i połączenie internetowe.",
"error_generating_response": "Błąd generowania odpowiedzi AI",
"notes_indexed_0": "{{ count }} notatka zaindeksowana",
"notes_indexed_1": "{{ count }} notatek zaindeksowanych",
"notes_indexed_2": "",
"sources": "Źródła",
"start_indexing": "Rozpocznij indeksowanie",
"use_advanced_context": "Użyj zaawansowanego kontekstu",
@ -1237,9 +1231,10 @@
"zoom_out_title": "Pomniejsz"
},
"zpetne_odkazy": {
"backlink": "{{count}} Backlink",
"backlinks": "{{count}} Backlinków",
"relation": "relacja"
"relation": "relacja",
"backlink_one": "{{count}} Backlink",
"backlink_few": "",
"backlink_many": "{{count}} Backlinków"
},
"mobile_detail_menu": {
"insert_child_note": "Wstaw notatkę podrzędną",

View File

@ -711,9 +711,10 @@
"zoom_out_title": "Reduzir"
},
"zpetne_odkazy": {
"backlink": "{{count}} Ligação Reversa",
"backlinks": "{{count}} Ligações Reversas",
"relation": "relação"
"relation": "relação",
"backlink_one": "{{count}} Ligação Reversa",
"backlink_many": "",
"backlink_other": "{{count}} Ligações Reversas"
},
"mobile_detail_menu": {
"insert_child_note": "Inserir nota filha",
@ -1234,13 +1235,7 @@
"indexing_stopped": "Indexação interrompida",
"indexing_in_progress": "Indexação em andamento…",
"last_indexed": "Última Indexada",
"n_notes_queued_0": "{{ count }} nota enfileirada para indexação",
"n_notes_queued_1": "{{ count }} notas enfileiradas para indexação",
"n_notes_queued_2": "{{ count }} notas enfileiradas para indexação",
"note_chat": "Conversa de Nota",
"notes_indexed_0": "{{ count }} nota indexada",
"notes_indexed_1": "{{ count }} notas indexadas",
"notes_indexed_2": "{{ count }} notas indexadas",
"sources": "Origens",
"start_indexing": "Iniciar Indexação",
"use_advanced_context": "Usar Contexto Avançado",

View File

@ -75,12 +75,6 @@
"note_cloned": "A nota \"{{clonedTitle}}\" foi clonada para \"{{targetTitle}}\""
},
"ai_llm": {
"n_notes_queued_0": "{{ count }} nota enfileirada para indexação",
"n_notes_queued_1": "{{ count }} notas enfileiradas para indexação",
"n_notes_queued_2": "{{ count }} notas enfileiradas para indexação",
"notes_indexed_0": "{{ count }} nota indexada",
"notes_indexed_1": "{{ count }} notas indexadas",
"notes_indexed_2": "{{ count }} notas indexadas",
"temperature": "Temperatura",
"retry_queued": "Nota enfileirada para nova tentativa",
"queued_notes": "Notas Enfileiradas",
@ -976,9 +970,10 @@
"reset_pan_zoom_title": "Redefinir pan & zoom para coordenadas e ampliação iniciais"
},
"zpetne_odkazy": {
"backlink": "{{count}} Links Reversos",
"backlinks": "{{count}} Links Reversos",
"relation": "relação"
"relation": "relação",
"backlink_one": "",
"backlink_many": "",
"backlink_other": "{{count}} Links Reversos"
},
"mobile_detail_menu": {
"insert_child_note": "Inserir nota filha",

View File

@ -1888,13 +1888,7 @@
"indexing_stopped": "Indexarea s-a oprit",
"indexing_in_progress": "Indexare în curs...",
"last_indexed": "Ultima indexare",
"n_notes_queued_0": "O notiță adăugată în coada de indexare",
"n_notes_queued_1": "{{ count }} notițe adăugate în coada de indexare",
"n_notes_queued_2": "{{ count }} de notițe adăugate în coada de indexare",
"note_chat": "Discuție pe baza notițelor",
"notes_indexed_0": "O notiță indexată",
"notes_indexed_1": "{{ count }} notițe indexate",
"notes_indexed_2": "{{ count }} de notițe indexate",
"sources": "Surse",
"start_indexing": "Indexează",
"use_advanced_context": "Folosește context îmbogățit",

View File

@ -984,9 +984,10 @@
"new-version-available": "Доступно обновление"
},
"zpetne_odkazy": {
"backlink": "{{count}} ссылки",
"backlinks": "{{count}} ссылок",
"relation": "отношение"
"relation": "отношение",
"backlink_one": "{{count}} ссылки",
"backlink_few": "",
"backlink_many": "{{count}} ссылок"
},
"note_icon": {
"category": "Категория:",
@ -1334,16 +1335,10 @@
"error_fetching": "Ошибка получения списка моделей: {{error}}",
"index_rebuild_status_error": "Ошибка проверки статуса перестроения индекса",
"enhanced_context_description": "Предоставляет ИИ больше контекста из заметки и связанных с ней заметок для более точных ответов",
"n_notes_queued_0": "{{ count }} заметка в очереди на индексирование",
"n_notes_queued_1": "{{ count }} заметки в очереди на индексирование",
"n_notes_queued_2": "{{ count }} заметок в очереди на индексирование",
"no_models_found_ollama": "Модели Ollama не найдены. Проверьте, запущена ли Ollama.",
"no_models_found_online": "Модели не найдены. Проверьте ваш ключ API и настройки.",
"experimental_warning": "Функция LLM в настоящее время является экспериментальной — вы предупреждены.",
"ollama_no_url": "Ollama не настроена. Введите корректный URL-адрес.",
"notes_indexed_0": "{{ count }} заметка проиндексирована",
"notes_indexed_1": "{{ count }} заметки проиндексировано",
"notes_indexed_2": "{{ count }} заметок проиндексировано",
"show_thinking_description": "Показать цепочку мыслительного процесса ИИ",
"api_key_tooltip": "API-ключ для доступа к сервису",
"all_notes_queued_for_retry": "Все неудачные заметки поставлены в очередь на повторную попытку",

View File

@ -425,14 +425,6 @@
"print_page_size": "Prilikom izvoza u PDF, menja veličinu stranice. Podržane vrednosti: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
"color_type": "Boja"
},
"ai_llm": {
"n_notes_queued_0": "{{ count }} beleška stavljena u red za indeksiranje",
"n_notes_queued_1": "{{ count }} beleški stavljeno u red za indeksiranje",
"n_notes_queued_2": "{{ count }} beleški stavljeno u red za indeksiranje",
"notes_indexed_0": "{{ count }} beleška je indeksirana",
"notes_indexed_1": "{{ count }} beleški je indeksirano",
"notes_indexed_2": "{{ count }} beleški je indeksirano"
},
"attribute_editor": {
"help_text_body1": "Da biste dodali oznaku, samo unesite npr. <code>#rock</code> ili ako želite da dodate i vrednost, onda npr. <code>#year = 2020</code>",
"help_text_body2": "Za relaciju, unesite <code>~author = @</code> što bi trebalo da otvori automatsko dovršavanje gde možete potražiti željenu belešku.",

View File

@ -96,11 +96,5 @@
"are_you_sure_remove_note": "\"{{title}}\" notunu ilişki haritasından kaldırmak istediğinize emin misiniz?. ",
"also_delete_note": "Notu da sil",
"if_you_dont_check": "Bunu işaretlemezseniz, not yalnızca ilişki haritasından kaldırılacaktır."
},
"ai_llm": {
"n_notes_queued": "{{ count }} not dizinleme için sıraya alındı",
"n_notes_queued_plural": "{{ count }} adet not dizinleme için sıraya alındı",
"notes_indexed": "{{ count }} not dizinlendi",
"notes_indexed_plural": "{{ count }} adet not dizinlendi"
}
}

View File

@ -733,9 +733,8 @@
"zoom_out_title": "縮小"
},
"zpetne_odkazy": {
"backlink": "{{count}} 個反連結",
"backlinks": "{{count}} 個反連結",
"relation": "關聯"
"relation": "關聯",
"backlink_one": "{{count}} 個反連結"
},
"mobile_detail_menu": {
"insert_child_note": "插入子筆記",
@ -1798,9 +1797,7 @@
"indexing_stopped": "已停止索引",
"indexing_in_progress": "正在進行索引…",
"last_indexed": "最後索引時間",
"n_notes_queued_0": "{{ count }} 條筆記已加入索引隊列",
"note_chat": "筆記聊天",
"notes_indexed_0": "已索引 {{ count }} 條筆記",
"sources": "來源",
"start_indexing": "開始索引",
"use_advanced_context": "使用進階上下文",

View File

@ -839,9 +839,10 @@
"zoom_out_title": "Зменшити масштаб"
},
"zpetne_odkazy": {
"backlink": "{{count}} Зворотне посилання",
"backlinks": "{{count}} Зворотні посилання",
"relation": "зв'язок"
"relation": "зв'язок",
"backlink_one": "{{count}} Зворотне посилання",
"backlink_few": "{{count}} Зворотні посилання",
"backlink_many": "{{count}} Зворотні посилання"
},
"mobile_detail_menu": {
"insert_child_note": "Вставити дочірню нотатку",
@ -1350,13 +1351,7 @@
"indexing_stopped": "Індексацію зупинено",
"indexing_in_progress": "Триває індексація...",
"last_indexed": "Остання індексація",
"n_notes_queued_0": "{{ count }} нотатка в черзі на індексацію",
"n_notes_queued_1": "{{ count }} нотатки в черзі на індексацію",
"n_notes_queued_2": "{{ count }} нотаток в черзі на індексацію",
"note_chat": "Нотатка Чат",
"notes_indexed_0": "{{ count }} нотатка індексовано",
"notes_indexed_1": "{{ count }} нотатки індексовано",
"notes_indexed_2": "{{ count }} нотаток індексовано",
"sources": "Джерела",
"start_indexing": "Почати індексацію",
"use_advanced_context": "Використовувати розширений контекст",

View File

@ -19,6 +19,7 @@ import NoteLink from "./react/NoteLink";
import RawHtml from "./react/RawHtml";
import { ViewTypeOptions } from "./collections/interface";
import attributes from "../services/attributes";
import LoadResults from "../services/load_results";
export interface FloatingButtonContext {
parentComponent: Component;
@ -321,13 +322,7 @@ function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
useEffect(() => refresh(), [ note ]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
loadResults.getAttributeRows().some(attr =>
attr.type === "relation" &&
attr.name === "internalLink" &&
attributes.isAffecting(attr, note))
{
refresh();
}
if (needsRefresh(note, loadResults)) refresh();
});
// Determine the max height of the container.
@ -353,18 +348,18 @@ function Backlinks({ note, isDefaultViewMode }: FloatingButtonContext) {
{popupOpen && (
<div ref={backlinksContainerRef} className="backlinks-items dropdown-menu" style={{ display: "block" }}>
<BacklinksList noteId={note.noteId} />
<BacklinksList note={note} />
</div>
)}
</div>
);
}
function BacklinksList({ noteId }: { noteId: string }) {
function BacklinksList({ note }: { note: FNote }) {
const [ backlinks, setBacklinks ] = useState<BacklinksResponse>([]);
useEffect(() => {
server.get<BacklinksResponse>(`note-map/${noteId}/backlinks`).then(async (backlinks) => {
function refresh() {
server.get<BacklinksResponse>(`note-map/${note.noteId}/backlinks`).then(async (backlinks) => {
// prefetch all
const noteIds = backlinks
.filter(bl => "noteId" in bl)
@ -372,7 +367,12 @@ function BacklinksList({ noteId }: { noteId: string }) {
await froca.getNotes(noteIds);
setBacklinks(backlinks);
});
}, [ noteId ]);
}
useEffect(() => refresh(), [ note ]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (needsRefresh(note, loadResults)) refresh();
});
return backlinks.map(backlink => (
<div>
@ -392,3 +392,9 @@ function BacklinksList({ noteId }: { noteId: string }) {
</div>
));
}
function needsRefresh(note: FNote, loadResults: LoadResults) {
return loadResults.getAttributeRows().some(attr =>
attr.type === "relation" &&
attributes.isAffecting(attr, note));
}

View File

@ -41,7 +41,7 @@ export function openNoteContextMenu(api: Api, event: ContextMenuEvent, note: FNo
x: event.pageX,
y: event.pageY,
items: [
...link_context_menu.getItems(),
...link_context_menu.getItems(event),
{ kind: "separator" },
{
title: t("board_view.insert-above"),
@ -81,7 +81,7 @@ export function openNoteContextMenu(api: Api, event: ContextMenuEvent, note: FNo
componentFn: () => NoteColorPicker({note})
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, note.noteId),
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, event, note.noteId),
});
}

View File

@ -14,7 +14,7 @@ export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parent
x: e.pageX,
y: e.pageY,
items: [
...link_context_menu.getItems(),
...link_context_menu.getItems(e),
{ kind: "separator" },
getArchiveMenuItem(note),
{
@ -40,6 +40,6 @@ export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parent
componentFn: () => NoteColorPicker({note: note})
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, note.noteId),
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, e, note.noteId),
})
}

View File

@ -117,7 +117,10 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
if (loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) // note title change.
|| loadResults.getAttributeRows().some((a) => noteIds.includes(a.noteId ?? ""))) // subnote change.
{
// Defer execution after the load results are processed so that the event builder has the updated data to work with.
setTimeout(() => {
calendarRef.current?.refetchEvents();
}, 0);
}
});

View File

@ -12,7 +12,7 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent, is
let items: MenuItem<keyof CommandMappings>[] = [
...buildGeoLocationItem(e),
{ kind: "separator" },
...linkContextMenu.getItems(),
...linkContextMenu.getItems(e),
];
if (isEditable) {
@ -32,14 +32,14 @@ export default function openContextMenu(noteId: string, e: LeafletMouseEvent, is
x: e.originalEvent.pageX,
y: e.originalEvent.pageY,
items,
selectMenuItemHandler: ({ command }, e) => {
selectMenuItemHandler: ({ command }) => {
if (command === "deleteFromMap") {
appContext.triggerCommand(command, { noteId });
return;
}
// Pass the events to the link context menu
linkContextMenu.handleLinkContextMenuItem(command, noteId);
linkContextMenu.handleLinkContextMenuItem(command, e, noteId);
}
});
}

View File

@ -174,7 +174,7 @@ export function showRowContextMenu(parentComponent: Component, e: MouseEvent, ro
contextMenu.show({
items: [
...link_context_menu.getItems(),
...link_context_menu.getItems(e),
{ kind: "separator" },
{
title: t("table_view.row-insert-above"),
@ -227,7 +227,7 @@ export function showRowContextMenu(parentComponent: Component, e: MouseEvent, ro
componentFn: () => NoteColorPicker({note: rowData.noteId})
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, rowData.noteId),
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, e, rowData.noteId),
x: e.pageX,
y: e.pageY
});

View File

@ -3,6 +3,8 @@ import appContext, { type CommandData, type CommandListenerData, type EventData,
import type BasicWidget from "../basic_widget.js";
import Component from "../../components/component.js";
import splitService from "../../services/resizer.js";
import { isMobile } from "../../services/utils.js";
import NoteContext from "../../components/note_context.js";
interface SplitNoteWidget extends BasicWidget {
hasBeenAlreadyShown?: boolean;
@ -49,13 +51,14 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
this.child(widget);
if (noteContext.mainNtxId && noteContext.ntxId) {
if (noteContext.mainNtxId && noteContext.ntxId && !isMobile()) {
splitService.setupNoteSplitResizer([noteContext.mainNtxId,noteContext.ntxId]);
}
}
async openNewNoteSplitEvent({ ntxId, notePath, hoistedNoteId, viewScope }: EventData<"openNewNoteSplit">) {
const mainNtxId = appContext.tabManager.getActiveMainContext()?.ntxId;
const activeContext = appContext.tabManager.getActiveMainContext();
const mainNtxId = activeContext?.ntxId;
if (!mainNtxId) {
console.warn("Missing main note context ID");
return;
@ -69,22 +72,30 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
hoistedNoteId = hoistedNoteId || appContext.tabManager.getActiveContext()?.hoistedNoteId;
const noteContext = await appContext.tabManager.openEmptyTab(null, hoistedNoteId, mainNtxId);
const subContexts = activeContext.getSubContexts();
let noteContext: NoteContext | undefined = undefined;
if (isMobile() && subContexts.length > 1) {
noteContext = subContexts.find(s => s.ntxId !== ntxId);
}
if (!noteContext) {
noteContext = await appContext.tabManager.openEmptyTab(null, hoistedNoteId, mainNtxId);
// remove the original position of newly created note context
const ntxIds = appContext.tabManager.children.map((c) => c.ntxId).filter((id) => id !== noteContext?.ntxId) as string[];
// insert the note context after the originating note context
if (!noteContext.ntxId) {
logError("Failed to create new note context!");
return;
}
// remove the original position of newly created note context
const ntxIds = appContext.tabManager.children.map((c) => c.ntxId).filter((id) => id !== noteContext.ntxId) as string[];
// insert the note context after the originating note context
ntxIds.splice(ntxIds.indexOf(ntxId) + 1, 0, noteContext.ntxId);
this.triggerCommand("noteContextReorder", { ntxIdsInOrder: ntxIds });
// move the note context rendered widget after the originating widget
this.$widget.find(`[data-ntx-id="${noteContext.ntxId}"]`).insertAfter(this.$widget.find(`[data-ntx-id="${ntxId}"]`));
}
await appContext.tabManager.activateNoteContext(noteContext.ntxId);

View File

@ -34,15 +34,26 @@ export default function MarkdownImportDialog() {
}
});
function submit() {
setShown(false);
if (editorApiRef.current && text) {
convertMarkdownToHtml(text, editorApiRef.current);
}
}
return (
<Modal
className="markdown-import-dialog" title={t("markdown_import.dialog_title")} size="lg"
footer={<Button className="markdown-import-button" text={t("markdown_import.import_button")} onClick={() => setShown(false)} keyboardShortcut="Ctrl+Enter" />}
onShown={() => markdownImportTextArea.current?.focus()}
onHidden={async () => {
if (editorApiRef.current) {
await convertMarkdownToHtml(text, editorApiRef.current);
footer={
<Button
className="markdown-import-button"
text={t("markdown_import.import_button")}
keyboardShortcut="Ctrl+Enter"
onClick={submit}
/>
}
onShown={() => markdownImportTextArea.current?.focus()}
onHidden={() => {
setShown(false);
setText("");
}}
@ -55,7 +66,7 @@ export default function MarkdownImportDialog() {
onKeyDown={(e) => {
if (e.key === "Enter" && e.ctrlKey) {
e.preventDefault();
setShown(false);
submit();
}
}}></textarea>
</Modal>

View File

@ -1,12 +1,13 @@
import { useContext } from "preact/hooks";
import appContext from "../../components/app_context";
import contextMenu from "../../menus/context_menu";
import appContext, { CommandMappings } from "../../components/app_context";
import contextMenu, { MenuItem } from "../../menus/context_menu";
import branches from "../../services/branches";
import { t } from "../../services/i18n";
import note_create from "../../services/note_create";
import tree from "../../services/tree";
import ActionButton from "../react/ActionButton";
import { ParentComponent } from "../react/react_utils";
import BasicWidget from "../basic_widget";
export default function MobileDetailMenu() {
const parentComponent = useContext(ParentComponent);
@ -16,17 +17,33 @@ export default function MobileDetailMenu() {
icon="bx bx-dots-vertical-rounded"
text=""
onClick={(e) => {
const note = appContext.tabManager.getActiveContextNote();
const ntxId = (parentComponent as BasicWidget | null)?.getClosestNtxId();
if (!ntxId) return;
contextMenu.show<"insertChildNote" | "delete" | "showRevisions">({
x: e.pageX,
y: e.pageY,
items: [
const noteContext = appContext.tabManager.getNoteContextById(ntxId);
const subContexts = noteContext.getMainContext().getSubContexts();
const isMainContext = noteContext?.isMainContext();
const note = noteContext.note;
const items: (MenuItem<keyof CommandMappings>)[] = [
{ title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" },
{ title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note?.noteId !== "root" },
{ kind: "separator" },
{ title: t("mobile_detail_menu.note_revisions"), command: "showRevisions", uiIcon: "bx bx-history" }
],
{ title: t("mobile_detail_menu.note_revisions"), command: "showRevisions", uiIcon: "bx bx-history" },
{ kind: "separator" },
subContexts.length < 2 && { title: t("create_pane_button.create_new_split"), command: "openNewNoteSplit", uiIcon: "bx bx-dock-right" },
!isMainContext && { title: t("close_pane_button.close_this_pane"), command: "closeThisNoteSplit", uiIcon: "bx bx-x" }
].filter(i => !!i) as MenuItem<keyof CommandMappings>[];
const lastItem = items.at(-1);
if (lastItem && "kind" in lastItem && lastItem.kind === "separator") {
items.pop();
}
contextMenu.show<keyof CommandMappings>({
x: e.pageX,
y: e.pageY,
items,
selectMenuItemHandler: async ({ command }) => {
if (command === "insertChildNote") {
note_create.createNote(appContext.tabManager.getActiveContextNotePath() ?? undefined);
@ -46,7 +63,7 @@ export default function MobileDetailMenu() {
parentComponent.triggerCommand("setActiveScreen", { screen: "tree" });
}
} else if (command && parentComponent) {
parentComponent.triggerCommand(command);
parentComponent.triggerCommand(command, { ntxId });
}
},
forcePositionOnMobile: true

View File

@ -1,18 +1,19 @@
import { useContext } from "preact/hooks";
import ActionButton from "../react/ActionButton";
import { ParentComponent } from "../react/react_utils";
import { t } from "../../services/i18n";
import { useNoteContext } from "../react/hooks";
export default function ToggleSidebarButton() {
const parentComponent = useContext(ParentComponent);
const { noteContext, parentComponent } = useNoteContext();
return (
<ActionButton
<div style={{ contain: "none", minWidth: 8 }}>
{ noteContext?.isMainContext() && <ActionButton
icon="bx bx-sidebar"
text={t("note_tree.toggle-sidebar")}
onClick={() => parentComponent?.triggerCommand("setActiveScreen", {
screen: "tree"
})}
/>
/>}
</div>
)
}

View File

@ -484,8 +484,7 @@ export function useNoteBlob(note: FNote | null | undefined, componentId?: string
return;
}
if (loadResults.hasRevisionForNote(note.noteId) ||
loadResults.isNoteContentReloaded(note.noteId, componentId)) {
if (loadResults.isNoteContentReloaded(note.noteId, componentId)) {
refresh();
}
});

View File

@ -26,7 +26,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
&& !(await noteContext?.isReadOnly()),
toggleCommand: "toggleRibbonTabClassicEditor",
content: FormattingToolbar,
activate: () => !options.is("editedNotesOpenInRibbon"),
activate: ({ note }) => !options.is("editedNotesOpenInRibbon") || !note?.hasOwnedLabel("dateNote"),
stayInDom: true
},
{

View File

@ -19,3 +19,9 @@ export function onWheelHorizontalScroll(event: WheelEvent) {
event.stopImmediatePropagation();
(event.currentTarget as HTMLElement).scrollLeft += event.deltaY + event.deltaX;
}
export function getClosestNtxId(element: HTMLElement) {
const closestNtxEl = element.closest<HTMLElement>("[data-ntx-id]");
if (!closestNtxEl) return null;
return closestNtxEl.dataset.ntxId ?? null;
}

View File

@ -84,7 +84,7 @@
"electron-debug": "4.1.0",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"express": "5.1.0",
"express": "5.2.0",
"express-http-proxy": "2.1.2",
"express-openid-connect": "2.19.3",
"express-rate-limit": "8.2.1",

View File

@ -6,23 +6,21 @@
which will work even without Trilium being in focus (requires app restart
to take effect).</p>
<h2>Tree</h2>
<p>See the corresponding section:&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/oPVyFC7WL2Lp/_help_DvdZhoQZY9Yd">Keyboard shortcuts</a>
<p>See the corresponding section:&nbsp;<a class="reference-link" href="#root/_help_DvdZhoQZY9Yd">Keyboard shortcuts</a>
</p>
<h2>Note navigation</h2>
<ul>
<li data-list-item-id="ed8fd9c2ab08ae80ea76c1ae5dc943e90"><kbd>Alt</kbd> + <kbd><span></span></kbd>, <kbd>Alt</kbd> + <kbd><span></span></kbd>
<li><kbd>Alt</kbd> + <kbd><span></span></kbd>, <kbd>Alt</kbd> + <kbd><span></span></kbd>
go back / forwards in the history</li>
<li data-list-item-id="eff3c5760b3d985e7808f1524982fcb9b"><kbd>Ctrl</kbd> + <kbd>J</kbd> show <a href="#root/_help_MMiBEQljMQh2">"Jump to" dialog</a>
<li><kbd>Ctrl</kbd> + <kbd>J</kbd> show <a href="#root/_help_MMiBEQljMQh2">"Jump to" dialog</a>
</li>
<li data-list-item-id="e1fdee08a424868922b0579e78584279c"><kbd>Ctrl</kbd> + <kbd>.</kbd> scroll to current note (useful when you
<li><kbd>Ctrl</kbd> + <kbd>.</kbd> scroll to current note (useful when you
scroll away from your note or your focus is currently in the editor)</li>
<li
data-list-item-id="e9bbc51f19a729c10997010a915779d07"><kbd><span>Backspace</span></kbd> jumps to parent note</li>
<li data-list-item-id="eaaa0f36b2e561d4a24358552e633d924"><kbd>Alt</kbd> + <kbd>C</kbd> collapse whole note tree</li>
<li data-list-item-id="ee625a141f5298dbd52ecb6e56693e647"><kbd>Alt</kbd> + <kbd>-</kbd> (alt with minus sign) collapse subtree (if
<li><kbd><span>Backspace</span></kbd> jumps to parent note</li>
<li><kbd>Alt</kbd> + <kbd>C</kbd> collapse whole note tree</li>
<li><kbd>Alt</kbd> + <kbd>-</kbd> (alt with minus sign) collapse subtree (if
some subtree takes too much space on tree pane you can collapse it)</li>
<li
data-list-item-id="ec1bf00840a017ea798880470e419f3d9">you can define a <a href="#root/_help_zEY4DaJG4YT5">label</a> <code>#keyboardShortcut</code> with
<li>you can define a <a href="#root/_help_zEY4DaJG4YT5">label</a> <code>#keyboardShortcut</code> with
e.g. value <kbd>Ctrl</kbd> + <kbd>I</kbd> . Pressing this keyboard combination
will then bring you to the note on which it is defined. Note that Trilium
must be reloaded/restarted (<kbd>Ctrl</kbd> + <kbd>R</kbd> ) for changes to
@ -31,23 +29,21 @@
<p>See demo of some of these features in <a href="#root/_help_MMiBEQljMQh2">note navigation</a>.</p>
<h2>Tabs</h2>
<ul>
<li data-list-item-id="e6a9d460427c0394177000c8a0eecfca4"><kbd>Ctrl</kbd> + <kbd>🖱 Left click</kbd> (or middle mouse click) on note
<li><kbd>Ctrl</kbd> + <kbd>🖱 Left click</kbd> (or middle mouse click) on note
link opens note in a new tab</li>
</ul>
<p>Only in desktop (electron build):</p>
<ul>
<li data-list-item-id="e745e6690db1a4bee718faa94b272013e"><kbd>Ctrl</kbd> + <kbd>T</kbd> opens empty tab</li>
<li data-list-item-id="ec34c224c2c0dd74367753a6aff4f9721"><kbd>Ctrl</kbd> + <kbd>W</kbd> closes active tab</li>
<li data-list-item-id="e4b9a2163cccc3ab7dbdcf6fb7f149315"><kbd>Ctrl</kbd> + <kbd>Tab</kbd> activates next tab</li>
<li data-list-item-id="eeeabd750227ec42c30b282b9b8ab5c8d"><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd> activates previous tab</li>
<li><kbd>Ctrl</kbd> + <kbd>T</kbd> opens empty tab</li>
<li><kbd>Ctrl</kbd> + <kbd>W</kbd> closes active tab</li>
<li><kbd>Ctrl</kbd> + <kbd>Tab</kbd> activates next tab</li>
<li><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd> activates previous tab</li>
</ul>
<h2>Creating notes</h2>
<ul>
<li data-list-item-id="e90a7441e67297c921f8bb2145829dd55"><kbd>CTRL</kbd>+<kbd>O</kbd> creates new note after the current note</li>
<li
data-list-item-id="e6064f6dd7b09083448cfb52247c94baa"><kbd>CTRL</kbd>+<kbd>P</kbd> creates new sub-note into current note</li>
<li
data-list-item-id="e191cb0e7231c4439632d70da65dcf800"><kbd>F2</kbd> edit&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/IakOLONlIfGI/_help_TBwsyfadTA18">Branch prefix</a>&nbsp;of
<li><kbd>CTRL</kbd>+<kbd>O</kbd> creates new note after the current note</li>
<li><kbd>CTRL</kbd>+<kbd>P</kbd> creates new sub-note into current note</li>
<li><kbd>F2</kbd> edit&nbsp;<a class="reference-link" href="#root/_help_TBwsyfadTA18">Branch prefix</a>&nbsp;of
current note clone</li>
</ul>
<h2>Editing notes</h2>
@ -58,32 +54,30 @@
class="reference-link" href="#root/_help_QrtTYPmdd1qq">Markdown-like formatting</a>.</p>
</aside>
<ul>
<li data-list-item-id="eea3611b470b2482ccf07e8844e4f66b9"><kbd>Enter</kbd> in tree pane switches from tree pane into note title.
<li><kbd>Enter</kbd> in tree pane switches from tree pane into note title.
Enter from note title switches focus to text editor. <kbd>Ctrl</kbd>+<kbd>.</kbd> switches
back from editor to tree pane.</li>
<li data-list-item-id="e5dab831910ac694ccef7bc474b2e67fc"><kbd>Ctrl</kbd>+<kbd>.</kbd> jump away from the editor to tree pane and
<li><kbd>Ctrl</kbd>+<kbd>.</kbd> jump away from the editor to tree pane and
scroll to current note</li>
</ul>
<h2>Runtime shortcuts</h2>
<p>These are hooked in Electron to be similar to native browser keyboard
shortcuts.</p>
<ul>
<li data-list-item-id="e1a17ce297e78109362180536b7eabd68"><kbd>F5</kbd>, <kbd>Ctrl</kbd>+<kbd>R</kbd> reloads Trilium front-end</li>
<li
data-list-item-id="e07d1b25dc46ce0b0bd76a8db13373198"><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd> show developer tools</li>
<li
data-list-item-id="e7dc66078ba520c48364952f2ac48aa79"><kbd>Ctrl</kbd>+<kbd>F</kbd> show search dialog</li>
<li data-list-item-id="e63161bd165a51e19fd04dd1223173e68"><kbd>Ctrl</kbd>+<kbd>-</kbd> zoom out</li>
<li data-list-item-id="e961144d0a57b51792830a2182459c8cf"><kbd>Ctrl</kbd>+<kbd>=</kbd> zoom in</li>
<li><kbd>F5</kbd>, <kbd>Ctrl</kbd>+<kbd>R</kbd> reloads Trilium front-end</li>
<li><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd> show developer tools</li>
<li><kbd>Ctrl</kbd>+<kbd>F</kbd> show search dialog</li>
<li><kbd>Ctrl</kbd>+<kbd>-</kbd> zoom out</li>
<li><kbd>Ctrl</kbd>+<kbd>=</kbd> zoom in</li>
</ul>
<h2>Other</h2>
<ul>
<li data-list-item-id="eb007c4ea2f2cd6eefbc8972d538c724c"><kbd>Alt</kbd> + <kbd>O</kbd> show SQL console (use only if you know what
<li><kbd>Alt</kbd> + <kbd>O</kbd> show SQL console (use only if you know what
you're doing)</li>
<li data-list-item-id="e9caedb43e6c66fc7c57b4899bd6d5d76"><kbd>Alt</kbd> + <kbd>M</kbd> distraction-free mode - display only note
<li><kbd>Alt</kbd> + <kbd>M</kbd> distraction-free mode - display only note
editor, everything else is hidden</li>
<li data-list-item-id="ec55bd0e5ff7da0c488a5378c0bdabed5"><kbd>F11</kbd> toggle full screen</li>
<li data-list-item-id="e8d4ec0d76155371de5d060ad661cb19e"><kbd>Ctrl</kbd> + <kbd>S</kbd> toggle <a href="#root/_help_eIg8jdvaoNNd">search</a> form
<li><kbd>F11</kbd> toggle full screen</li>
<li><kbd>Ctrl</kbd> + <kbd>S</kbd> toggle <a href="#root/_help_eIg8jdvaoNNd">search</a> form
in tree pane</li>
<li data-list-item-id="eb938081f527f44b158be7c3ed4af4925"><kbd>Alt</kbd> +<kbd>A</kbd> show note <a href="#root/_help_zEY4DaJG4YT5">attributes</a> dialog</li>
<li><kbd>Alt</kbd> +<kbd>A</kbd> show note <a href="#root/_help_zEY4DaJG4YT5">attributes</a> dialog</li>
</ul>

View File

@ -2,52 +2,47 @@
with multiple keyboard shortcuts to make editing faster:</p>
<h2>Navigation within the tree</h2>
<ul>
<li data-list-item-id="eede1a5721dab5213153c6bb25bad38c9"><kbd><span></span></kbd> and <kbd><span></span></kbd> to navigate between
<li><kbd><span></span></kbd> and <kbd><span></span></kbd> to navigate between
notes.</li>
<li data-list-item-id="ec4c4ab60720b34a95f73e93223ed3628"><kbd><span></span></kbd> to collapse a note with children, or <kbd><span></span></kbd> to
<li><kbd><span></span></kbd> to collapse a note with children, or <kbd><span></span></kbd> to
expand it.</li>
<li data-list-item-id="eb5ad59d78e70611d0233ffbb5ede3b96"><kbd><span></span></kbd> on a note with no children to navigate to its
<li><kbd><span></span></kbd> on a note with no children to navigate to its
parent.</li>
</ul>
<h2>Opening notes</h2>
<ul>
<li data-list-item-id="e3d92e8c3a1e5b9d6f2efe5067004ef5c"><kbd>Click</kbd> to open the note in the current tab.</li>
<li data-list-item-id="e8ae44ff429a0da1d529c627874f54908"><kbd>Ctrl</kbd>+<kbd>Click</kbd> or <kbd>Middle click</kbd> to open the note
<li><kbd>Click</kbd> to open the note in the current tab.</li>
<li><kbd>Ctrl</kbd>+<kbd>Click</kbd> or <kbd>Middle click</kbd> to open the note
in a new tab.</li>
<li data-list-item-id="e183b1a014e2bd563450dac0c8913c6f0"><kbd>Ctrl</kbd>+<kbd>Right click</kbd> to open the note in&nbsp;<a class="reference-link"
<li><kbd>Ctrl</kbd>+<kbd>Right click</kbd> to open the note in&nbsp;<a class="reference-link"
href="#root/_help_ZjLYv08Rp3qC">Quick edit</a>.</li>
</ul>
<h2>Clipboard management</h2>
<ul>
<li data-list-item-id="e28fb424bcc0d06b97ddcbcbc5145c350"><kbd>Ctrl</kbd>+<kbd>C</kbd> to copy one or more notes based on selection
(see&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_IakOLONlIfGI">Cloning Notes</a>).</li>
<li
data-list-item-id="e11c4c9fcdfeb566a58d9319a340ff893"><kbd>Ctrl</kbd>+<kbd>X</kbd> to cut one or more notes (for moving them).</li>
<li
data-list-item-id="e242519cf16b3414242c4d11f20688b5b"><kbd>Ctrl</kbd>+<kbd>V</kbd> to paste them somewhere (which results in
<li><kbd>Ctrl</kbd>+<kbd>C</kbd> to copy one or more notes based on selection
(see&nbsp;<a class="reference-link" href="#root/_help_IakOLONlIfGI">Cloning Notes</a>).</li>
<li><kbd>Ctrl</kbd>+<kbd>X</kbd> to cut one or more notes (for moving them).</li>
<li><kbd>Ctrl</kbd>+<kbd>V</kbd> to paste them somewhere (which results in
a copy or move based on the shortcut used).</li>
</ul>
<h2>Moving notes</h2>
<ul>
<li data-list-item-id="ee2d0e5633e0ab23d0fcde7b968731794"><kbd>Ctrl</kbd> + <kbd><span></span></kbd> , <kbd>Ctrl</kbd> + <kbd><span></span></kbd> -
<li><kbd>Ctrl</kbd> + <kbd><span></span></kbd> , <kbd>Ctrl</kbd> + <kbd><span></span></kbd> -
move note up/down in the note list.</li>
<li data-list-item-id="e14146e7aacfc92aa7daf8215ce4b379c"><kbd>Ctrl</kbd> + <kbd><span></span></kbd> - move note up in the note tree.</li>
<li
data-list-item-id="e21a7f5ab9d62af04b6ab81b0cc4cf8c7"><kbd>Ctrl</kbd>+<kbd><span></span></kbd> - move note down in the note
<li><kbd>Ctrl</kbd> + <kbd><span></span></kbd> - move note up in the note tree.</li>
<li><kbd>Ctrl</kbd>+<kbd><span></span></kbd> - move note down in the note
tree.</li>
<li data-list-item-id="e99f4b0aef9693ad25ca0ce62c5b67418"><kbd>Del</kbd> - deletes note and optionally its subtree (asked in the
<li><kbd>Del</kbd> - deletes note and optionally its subtree (asked in the
dialog).</li>
</ul>
<h2>Multiple selection</h2>
<p>See&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/oPVyFC7WL2Lp/_help_yTjUdsOi4CIE">Multiple selection</a>&nbsp;for
<p>See&nbsp;<a class="reference-link" href="#root/_help_yTjUdsOi4CIE">Multiple selection</a>&nbsp;for
more information about how selection works.</p>
<ul>
<li data-list-item-id="ebe4907d702229ab599e50f753d23cfb6"><kbd>Alt</kbd>+<kbd>Click</kbd> add a single note to the current selection.</li>
<li
data-list-item-id="ecf6bb49f8d993d376382dcab4e4a7ee8"><kbd>Shift</kbd>+<kbd>Click</kbd> select a range of notes, starting from
<li><kbd>Alt</kbd>+<kbd>Click</kbd> add a single note to the current selection.</li>
<li><kbd>Shift</kbd>+<kbd>Click</kbd> select a range of notes, starting from
the current note (the highlighted one) to the one that is being clicked.</li>
<li
data-list-item-id="eb1398c2cf85b53eafc3b7ab7a0974668"><kbd>Shift</kbd>+<kbd><span></span></kbd>, <kbd>Shift</kbd>+<kbd><span></span></kbd>
<li><kbd>Shift</kbd>+<kbd><span></span></kbd>, <kbd>Shift</kbd>+<kbd><span></span></kbd>
multi-select not above/below.</li>
<li data-list-item-id="ebc1274f576f67d08a6d665a7318d80a8"><kbd>Ctrl</kbd>+<kbd>A</kbd> select all notes in the current level</li>
<li><kbd>Ctrl</kbd>+<kbd>A</kbd> select all notes in the current level</li>
</ul>

View File

@ -4,29 +4,28 @@ class="image image-style-align-center">
<img style="aspect-ratio:1398/1015;" src="Split View_2_Split View_im.png"
width="1398" height="1015">
</figure>
<h2><strong>Interactions</strong></h2>
<ul>
<li>Press the
<li data-list-item-id="eb22263532280510ca0efeb2c2e757629">Press the
<img src="Split View_Split View_imag.png">button to the right of a note's title to open a new split to the right
of it.
<ul>
<li>It is possible to have as many splits as desired, simply press again the
<li data-list-item-id="eda17492ea2d8da7c4bf2fb3e2f7bfbe9">It is possible to have as many splits as desired, simply press again the
button.</li>
<li>Only horizontal splits are possible, vertical or drag &amp; dropping is
<li data-list-item-id="ea0223c947ea17534d577c9cfef4d5c6e">Only horizontal splits are possible, vertical or drag &amp; dropping is
not supported.</li>
</ul>
</li>
<li>When at least one split is open, press the
<li data-list-item-id="e77d99fdc9a0846903de57d1b710fdd56">When at least one split is open, press the
<img src="Split View_3_Split View_im.png">button next to it to close it.</li>
<li>Use the
<li data-list-item-id="ec9d11f5bcfd10795f282e275938b2f4a">Use the
<img src="Split View_4_Split View_im.png">or the
<img src="Split View_1_Split View_im.png">button to move around the splits.</li>
<li>Each <a href="#root/_help_3seOhtN8uLIY">tab</a> has its own split view configuration
<li data-list-item-id="e8384a579c3d6ee8df4d7dbf9b07c3436">Each <a href="#root/_help_3seOhtN8uLIY">tab</a> has its own split view configuration
(e.g. one tab can have two notes in a split view, whereas the others are
one-note views).
<ul>
<li>The tab will indicate only the title of the main note (the first one in
<li data-list-item-id="e298299f6b2f1b9d8b5f26a5f8a0c9092">The tab will indicate only the title of the main note (the first one in
the list).</li>
</ul>
</li>
@ -44,3 +43,41 @@ class="image image-style-align-center">
the other, by hoisting the old place in the first split and hoisting the
new place to the second one. This will allow easy cut and paste without
the tree jumping around from switching between notes.</p>
<h2>Mobile support</h2>
<p>Since v0.100.0, it's possible to have a split view on the mobile view
as well, with the following differences from the desktop version of the
split:</p>
<ul>
<li data-list-item-id="efc0bb8eb81ea1617b73188613f2ede5d">On smartphones, the split views are laid out vertically (one on the top
and one on the bottom), instead of horizontally as on the desktop.</li>
<li
data-list-item-id="e7659da22c36db39ae8e3dc5424afba1e">There can be only one split open per tab.</li>
<li data-list-item-id="eb848af9837a484a9de9117922c6d7186">It's not possible to resize the two split panes.</li>
<li data-list-item-id="e869c240066f602fbc1c0e55259ba62e5">When the keyboard is opened, the active note will be “maximized”, thus
allowing for more space even when a split is open. When the keyboard is
closed, the splits become equal in size again.</li>
</ul>
<p>Interaction:</p>
<ul>
<li data-list-item-id="edbf5c644758db5aca9867c516f97542b">To create a new split, click the three dots button on the right of the
note title and select <em>Create new split</em>.
<ul>
<li data-list-item-id="eed272873b629f70418c3e7074a829369">This option will only be available if there is no split already open in
the current tab.</li>
</ul>
</li>
<li data-list-item-id="e733863e6058336ebfcf27042f56be312">To close a split, click the three dots button on the right of the note
title and select <em>Close this pane</em>.
<ul>
<li data-list-item-id="e9e6c191873dcd658242c64553343e0c7">Note that this option will only be available on the second note in the
split (the one at the bottom on smartphones, the one on the right on tablets).</li>
</ul>
</li>
<li data-list-item-id="e780e5e7736b4d26705102545c5626a6a">When long-pressing a link, a contextual menu will show up with an option
to <em>Open note in a new split</em>.
<ul>
<li data-list-item-id="e264277ee51f6d266a4088e2545f6648d">If there's already a split, the option will replace the existing split
instead.</li>
</ul>
</li>
</ul>

View File

@ -193,7 +193,7 @@
<td><code>#calendar:color</code>
</td>
<td>Similar to <code>#color</code>, but applies the color only for the event
in the calendar and not for other places such as the note tree.</td>
in the calendar and not for other places such as the note tree. (<em>Deprecated</em>)</td>
</tr>
<tr>
<td><code>#iconClass</code>

View File

@ -4,7 +4,7 @@
<p>Most of the interaction with text notes is done via the built-in toolbars.
Depending on preference, there are two different layouts:</p>
<ul>
<li data-list-item-id="eafcb31c309cb140eedbb41048a0e0db1">The <em>Floating toolbar</em> is hidden by default and only appears when
<li>The <em>Floating toolbar</em> is hidden by default and only appears when
needed. In this mode there are actually two different toolbars:
<br>
<img src="1_Text_image.png" width="496"
@ -12,7 +12,7 @@
<img src="2_Text_image.png" width="812"
height="114">
</li>
<li data-list-item-id="e59aa3103fa4c5de33075e51f8d482164">A toolbar that appears when text is selected. This provides text-level
<li>A toolbar that appears when text is selected. This provides text-level
formatting such as bold, italic, text colors, inline code, etc.
<br><em><img src="Text_image.png" width="1109" height="124"></em>
</li>
@ -20,8 +20,6 @@
<p>Fore more information see&nbsp;<a class="reference-link" href="#root/_help_nRhnJkTT8cPs">Formatting toolbar</a>.</p>
<h2>Features and formatting</h2>
<p>Here's a list of various features supported by text notes:</p>
<figure
class="table">
<table>
<thead>
<tr>
@ -163,14 +161,14 @@ class="table">
</li>
<li data-list-item-id="e564b978c09fe5adf476b331b1e0640e3"><a class="reference-link" href="#root/_help_KC1HB96bqqHX">Templates</a>
</li>
<li data-list-item-id="e756306c31d9beffbba3820b6d1b9bc61"><a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/gLt3vA97tMcp/_help_5wZallV2Qo1t">Format Painter</a>
<li data-list-item-id="e756306c31d9beffbba3820b6d1b9bc61"><a class="reference-link" href="#root/_help_5wZallV2Qo1t">Format Painter</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</figure>
<h2>Read-Only vs. Editing Mode</h2>
<p>Text notes are usually opened in edit mode. However, they may open in
read-only mode if the note is too big or the note is explicitly marked

View File

@ -12,11 +12,11 @@
<p>Apart from using the UI, it is also possible to quickly insert headings
using the Markdown-like shortcuts:</p>
<ul>
<li data-list-item-id="e6083df2814e520e138326ef004812a46"><code>##</code> for Heading 2</li>
<li data-list-item-id="e8b8acfd06c340271cc4fe84f65e67375"><code>###</code> for Heading 3</li>
<li data-list-item-id="e19e95a52ddbcd54428ed5ab40df165a7"><code>####</code> for Heading 4</li>
<li data-list-item-id="e69788e0bd8f4459748b8df997a19fa92"><code>#####</code> for Heading 5</li>
<li data-list-item-id="eb7205b764231a314c9e9c1590c12ac1c"><code>######</code> for Heading 6</li>
<li><code>##</code> for Heading 2</li>
<li><code>###</code> for Heading 3</li>
<li><code>####</code> for Heading 4</li>
<li><code>#####</code> for Heading 5</li>
<li><code>######</code> for Heading 6</li>
</ul>
<h2>Font size</h2>
<figure class="image image-style-align-right">
@ -44,17 +44,17 @@
<p>This formatting can be easily removed using the <em>Remove formatting</em> item.</p>
<p>The following keyboard shortcuts can be used here:</p>
<ul>
<li data-list-item-id="ecda6994b8be7fe33daeccbeb8bffe20b"><kbd>Ctrl</kbd>+<kbd>B</kbd> for bold</li>
<li data-list-item-id="eaa20ab7965945937bd10a273e5fc323f"><kbd>Ctrl</kbd>+<kbd>I</kbd> for italic</li>
<li data-list-item-id="e86881b33671d3bd6930d361e4bb76767"><kbd>Ctrl</kbd>+<kbd>U</kbd> for underline</li>
<li><kbd>Ctrl</kbd>+<kbd>B</kbd> for bold</li>
<li><kbd>Ctrl</kbd>+<kbd>I</kbd> for italic</li>
<li><kbd>Ctrl</kbd>+<kbd>U</kbd> for underline</li>
</ul>
<p>Alternatively, Markdown-like formatting can be used:</p>
<ul>
<li data-list-item-id="e90b0f040ab78084c41cd8f44045528ec"><strong>Bold</strong>: Type <code>**text**</code> or <code>__text__</code>
<li><strong>Bold</strong>: Type <code>**text**</code> or <code>__text__</code>
</li>
<li data-list-item-id="eebc210ee7f2b92e0885cd411c6fb8b80"><em>Italic</em>: Type <code>*text*</code> or <code>_text_</code>
<li><em>Italic</em>: Type <code>*text*</code> or <code>_text_</code>
</li>
<li data-list-item-id="eb7628d2cb441576b3b3edf6c42ab84b4"><del>Strikethrough</del>: Type <code>~~text~~</code>
<li><del>Strikethrough</del>: Type <code>~~text~~</code>
</li>
</ul>
<h2>Superscript, subscript</h2>
@ -90,7 +90,7 @@
<p>When pasting content that comes with undesired formatting, an alternative
to pasting and then removing formatting is pasting as plain text via <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>V</kbd>.</p>
<h2>Format painter</h2>
<p>The&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/gLt3vA97tMcp/_help_5wZallV2Qo1t">Format Painter</a>&nbsp;allows
<p>The&nbsp;<a class="reference-link" href="#root/_help_5wZallV2Qo1t">Format Painter</a>&nbsp;allows
users to copy the formatting of text (such as bold, italic, Strikethrough,
etc.) and apply it to other parts of the document. It helps maintain consistent
formatting and accelerates the creation of rich content.</p>

View File

@ -6,7 +6,7 @@
<p>This is a premium feature of the editor we are using (CKEditor) and we
benefit from it thanks to an written agreement with the team. See &nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/iPIMuisry3hd/_help_gLt3vA97tMcp">Premium features</a>&nbsp;for more information.</p>
class="reference-link" href="#root/_help_gLt3vA97tMcp">Premium features</a>&nbsp;for more information.</p>
</aside>
<p>The Format Painter is a feature in text notes that allows users to copy
the formatting of text (such as <strong>bold</strong>, <em>italic</em>, <del>Strikethrough</del>,
@ -14,20 +14,24 @@
formatting and accelerates the creation of rich content.</p>
<h2>Usage Instructions</h2>
<p>Click the text that you want to copy the formatting from and use the paint
formatting toolbar button (<span><img class="image_resized" style="aspect-ratio:150/150;width:2.7%;" src="Format Painter_746436a2e1.svg" alt="Format painter" width="150" height="150">) </span>to
copy the style. Then select the target text with your mouse to apply the
formatting.</p>
formatting toolbar button (
<img class="image_resized" style="aspect-ratio:150/150;width:2.7%;"
src="Format Painter_746436a2e1.svg"
alt="Format painter" width="150" height="150">) to copy the style. Then select the target text with your mouse to apply
the formatting.</p>
<ul>
<li data-list-item-id="e9a728e8f49fb3ecf1202002dfafccabd"><strong>To copy the formatting</strong>: Place the cursor inside a text
<li><strong>To copy the formatting</strong>: Place the cursor inside a text
with some formatting and click the paint formatting toolbar button. Notice
that the mouse cursor changes to the <span><img class="image_resized" style="aspect-ratio:30/20;width:3.64%;" src="Format Painter_e144e96df9.svg" alt="Format painter text cursor" width="30" height="20"></span>.</li>
<li
data-list-item-id="e745c039a3502d4e979d6dcde7876461b"><strong>To paint with the copied formatting</strong>: Click any word in
that the mouse cursor changes to the
<img class="image_resized" style="aspect-ratio:30/20;width:3.64%;"
src="Format Painter_e144e96df9.svg"
alt="Format painter text cursor" width="30" height="20">.</li>
<li><strong>To paint with the copied formatting</strong>: Click any word in
the document and the new formatting will be applied. Alternatively, instead
of clicking a single word, you can select a text fragment (like an entire
paragraph). Notice that the cursor will go back to the default one after
the formatting is applied.</li>
<li data-list-item-id="e49e547cfd4e4cbb45712bace9a6e0979"><strong>To keep painting using the same formatting</strong>: Open the
<li><strong>To keep painting using the same formatting</strong>: Open the
toolbar dropdown and enable the continuous painting mode. Once copied,
the same formatting can be applied multiple times in different places until
the paint formatting button is clicked (the cursor will then revert to
@ -35,11 +39,10 @@
</ul>
<h2>Limitations</h2>
<ol>
<li data-list-item-id="ea3e6a7c317b51341c7a83cee5387ac1e">Painting with block-level formatting (like headings or image styles) is
not supported yet. This is because, in&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/1YeN2MzFUluU/_help_MI26XDLSAlCD">CKEditor</a>,
<li>Painting with block-level formatting (like headings or image styles) is
not supported yet. This is because, in&nbsp;<a class="reference-link" href="#root/_help_MI26XDLSAlCD">CKEditor</a>,
they are considered a part of the content rather than text formatting.</li>
<li
data-list-item-id="edbd74adb8916daaae6024d8b0ae80e32">When applying formatting to words, spaces or other Western punctuation
<li>When applying formatting to words, spaces or other Western punctuation
are used as word boundaries, which prevents proper handling of languages
that do not use space-based word segmentation.</li>
</ol>

View File

@ -3,7 +3,9 @@
"back-in-note-history": "Navega a la nota previa a l'historial",
"forward-in-note-history": "Navega a la següent nota a l'historial",
"dialogs": "Diàlegs",
"other": "Altres"
"other": "Altres",
"open-jump-to-note-dialog": "Obre \"Salta a la nota\"",
"open-command-palette": "Obre el panell de comandes"
},
"login": {
"title": "Inicia sessió",

View File

@ -6,7 +6,7 @@
"open-command-palette": "Open het opdrachtvenster",
"scroll-to-active-note": "Scroll naar actieve notitie in de notitieboom",
"quick-search": "Snelle zoekbalk activeren",
"search-in-subtree": "Zoek naar notities in de subboom van de actieve notitie",
"search-in-subtree": "Zoek naar notities in de subtak van de actieve notitie",
"expand-subtree": "Subboom van huidige notitie uitbreiden",
"collapse-tree": "Vouwt de volledige notitieboom samen",
"collapse-subtree": "Vouwt de subboom van de huidige notitie samen",

View File

@ -40,6 +40,13 @@ interface Subroot {
type GetNoteFunction = (id: string) => SNote | BNote | null;
function addContentAccessQuery(note: SNote | BNote, secondEl?:boolean) {
if (!(note instanceof BNote) && note.contentAccessor && note.contentAccessor?.type === "query") {
return secondEl ? `&cat=${note.contentAccessor.getToken()}` : `?cat=${note.contentAccessor.getToken()}`;
}
return ""
}
function getSharedSubTreeRoot(note: SNote | BNote | undefined): Subroot {
if (!note || note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
// share root itself is not shared
@ -111,7 +118,7 @@ export function renderNoteContent(note: SNote) {
cssToLoad.push(`assets/scripts.css`);
}
for (const cssRelation of note.getRelations("shareCss")) {
cssToLoad.push(`api/notes/${cssRelation.value}/download`);
cssToLoad.push(`api/notes/${cssRelation.value}/download${addContentAccessQuery(note)}`);
}
// Determine JS to load.
@ -119,11 +126,11 @@ export function renderNoteContent(note: SNote) {
"assets/scripts.js"
];
for (const jsRelation of note.getRelations("shareJs")) {
jsToLoad.push(`api/notes/${jsRelation.value}/download`);
jsToLoad.push(`api/notes/${jsRelation.value}/download${addContentAccessQuery(note)}`);
}
const customLogoId = note.getRelation("shareLogo")?.value;
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png` : `../${assetUrlFragment}/images/icon-color.svg`;
const logoUrl = customLogoId ? `api/images/${customLogoId}/image.png${addContentAccessQuery(note)}` : `../${assetUrlFragment}/images/icon-color.svg`;
return renderNoteContentInternal(note, {
subRoot,
@ -133,7 +140,7 @@ export function renderNoteContent(note: SNote) {
logoUrl,
ancestors,
isStatic: false,
faviconUrl: note.hasRelation("shareFavicon") ? `api/notes/${note.getRelationValue("shareFavicon")}/download` : `../favicon.ico`
faviconUrl: note.hasRelation("shareFavicon") ? `api/notes/${note.getRelationValue("shareFavicon")}/download${addContentAccessQuery(note)}` : `../favicon.ico`
});
}
@ -158,6 +165,7 @@ function renderNoteContentInternal(note: SNote | BNote, renderArgs: RenderArgs)
isEmpty,
assetPath: shareAdjustedAssetPath,
assetUrlFragment,
addContentAccessQuery: (second: boolean | undefined) => addContentAccessQuery(note, second),
showLoginInShareTheme,
t,
isDev,
@ -325,7 +333,7 @@ function renderText(result: Result, note: SNote | BNote) {
}
if (href?.startsWith("#")) {
handleAttachmentLink(linkEl, href, getNote, getAttachment);
handleAttachmentLink(linkEl, href, getNote, getAttachment, note);
}
}
@ -349,7 +357,7 @@ function renderText(result: Result, note: SNote | BNote) {
}
}
function handleAttachmentLink(linkEl: HTMLElement, href: string, getNote: GetNoteFunction, getAttachment: (id: string) => BAttachment | SAttachment | null) {
function handleAttachmentLink(linkEl: HTMLElement, href: string, getNote: GetNoteFunction, getAttachment: (id: string) => BAttachment | SAttachment | null, note: SNote | BNote) {
const linkRegExp = /attachmentId=([a-zA-Z0-9_]+)/g;
let attachmentMatch;
if ((attachmentMatch = linkRegExp.exec(href))) {
@ -357,7 +365,7 @@ function handleAttachmentLink(linkEl: HTMLElement, href: string, getNote: GetNot
const attachment = getAttachment(attachmentId);
if (attachment) {
linkEl.setAttribute("href", `api/attachments/${attachmentId}/download`);
linkEl.setAttribute("href", `api/attachments/${attachmentId}/download${addContentAccessQuery(note)}`);
linkEl.classList.add(`attachment-link`);
linkEl.classList.add(`role-${attachment.role}`);
linkEl.childNodes.length = 0;
@ -430,7 +438,7 @@ function renderMermaid(result: Result, note: SNote | BNote) {
}
result.content = `
<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">
<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}${addContentAccessQuery(note, true)}">
<hr>
<details>
<summary>Chart source</summary>
@ -439,14 +447,14 @@ function renderMermaid(result: Result, note: SNote | BNote) {
}
function renderImage(result: Result, note: SNote | BNote) {
result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`;
result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}${addContentAccessQuery(note, true)}">`;
}
function renderFile(note: SNote | BNote, result: Result) {
if (note.mime === "application/pdf") {
result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view"></iframe>`;
result.content = `<iframe class="pdf-view" src="api/notes/${note.noteId}/view${addContentAccessQuery(note)}"></iframe>`;
} else {
result.content = `<button type="button" onclick="location.href='api/notes/${note.noteId}/download'">Download file</button>`;
result.content = `<button type="button" onclick="location.href='api/notes/${note.noteId}/download${addContentAccessQuery(note)}'">Download file</button>`;
}
}

View File

@ -60,6 +60,20 @@ function checkNoteAccess(noteId: string, req: Request, res: Response) {
const header = req.header("Authorization");
if (!header?.startsWith("Basic ")) {
if (req.path.startsWith("/share/api") && note.contentAccessor) {
let contentAccessToken = ""
if (note.contentAccessor.type === "cookie") contentAccessToken += req.cookies["trilium.cat"] || ""
else if (note.contentAccessor.type === "query") contentAccessToken += req.query['cat'] || ""
if (contentAccessToken){
if (note.contentAccessor.isTokenValid(contentAccessToken)){
return note
}
res.status(401).send("Access is expired. Return back and update the page.");
return false;
}
}
return false;
}
@ -124,9 +138,14 @@ function register(router: Router) {
return;
}
if (note.isLabelTruthy("shareExclude")) {
res.status(404);
render404(res);
return;
}
if (!checkNoteAccess(note.noteId, req, res)) {
requestCredentials(res);
return;
}
@ -138,6 +157,10 @@ function register(router: Router) {
return;
}
if (note.contentAccessor && note.contentAccessor.type === "cookie") {
res.cookie('trilium.cat', note.contentAccessor.getToken(), { maxAge: note.contentAccessor.getTokenExpiration() * 1000, httpOnly: true })
}
res.send(renderNoteContent(note));
}
@ -163,6 +186,9 @@ function register(router: Router) {
const { shareId } = req.params;
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
if (note){
note.initContentAccessor()
}
renderNote(note, req, res);
});

View File

@ -0,0 +1,81 @@
import crypto from "crypto";
import SNote from "./snote";
import utils from "../../../services/utils";
const DefaultAccessTimeoutSec = 10 * 60; // 10 minutes
export class ContentAccessor {
note: SNote;
token: string;
timestamp: number;
type: string;
timeout: number;
key: Buffer;
constructor(note: SNote) {
this.note = note;
this.key = crypto.randomBytes(32);
this.token = "";
this.timestamp = 0;
this.timeout = Number(this.note.getAttributeValue("label", "shareAccessTokenTimeout") || DefaultAccessTimeoutSec)
switch (this.note.getAttributeValue("label", "shareContentAccess")) {
case "basic": this.type = "basic"; break
case "query": this.type = "query"; break
default: this.type = "cookie"; break
};
}
__encrypt(text: string) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + encrypted;
}
__decrypt(encryptedText: string) {
try {
const iv = Buffer.from(encryptedText.slice(0, 32), 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, iv);
let decrypted = decipher.update(encryptedText.slice(32), 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch {
return ""
}
}
__compare(originalText: string, encryptedText: string) {
return originalText === this.__decrypt(encryptedText)
}
update() {
if (new Date().getTime() < this.timestamp + this.getTimeout() * 1000) return
this.token = utils.randomString(36);
this.key = crypto.randomBytes(32);
this.timestamp = new Date().getTime();
}
isTokenValid(encToken: string) {
return this.__compare(this.token, encToken) && new Date().getTime() < this.timestamp + this.getTimeout() * 1000;
}
getToken() {
return this.__encrypt(this.token);
}
getTokenExpiration() {
return (this.timestamp + (this.timeout * 1000) - new Date().getTime()) /1000;
}
getTimeout() {
return this.timeout;
}
getContentAccessType() {
return this.type;
}
}

View File

@ -10,6 +10,7 @@ import type SAttribute from "./sattribute.js";
import type SBranch from "./sbranch.js";
import type { SNoteRow } from "./rows.js";
import { NOTE_TYPE_ICONS } from "../../../becca/entities/bnote.js";
import { ContentAccessor } from "./content_accessor.js";
const LABEL = "label";
const RELATION = "relation";
@ -33,6 +34,7 @@ class SNote extends AbstractShacaEntity {
private __inheritableAttributeCache: SAttribute[] | null;
targetRelations: SAttribute[];
attachments: SAttachment[];
contentAccessor: ContentAccessor | undefined;
constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]: SNoteRow) {
super();
@ -59,6 +61,15 @@ class SNote extends AbstractShacaEntity {
this.shaca.notes[this.noteId] = this;
}
initContentAccessor(){
if (!this.contentAccessor && this.getCredentials().length > 0) {
this.contentAccessor = new ContentAccessor(this);
}
if (this.contentAccessor) {
this.contentAccessor.update()
}
}
getParentBranches() {
return this.parentBranches;
}
@ -72,7 +83,7 @@ class SNote extends AbstractShacaEntity {
}
getVisibleChildBranches() {
return this.getChildBranches().filter((branch) => !branch.isHidden && !branch.getNote().isLabelTruthy("shareHiddenFromTree"));
return this.getChildBranches().filter((branch) => !branch.isHidden && !branch.getNote().isLabelTruthy("shareHiddenFromTree") && !branch.getNote().isLabelTruthy("shareExclude"));
}
getParentNotes() {
@ -80,7 +91,7 @@ class SNote extends AbstractShacaEntity {
}
getChildNotes() {
return this.children;
return this.children.filter((note) => !note.isLabelTruthy("shareExclude"));
}
getVisibleChildNotes() {

View File

@ -1 +1,8 @@
{}
{
"get-started": {
"title": "Comença",
"desktop_title": "Descarrega l'aplicació d'escriptori (v{{version}})",
"architecture": "Arquitectura:",
"older_releases": "Veure versions anteriors"
}
}

View File

@ -29,6 +29,13 @@
"revisions_content": "Notities worden periodiek op de achtergrond opgeslagen, en versies kunnen worden gebruikt om wijzigingen te bekijken of om per ongeluk gemaakte aanpassingen ongedaan te maken. Revisies kunnen ook handmatig worden aangemaakt.",
"sync_title": "Synchronisatie",
"sync_content": "Gebruik een zelfgehoste of cloudinstantie om je notities eenvoudig te synchroniseren tussen meerdere apparaten, en om er via een PWA toegang toe te krijgen op je mobiele telefoon.",
"protected_notes_title": "Beveiligde notities"
"protected_notes_title": "Beveiligde notities",
"protected_notes_content": "Bescherm gevoelige persoonlijke informatie door notities te versleutelen en te beschermen achter een met wachtwoord beveiligde sessie.",
"jump_to_title": "Snel zoeken en commando's",
"jump_to_content": "Spring snel naar notities of gebruikersinterfacecommando's binnen de hiërarchie door te zoeken naar de titel, met benaderingslogica om tikfouten en kleine verschillen te omzeilen.",
"search_title": "Krachtige zoekfunctie",
"search_content": "Of zoek naar tekst binnen notities en verfijn de zoekopdracht door in de bovenliggende notitie te filteren of zoekdiepte aan te geven.",
"web_clipper_title": "Web clipper",
"web_clipper_content": "Bewaar webpagina's (of schermafbeeldingen) en plaats deze direct in Trillium door de web clipper browser extensie te gebruiken."
}
}

View File

@ -1,5 +1,5 @@
# Documentation
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/Xtlv3JlumknM/Documentation_image.png" width="205" height="162">
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/KJUp1g3csedB/Documentation_image.png" width="205" height="162">
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.

13
docs/README-ca.md vendored
View File

@ -20,13 +20,14 @@ releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)\
[![Translation
status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
[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)
[Anglès](./README.md) | [Xinès (simplificado)](./docs/README-ZH_CN.md) | [Xinès
(tradicional)](./docs/README-ZH_TW.md) | [Rus](./docs/README-ru.md) |
[Japonès](./docs/README-ja.md) | [Italià](./docs/README-it.md) |
[Castellà](./docs/README-es.md)
Trilium Notes is a free and open-source, cross-platform hierarchical note taking
application with focus on building large personal knowledge bases.
Trillium Notes és una aplicació gratuïta i de codi obert, multiplataforma, per
prendre notes jeràrquiques, amb l'objectiu de crear grans bases de coneixement
personal.
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for
quick overview:

38
docs/README-nl.md vendored
View File

@ -54,12 +54,12 @@ Onze documentatie is beschikbaar in meerdere formaten:
- **GitHub**: Navigeer door de [User Guide](./docs/User%20Guide/User%20Guide/)
in deze repository
### Quick Links
### Snelkoppelingen
- [Getting Started Gids](https://docs.triliumnotes.org/)
- [Vertaal
Instructies](./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)
Installatie](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [TriliumNext
Upgraden](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
- [Basis Concepten en
@ -67,7 +67,7 @@ Onze documentatie is beschikbaar in meerdere formaten:
- [Patronen van Personal Knowledge
Base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
## 🎁 Features
## 🎁 Kenmerken
* Notes kunnen in een diepe boomstructuur geplaatst worden. Een enkele note kan
op meerdere plekken in de boom geplaatst worden. (zie
@ -78,22 +78,22 @@ Onze documentatie is beschikbaar in meerdere formaten:
* Ondersteuning voor bewerken van [notes met source
code](https://triliumnext.github.io/Docs/Wiki/code-notes), inclusief 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
* Snelle en gemakkelijke [navigatie tussen
notities](https://triliumnext.github.io/Docs/Wiki/note-navigation),
uitgebreide text zoekopdrachten en [notities
promoveren](https://triliumnext.github.io/Docs/Wiki/note-hoisting)
* Naadloze [notitie
versiegeschiedenis](https://triliumnext.github.io/Docs/Wiki/note-revisions)
* Notitie[-attributen](https://triliumnext.github.io/Docs/Wiki/attributes)
kunnen worden ingezet voor notitie-organisatie, queries en geavanceerd
[scripten](https://triliumnext.github.io/Docs/Wiki/scripts)
* Gebruikersinterfacevariabele in het Engels, Duits, Spaans, Frans, Roemeens en
Chinees (versimpeld en traditioneel)
* Directe [OpenID en TOTP
integratie](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Server%20Installation/Multi-Factor%20Authentication.md)
voor beter beveiligde aanmelding
* [Synchronisatie](https://triliumnext.github.io/Docs/Wiki/synchronization) met
zelfgehoste synchronisatieserver
* 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

View File

@ -6192,31 +6192,38 @@
{
"type": "relation",
"name": "internalLink",
"value": "CoFPLs3dRlXc",
"value": "5wZallV2Qo1t",
"isInheritable": false,
"position": 180
},
{
"type": "relation",
"name": "internalLink",
"value": "A9Oc6YKKc65v",
"value": "CoFPLs3dRlXc",
"isInheritable": false,
"position": 190
},
{
"type": "relation",
"name": "internalLink",
"value": "QrtTYPmdd1qq",
"value": "A9Oc6YKKc65v",
"isInheritable": false,
"position": 200
},
{
"type": "relation",
"name": "internalLink",
"value": "MI26XDLSAlCD",
"value": "QrtTYPmdd1qq",
"isInheritable": false,
"position": 210
},
{
"type": "relation",
"name": "internalLink",
"value": "MI26XDLSAlCD",
"isInheritable": false,
"position": 220
},
{
"type": "label",
"name": "shareAlias",
@ -6244,13 +6251,6 @@
"value": "",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "5wZallV2Qo1t",
"isInheritable": false,
"position": 220
}
],
"format": "markdown",
@ -6904,10 +6904,17 @@
{
"type": "relation",
"name": "internalLink",
"value": "Oau6X9rCuegd",
"value": "5wZallV2Qo1t",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "Oau6X9rCuegd",
"isInheritable": false,
"position": 50
},
{
"type": "label",
"name": "iconClass",
@ -6921,13 +6928,6 @@
"value": "general-formatting",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "5wZallV2Qo1t",
"isInheritable": false,
"position": 70
}
],
"format": "markdown",
@ -8460,14 +8460,14 @@
"name": "internalLink",
"value": "gLt3vA97tMcp",
"isInheritable": false,
"position": 30
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "MI26XDLSAlCD",
"isInheritable": false,
"position": 40
"position": 20
},
{
"type": "label",

View File

@ -131,7 +131,7 @@ To do so, create a shared text note and apply the `shareIndex` label. When viewe
## Attribute reference
<table class="ck-table-resized"><colgroup><col style="width:18.38%;"><col style="width:81.62%;"></colgroup><thead><tr><th>Attribute</th><th>Description</th></tr></thead><tbody><tr><td><code>#shareHiddenFromTree</code></td><td>this note is hidden from left navigation tree, but still accessible with its URL</td></tr><tr><td><code>#shareExternalLink</code></td><td>note will act as a link to an external website in the share tree</td></tr><tr><td><code>#shareAlias</code></td><td>define an alias using which the note will be available under <code>https://your_trilium_host/share/[your_alias]</code></td></tr><tr><td><code>#shareOmitDefaultCss</code></td><td>default share page CSS will be omitted. Use when you make extensive styling changes.</td></tr><tr><td><code>#shareRoot</code></td><td>marks note which is served on /share root.</td></tr><tr><td><code>#shareDescription</code></td><td>define text to be added to the HTML meta tag for description</td></tr><tr><td><code>#shareRaw</code></td><td>Note will be served in its raw format, without HTML wrapper. See also&nbsp;<a class="reference-link" href="Sharing/Serving%20directly%20the%20content%20o.md">Serving directly the content of a note</a>&nbsp;for an alternative method without setting an attribute.</td></tr><tr><td><code>#shareDisallowRobotIndexing</code></td><td><p>Indicates to web crawlers that the page should not be indexed of this note by:</p><ul><li data-list-item-id="e6baa9f60bf59d085fd31aa2cce07a0e7">Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li><li data-list-item-id="ec0d067db136ef9794e4f1033405880b7">Setting the <code>noindex, follow</code> meta tag.</li></ul></td></tr><tr><td><code>#shareCredentials</code></td><td>require credentials to access this shared note. Value is expected to be in format <code>username:password</code>. Don't forget to make this inheritable to apply to child-notes/images.</td></tr><tr><td><code>#shareIndex</code></td><td>Note with this label will list all roots of shared notes.</td></tr><tr><td><code>#shareHtmlLocation</code></td><td>defines where custom HTML injected via <code>~shareHtml</code> relation should be placed. Applied to the HTML snippet note itself. Format: <code>location:position</code> where location is <code>head</code>, <code>body</code>, or <code>content</code> and position is <code>start</code> or <code>end</code>. Defaults to <code>content:end</code>.</td></tr></tbody></table>
<table class="ck-table-resized"><colgroup><col style="width:18.38%;"><col style="width:81.62%;"></colgroup><thead><tr><th>Attribute</th><th>Description</th></tr></thead><tbody><tr><td><code>#shareHiddenFromTree</code></td><td>this note is hidden from left navigation tree, but still accessible with its URL</td></tr><tr><td><code>#shareTemplateNoPrevNext</code></td><td>hide bottom page navigation prev and next page.</td></tr><tr><td><code>#shareTemplateNoLeftPanel</code></td><td>hide left panel fully.</td></tr><tr><td><code>#shareExclude</code></td><td>this note will be excluded from share, not accessible via direct URL (implemented to hide scripts from share)</td></tr><tr><td><code>#shareContentAccess</code></td><td>method for attachments authorization in case when note protected with login and password (#shareCredentials). Could be cookie (the cookie will be provided when page loads) / query (every url will be updated with token) / basic (only basic header authorization)). By default for browser used cookie.</td></tr><tr><td><code>#shareAccessTokenTimeout</code></td><td>token expiration timeout in seconds, by default 10 minutes. While token not expired user could download attachment, after that he will get message `Access is expired. Return back and update the page.`</td></tr><tr><td><code>#shareExternalLink</code></td><td>note will act as a link to an external website in the share tree</td></tr><tr><td><code>#shareAlias</code></td><td>define an alias using which the note will be available under <code>https://your_trilium_host/share/[your_alias]</code></td></tr><tr><td><code>#shareOmitDefaultCss</code></td><td>default share page CSS will be omitted. Use when you make extensive styling changes.</td></tr><tr><td><code>#shareRoot</code></td><td>marks note which is served on /share root.</td></tr><tr><td><code>#shareDescription</code></td><td>define text to be added to the HTML meta tag for description</td></tr><tr><td><code>#shareRaw</code></td><td>Note will be served in its raw format, without HTML wrapper. See also&nbsp;<a class="reference-link" href="Sharing/Serving%20directly%20the%20content%20o.md">Serving directly the content of a note</a>&nbsp;for an alternative method without setting an attribute.</td></tr><tr><td><code>#shareDisallowRobotIndexing</code></td><td><p>Indicates to web crawlers that the page should not be indexed of this note by:</p><ul><li data-list-item-id="e6baa9f60bf59d085fd31aa2cce07a0e7">Setting the <code>X-Robots-Tag: noindex</code> HTTP header.</li><li data-list-item-id="ec0d067db136ef9794e4f1033405880b7">Setting the <code>noindex, follow</code> meta tag.</li></ul></td></tr><tr><td><code>#shareCredentials</code></td><td>require credentials to access this shared note. Value is expected to be in format <code>username:password</code>. Don't forget to make this inheritable to apply to child-notes/images.</td></tr><tr><td><code>#shareIndex</code></td><td>Note with this label will list all roots of shared notes.</td></tr><tr><td><code>#shareHtmlLocation</code></td><td>defines where custom HTML injected via <code>~shareHtml</code> relation should be placed. Applied to the HTML snippet note itself. Format: <code>location:position</code> where location is <code>head</code>, <code>body</code>, or <code>content</code> and position is <code>start</code> or <code>end</code>. Defaults to <code>content:end</code>.</td></tr></tbody></table>
### Customizing logo

View File

@ -22,3 +22,21 @@ It is possible for each of the splits to have their own <a class="reference-lin
When a new split is created, it will share the same note hoisting as the previous one. An easy solution to this is to simply hoist the notes after the split is created.
This is generally quite useful for reorganizing notes from one place to the other, by hoisting the old place in the first split and hoisting the new place to the second one. This will allow easy cut and paste without the tree jumping around from switching between notes.
## Mobile support
Since v0.100.0, it's possible to have a split view on the mobile view as well, with the following differences from the desktop version of the split:
* On smartphones, the split views are laid out vertically (one on the top and one on the bottom), instead of horizontally as on the desktop.
* There can be only one split open per tab.
* It's not possible to resize the two split panes.
* When the keyboard is opened, the active note will be “maximized”, thus allowing for more space even when a split is open. When the keyboard is closed, the splits become equal in size again.
Interaction:
* To create a new split, click the three dots button on the right of the note title and select _Create new split_.
* This option will only be available if there is no split already open in the current tab.
* To close a split, click the three dots button on the right of the note title and select _Close this pane_.
* Note that this option will only be available on the second note in the split (the one at the bottom on smartphones, the one on the right on tablets).
* When long-pressing a link, a contextual menu will show up with an option to _Open note in a new split_.
* If there's already a split, the option will replace the existing split instead.

View File

@ -63,7 +63,7 @@ For each note of the calendar, the following attributes can be used:
| `#startTime` | The time the event starts at. If this value is missing, then the event is considered a full-day event. The format is `HH:MM` (hours in 24-hour format and minutes). |
| `#endTime` | Similar to `startTime`, it mentions the time at which the event ends (in relation with `endDate` if present, or `startDate`). |
| `#color` | Displays the event with a specified color (named such as `red`, `gray` or hex such as `#FF0000`). This will also change the color of the note in other places such as the note tree. |
| `#calendar:color` | Similar to `#color`, but applies the color only for the event in the calendar and not for other places such as the note tree. (*Deprecated*) |
| `#calendar:color` | Similar to `#color`, but applies the color only for the event in the calendar and not for other places such as the note tree. (_Deprecated_) |
| `#iconClass` | If present, the icon of the note will be displayed to the left of the event title. |
| `#calendar:title` | Changes the title of an event to point to an attribute of the note other than the title, can either a label or a relation (without the `#` or `~` symbol). See _Use-cases_ for more information. |
| `#calendar:displayedAttributes` | Allows displaying the value of one or more attributes in the calendar like this:     <br> <br>![](9_Calendar_image.png)    <br> <br>`#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"`   <br> <br>It can also be used with relations, case in which it will display the title of the target note:    <br> <br>`~assignee=@My assignee #calendar:displayedAttributes="assignee"` |

View File

@ -62,6 +62,16 @@ export default class MathEditing extends Plugin {
allowAttributes: [ 'equation', 'type', 'display', 'fontSize', 'fontColor', 'fontBackgroundColor' ]
} );
// Prevent <mathtex-inline> from being inserted inside <codeBlock>
schema.addChildCheck( ( context, childDefinition ) => {
if ( childDefinition && childDefinition.name === 'mathtex-inline' ) {
// If the context is inside a codeBlock, disallow it
if ( context.endsWith( 'codeBlock' ) ) {
return false;
}
}
});
schema.register( 'mathtex-display', {
inheritAllFrom: '$blockObject',
allowAttributes: [ 'equation', 'type', 'display', 'fontSize', 'fontColor' ]

View File

@ -50,7 +50,7 @@
let openGraphImage = subRoot.note.getLabelValue("shareOpenGraphImage");
// Relation takes priority and requires some altering
if (subRoot.note.hasRelation("shareOpenGraphImage")) {
openGraphImage = `api/images/${subRoot.note.getRelation("shareOpenGraphImage").value}/image.png`;
openGraphImage = `api/images/${subRoot.note.getRelation("shareOpenGraphImage").value}/image.png${addContentAccessQuery()}`;
}
%>
<title><%= pageTitle %></title>
@ -109,6 +109,7 @@ content = content.replaceAll(headingRe, (...match) => {
<button aria-label="Show Mobile Menu" id="show-menu-button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"></path></svg></button>
</div>
<div id="split-pane">
<% if (!note.isLabelTruthy("shareTemplateNoLeftPanel")) { %>
<div id="left-pane">
<div id="navigation">
<div id="site-header">
@ -143,6 +144,8 @@ content = content.replaceAll(headingRe, (...match) => {
<% } %>
</div>
</div>
<% } %>
<div id="right-pane">
<div id="main">
<div id="content" class="type-<%= note.type %><% if (note.type === "text") { %> ck-content<% } %><% if (isEmpty) { %> no-content<% } %>">
@ -152,7 +155,9 @@ content = content.replaceAll(headingRe, (...match) => {
<p>This note has no content.</p>
<% } else { %>
<%
content = content.replace(/<img /g, `<img alt="${t("share_theme.image_alt")}" loading="lazy" `);
content = content
.replace(/<img /g, `<img alt="${t("share_theme.image_alt")}" loading="lazy" `)
.replace(/src="(api\/[^"]+)"/g, (m, url) => `src="${url}${addContentAccessQuery(url.includes('?'))}"`);
%>
<%- content %>
<% } %>
@ -189,7 +194,7 @@ content = content.replaceAll(headingRe, (...match) => {
</div>
<% } %>
<% if (hasTree) { %>
<% if (hasTree && !note.isLabelTruthy("shareTemplateNoPrevNext")) { %>
<%- include("prev_next", { note: note, subRoot: subRoot }) %>
<% } %>
</footer>

89
pnpm-lock.yaml generated
View File

@ -661,17 +661,17 @@ importers:
specifier: 1.0.3
version: 1.0.3
express:
specifier: 5.1.0
version: 5.1.0
specifier: 5.2.0
version: 5.2.0
express-http-proxy:
specifier: 2.1.2
version: 2.1.2
express-openid-connect:
specifier: 2.19.3
version: 2.19.3(express@5.1.0)
version: 2.19.3(express@5.2.0)
express-rate-limit:
specifier: 8.2.1
version: 8.2.1(express@5.1.0)
version: 8.2.1(express@5.2.0)
express-session:
specifier: 1.18.2
version: 1.18.2
@ -6214,8 +6214,8 @@ packages:
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
body-parser@2.2.0:
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
body-parser@2.2.1:
resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==}
engines: {node: '>=18'}
bonjour-service@1.3.0:
@ -8001,8 +8001,8 @@ packages:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'}
express@5.1.0:
resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==}
express@5.2.0:
resolution: {integrity: sha512-XdpJDLxfztVY59X0zPI6sibRiGcxhTPXRD3IhJmjKf2jwMvkRGV1j7loB8U+heeamoU3XvihAaGRTR4aXXUN3A==}
engines: {node: '>= 18'}
exsolve@1.0.5:
@ -8743,6 +8743,10 @@ packages:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
http-errors@2.0.1:
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
engines: {node: '>= 0.8'}
http-parser-js@0.5.10:
resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==}
@ -8824,6 +8828,10 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
iconv-lite@0.7.0:
resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==}
engines: {node: '>=0.10.0'}
icss-utils@5.1.0:
resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==}
engines: {node: ^10 || ^12 || >= 14}
@ -11822,9 +11830,9 @@ packages:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
raw-body@3.0.0:
resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==}
engines: {node: '>= 0.8'}
raw-body@3.0.2:
resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
engines: {node: '>= 0.10'}
raw-loader@0.5.1:
resolution: {integrity: sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q==}
@ -14996,8 +15004,6 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-upload': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-ai@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
@ -15144,8 +15150,6 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies:
@ -15210,6 +15214,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
'@ckeditor/ckeditor5-watchdog': 47.2.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
@ -15336,8 +15342,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-classic@47.2.0':
dependencies:
@ -15347,8 +15351,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-decoupled@47.2.0':
dependencies:
@ -15367,8 +15369,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-multi-root@47.2.0':
dependencies:
@ -15391,8 +15391,6 @@ snapshots:
'@ckeditor/ckeditor5-table': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-emoji@47.2.0':
dependencies:
@ -15475,8 +15473,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-font@47.2.0':
dependencies:
@ -15551,8 +15547,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
'@ckeditor/ckeditor5-widget': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-html-embed@47.2.0':
dependencies:
@ -15879,8 +15873,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-restricted-editing@47.2.0':
dependencies:
@ -15967,8 +15959,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-special-characters@47.2.0':
dependencies:
@ -21164,16 +21154,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
body-parser@2.2.0:
body-parser@2.2.1:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 4.4.3(supports-color@8.1.1)
http-errors: 2.0.0
iconv-lite: 0.6.3
iconv-lite: 0.7.0
on-finished: 2.4.1
qs: 6.14.0
raw-body: 3.0.0
raw-body: 3.0.2
type-is: 2.0.1
transitivePeerDependencies:
- supports-color
@ -23541,13 +23531,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
express-openid-connect@2.19.3(express@5.1.0):
express-openid-connect@2.19.3(express@5.2.0):
dependencies:
base64url: 3.0.1
clone: 2.1.2
cookie: 0.7.2
debug: 4.4.3(supports-color@8.1.1)
express: 5.1.0
express: 5.2.0
futoin-hkdf: 1.5.3
http-errors: 1.8.1
joi: 17.13.3
@ -23559,9 +23549,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
express-rate-limit@8.2.1(express@5.1.0):
express-rate-limit@8.2.1(express@5.2.0):
dependencies:
express: 5.1.0
express: 5.2.0
ip-address: 10.0.1
express-session@1.18.2:
@ -23613,15 +23603,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
express@5.1.0:
express@5.2.0:
dependencies:
accepts: 2.0.0
body-parser: 2.2.0
body-parser: 2.2.1
content-disposition: 1.0.0
content-type: 1.0.5
cookie: 0.7.2
cookie-signature: 1.2.2
debug: 4.4.3(supports-color@8.1.1)
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
@ -24567,6 +24558,14 @@ snapshots:
statuses: 2.0.1
toidentifier: 1.0.1
http-errors@2.0.1:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.2
toidentifier: 1.0.1
http-parser-js@0.5.10: {}
http-proxy-agent@4.0.1:
@ -24682,6 +24681,10 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
iconv-lite@0.7.0:
dependencies:
safer-buffer: 2.1.2
icss-utils@5.1.0(postcss@8.5.6):
dependencies:
postcss: 8.5.6
@ -28032,11 +28035,11 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
raw-body@3.0.0:
raw-body@3.0.2:
dependencies:
bytes: 3.1.2
http-errors: 2.0.0
iconv-lite: 0.6.3
http-errors: 2.0.1
iconv-lite: 0.7.0
unpipe: 1.0.0
raw-loader@0.5.1: {}