mirror of
https://github.com/zadam/trilium.git
synced 2025-10-27 09:39:00 +01:00
Merge branch 'develop' into feat/note-edit-readonly-fix2
This commit is contained in:
commit
c28edb674c
@ -8,6 +8,9 @@ indent_style = space
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.sh]
|
||||||
|
end_of_line = lf
|
||||||
|
|
||||||
[{server,translation}.json]
|
[{server,translation}.json]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
|
|||||||
12
.gitattributes
vendored
12
.gitattributes
vendored
@ -1,17 +1,21 @@
|
|||||||
|
# Mark files as auto-generated to simplify reviews.
|
||||||
package-lock.json linguist-generated=true
|
package-lock.json linguist-generated=true
|
||||||
**/package-lock.json linguist-generated=true
|
**/package-lock.json linguist-generated=true
|
||||||
|
apps/server/src/assets/doc_notes/en/User[[:space:]]Guide/** linguist-generated
|
||||||
|
|
||||||
apps/server/src/assets/doc_notes/en/User[[:space:]]Guide/** linguist-generated=true
|
# Ignore from GitHub language stats.
|
||||||
apps/server/src/assets/doc_notes/en/User[[:space:]]Guide/**/*.html eol=lf
|
apps/server/src/assets/doc_notes/en/User[[:space:]]Guide/**/*.html eol=lf
|
||||||
|
apps/server/src/assets/doc_notes/** linguist-vendored=true
|
||||||
|
apps/edit-docs/demo/** linguist-vendored=true
|
||||||
|
docs/** linguist-vendored=true
|
||||||
|
|
||||||
|
# Normalize line endings.
|
||||||
docs/**/*.md eol=lf
|
docs/**/*.md eol=lf
|
||||||
docs/**/*.json eol=lf
|
docs/**/*.json eol=lf
|
||||||
|
|
||||||
demo/**/*.html eol=lf
|
demo/**/*.html eol=lf
|
||||||
demo/**/*.json eol=lf
|
demo/**/*.json eol=lf
|
||||||
demo/**/*.svg eol=lf
|
demo/**/*.svg eol=lf
|
||||||
demo/**/*.txt eol=lf
|
demo/**/*.txt eol=lf
|
||||||
demo/**/*.js eol=lf
|
demo/**/*.js eol=lf
|
||||||
demo/**/*.css eol=lf
|
demo/**/*.css eol=lf
|
||||||
|
*.sh eol=lf
|
||||||
apps/client/src/libraries/** linguist-vendored
|
|
||||||
|
|||||||
4
.github/workflows/dev.yml
vendored
4
.github/workflows/dev.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: nrwl/nx-set-shas@v4
|
- uses: nrwl/nx-set-shas@v4
|
||||||
- name: Check affected
|
- name: Check affected
|
||||||
run: pnpm nx affected -t build rebuild-deps
|
run: pnpm nx affected --verbose -t typecheck build rebuild-deps
|
||||||
|
|
||||||
report-electron-size:
|
report-electron-size:
|
||||||
name: Report Electron size
|
name: Report Electron size
|
||||||
@ -128,7 +128,7 @@ jobs:
|
|||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run the unit tests
|
- name: Run the unit tests
|
||||||
run: pnpm run test
|
run: pnpm run test:all
|
||||||
|
|
||||||
build_docker:
|
build_docker:
|
||||||
name: Build Docker image
|
name: Build Docker image
|
||||||
|
|||||||
14
.github/workflows/main-docker.yml
vendored
14
.github/workflows/main-docker.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
|||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: npx playwright install --with-deps
|
run: pnpx playwright install --with-deps
|
||||||
|
|
||||||
- name: Run the TypeScript build
|
- name: Run the TypeScript build
|
||||||
run: pnpm run server:build
|
run: pnpm run server:build
|
||||||
@ -62,7 +62,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: apps/server
|
context: apps/server
|
||||||
file: ${{ matrix.dockerfile }}
|
file: apps/server/${{ matrix.dockerfile }}
|
||||||
load: true
|
load: true
|
||||||
tags: ${{ env.TEST_TAG }}
|
tags: ${{ env.TEST_TAG }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
@ -70,7 +70,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Validate container run output
|
- name: Validate container run output
|
||||||
run: |
|
run: |
|
||||||
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }})
|
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./apps/server/spec/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }})
|
||||||
echo "Container ID: $CONTAINER_ID"
|
echo "Container ID: $CONTAINER_ID"
|
||||||
|
|
||||||
- name: Wait for the healthchecks to pass
|
- name: Wait for the healthchecks to pass
|
||||||
@ -82,7 +82,7 @@ jobs:
|
|||||||
require-healthy: true
|
require-healthy: true
|
||||||
|
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
run: TRILIUM_DOCKER=1 npx playwright test
|
run: TRILIUM_DOCKER=1 TRILIUM_PORT=8082 pnpx nx run server-e2e:e2e
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
with:
|
with:
|
||||||
@ -129,7 +129,6 @@ jobs:
|
|||||||
- name: Set TEST_TAG to lowercase
|
- name: Set TEST_TAG to lowercase
|
||||||
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
@ -142,6 +141,9 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run the TypeScript build
|
||||||
|
run: pnpm run server:build
|
||||||
|
|
||||||
- name: Update build info
|
- name: Update build info
|
||||||
run: pnpm run chore:update-build-info
|
run: pnpm run chore:update-build-info
|
||||||
|
|
||||||
@ -184,7 +186,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: apps/server
|
context: apps/server
|
||||||
file: ${{ matrix.dockerfile }}
|
file: apps/server/${{ matrix.dockerfile }}
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|||||||
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@ -33,11 +33,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
- run: npx playwright install --with-deps
|
- run: pnpx playwright install --with-deps
|
||||||
- uses: nrwl/nx-set-shas@v4
|
- uses: nrwl/nx-set-shas@v4
|
||||||
|
|
||||||
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
|
# Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud
|
||||||
# - run: npx nx-cloud record -- echo Hello World
|
# - run: npx nx-cloud record -- echo Hello World
|
||||||
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
|
# Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected
|
||||||
# When you enable task distribution, run the e2e-ci task instead of e2e
|
# When you enable task distribution, run the e2e-ci task instead of e2e
|
||||||
- run: npx nx affected -t e2e
|
- run: pnpx nx affected -t e2e
|
||||||
|
|||||||
@ -1,2 +1,7 @@
|
|||||||
_regroup
|
_regroup
|
||||||
_regroup_monorepo
|
_regroup_monorepo
|
||||||
|
|
||||||
|
# Asset copying respects .gitignore / .nxignore for some reason.
|
||||||
|
# See https://github.com/nrwl/nx/issues/20309
|
||||||
|
!dist
|
||||||
|
!node_modules
|
||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -24,5 +24,9 @@
|
|||||||
},
|
},
|
||||||
"github-actions.workflows.pinned.workflows": [
|
"github-actions.workflows.pinned.workflows": [
|
||||||
".github/workflows/nightly.yml"
|
".github/workflows/nightly.yml"
|
||||||
]
|
],
|
||||||
|
"typescript.validate.enable": true,
|
||||||
|
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||||
}
|
}
|
||||||
141
README.md
141
README.md
@ -4,15 +4,47 @@
|
|||||||
|
|
||||||
[English](./README.md) | [Chinese](./docs/README-ZH_CN.md) | [Russian](./docs/README.ru.md) | [Japanese](./docs/README.ja.md) | [Italian](./docs/README.it.md) | [Spanish](./docs/README.es.md)
|
[English](./README.md) | [Chinese](./docs/README-ZH_CN.md) | [Russian](./docs/README.ru.md) | [Japanese](./docs/README.ja.md) | [Italian](./docs/README.it.md) | [Spanish](./docs/README.es.md)
|
||||||
|
|
||||||
TriliumNext Notes is an open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
|
TriliumNext Notes is a free and open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
|
||||||
|
|
||||||
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for quick overview:
|
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for quick overview:
|
||||||
|
|
||||||
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
|
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
|
||||||
|
|
||||||
|
## 🎁 Features
|
||||||
|
|
||||||
|
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes))
|
||||||
|
* Rich WYSIWYG note editor including e.g. tables, images and [math](https://triliumnext.github.io/Docs/Wiki/text-notes) with markdown [autoformat](https://triliumnext.github.io/Docs/Wiki/text-notes#autoformat)
|
||||||
|
* Support for editing [notes with source code](https://triliumnext.github.io/Docs/Wiki/code-notes), including syntax highlighting
|
||||||
|
* Fast and easy [navigation between notes](https://triliumnext.github.io/Docs/Wiki/note-navigation), full text search and [note hoisting](https://triliumnext.github.io/Docs/Wiki/note-hoisting)
|
||||||
|
* Seamless [note versioning](https://triliumnext.github.io/Docs/Wiki/note-revisions)
|
||||||
|
* Note [attributes](https://triliumnext.github.io/Docs/Wiki/attributes) can be used for note organization, querying and advanced [scripting](https://triliumnext.github.io/Docs/Wiki/scripts)
|
||||||
|
* UI available in English, German, Spanish, French, Romanian, and Chinese (simplified and traditional)
|
||||||
|
* Direct [OpenID and TOTP integration](.docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Server%20Installation/Multi-Factor%20Authentication.md") for more secure login
|
||||||
|
* [Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization) with self-hosted sync server
|
||||||
|
* there's a [3rd party service for hosting synchronisation server](https://trilium.cc/paid-hosting)
|
||||||
|
* [Sharing](https://triliumnext.github.io/Docs/Wiki/sharing) (publishing) notes to public internet
|
||||||
|
* Strong [note encryption](https://triliumnext.github.io/Docs/Wiki/protected-notes) with per-note granularity
|
||||||
|
* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type "canvas")
|
||||||
|
* [Relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/link-map) for visualizing notes and their relations
|
||||||
|
* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/)
|
||||||
|
* [Geo maps](./docs/User%20Guide/User%20Guide/Note%20Types/Geo%20Map.md) with location pins and GPX tracks
|
||||||
|
* [Scripting](https://triliumnext.github.io/Docs/Wiki/scripts) - see [Advanced showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases)
|
||||||
|
* [REST API](https://triliumnext.github.io/Docs/Wiki/etapi) for automation
|
||||||
|
* Scales well in both usability and performance upwards of 100 000 notes
|
||||||
|
* Touch optimized [mobile frontend](https://triliumnext.github.io/Docs/Wiki/mobile-frontend) for smartphones and tablets
|
||||||
|
* Built-in [dark theme](https://triliumnext.github.io/Docs/Wiki/themes), support for user themes
|
||||||
|
* [Evernote](https://triliumnext.github.io/Docs/Wiki/evernote-import) and [Markdown import & export](https://triliumnext.github.io/Docs/Wiki/markdown)
|
||||||
|
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/web-clipper) for easy saving of web content
|
||||||
|
* Customizable UI (sidebar buttons, user-defined widgets, ...)
|
||||||
|
|
||||||
|
✨ Check out the following third-party resources/communities for more TriliumNext related goodies:
|
||||||
|
|
||||||
|
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||||
|
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
||||||
|
|
||||||
## ⚠️ Why TriliumNext?
|
## ⚠️ Why TriliumNext?
|
||||||
|
|
||||||
[The original Trilium project is in maintenance mode](https://github.com/zadam/trilium/issues/4620)
|
[The original Trilium project is in maintenance mode](https://github.com/zadam/trilium/issues/4620).
|
||||||
|
|
||||||
### Migrating from Trilium?
|
### Migrating from Trilium?
|
||||||
|
|
||||||
@ -20,53 +52,49 @@ There are no special migration steps to migrate from a zadam/Trilium instance to
|
|||||||
|
|
||||||
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Notes/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext have their sync versions incremented.
|
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Notes/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext have their sync versions incremented.
|
||||||
|
|
||||||
|
## 📖 Documentation
|
||||||
|
|
||||||
|
We're currently in the progress of moving the documentation to in-app (hit the `F1` key within Trilium). As a result, there may be some missing parts until we've completed the migration. If you'd prefer to navigate through the documentation within GitHub, you can navigate the [User Guide](./docs/User%20Guide/User%20Guide/) documentation.
|
||||||
|
|
||||||
|
Below are some quick links for your convenience to navigate the documentation:
|
||||||
|
- [Server installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
|
||||||
|
- [Docker installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
|
||||||
|
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
|
||||||
|
- [Concepts and Features - Note](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
|
||||||
|
- [Patterns of personal knowledge base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
|
||||||
|
|
||||||
|
Until we finish reorganizing the documentation, you may also want to [browse the old documentation](https://triliumnext.github.io/Docs).
|
||||||
|
|
||||||
## 💬 Discuss with us
|
## 💬 Discuss with us
|
||||||
|
|
||||||
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!
|
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!
|
||||||
|
|
||||||
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions)
|
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions.)
|
||||||
- The `General` Matrix room is also bridged to [XMPP](xmpp:discuss@trilium.thisgreat.party?join)
|
- The `General` Matrix room is also bridged to [XMPP](xmpp:discuss@trilium.thisgreat.party?join)
|
||||||
- [Github Discussions](https://github.com/TriliumNext/Notes/discussions) (For Asynchronous discussions)
|
- [Github Discussions](https://github.com/TriliumNext/Notes/discussions) (For asynchronous discussions.)
|
||||||
- [Wiki](https://triliumnext.github.io/Docs/) (For common how-to questions and user guides)
|
- [Github Issues](https://github.com/TriliumNext/Notes/issues) (For bug reports and feature requests.)
|
||||||
|
|
||||||
## 🎁 Features
|
|
||||||
|
|
||||||
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes))
|
|
||||||
* Rich WYSIWYG note editing including e.g. tables, images and [math](https://triliumnext.github.io/Docs/Wiki/text-notes) with markdown [autoformat](https://triliumnext.github.io/Docs/Wiki/text-notes#autoformat)
|
|
||||||
* Support for editing [notes with source code](https://triliumnext.github.io/Docs/Wiki/code-notes), including syntax highlighting
|
|
||||||
* Fast and easy [navigation between notes](https://triliumnext.github.io/Docs/Wiki/note-navigation), full text search and [note hoisting](https://triliumnext.github.io/Docs/Wiki/note-hoisting)
|
|
||||||
* Seamless [note versioning](https://triliumnext.github.io/Docs/Wiki/note-revisions)
|
|
||||||
* Note [attributes](https://triliumnext.github.io/Docs/Wiki/attributes) can be used for note organization, querying and advanced [scripting](https://triliumnext.github.io/Docs/Wiki/scripts)
|
|
||||||
* Direct OpenID and TOTP integration for more secure login
|
|
||||||
* [Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization) with self-hosted sync server
|
|
||||||
* there's a [3rd party service for hosting synchronisation server](https://trilium.cc/paid-hosting)
|
|
||||||
* [Sharing](https://triliumnext.github.io/Docs/Wiki/sharing) (publishing) notes to public internet
|
|
||||||
* Strong [note encryption](https://triliumnext.github.io/Docs/Wiki/protected-notes) with per-note granularity
|
|
||||||
* Sketching diagrams with built-in Excalidraw (note type "canvas")
|
|
||||||
* [Relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/link-map) for visualizing notes and their relations
|
|
||||||
* [Scripting](https://triliumnext.github.io/Docs/Wiki/scripts) - see [Advanced showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases)
|
|
||||||
* [REST API](https://triliumnext.github.io/Docs/Wiki/etapi) for automation
|
|
||||||
* Scales well in both usability and performance upwards of 100 000 notes
|
|
||||||
* Touch optimized [mobile frontend](https://triliumnext.github.io/Docs/Wiki/mobile-frontend) for smartphones and tablets
|
|
||||||
* [Night theme](https://triliumnext.github.io/Docs/Wiki/themes)
|
|
||||||
* [Evernote](https://triliumnext.github.io/Docs/Wiki/evernote-import) and [Markdown import & export](https://triliumnext.github.io/Docs/Wiki/markdown)
|
|
||||||
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/web-clipper) for easy saving of web content
|
|
||||||
|
|
||||||
✨ Check out the following third-party resources/communities for more TriliumNext related goodies:
|
|
||||||
|
|
||||||
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
|
||||||
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
|
||||||
|
|
||||||
## 🏗 Installation
|
## 🏗 Installation
|
||||||
|
|
||||||
### Desktop
|
### Windows / MacOS
|
||||||
|
|
||||||
To use TriliumNext on your desktop machine (Linux, MacOS, and Windows) you have a few options:
|
Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the `trilium` executable.
|
||||||
|
|
||||||
* Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the ```trilium``` executable.
|
### Linux
|
||||||
* Access TriliumNext via the web interface of a server installation (see below)
|
|
||||||
* Currently only the latest versions of Chrome & Firefox are supported (and tested).
|
If your distribution is listed in the table below, use your distribution's package.
|
||||||
* TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
|
|
||||||
|
[](https://repology.org/project/triliumnext/versions)
|
||||||
|
|
||||||
|
You may also download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the `trilium` executable.
|
||||||
|
|
||||||
|
TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
|
||||||
|
|
||||||
|
### Browser (any OS)
|
||||||
|
|
||||||
|
If you use a server installation (see below), you can directly access the web interface (which is almost identical to the desktop app).
|
||||||
|
|
||||||
|
Currently only the latest versions of Chrome & Firefox are supported (and tested).
|
||||||
|
|
||||||
### Mobile
|
### Mobile
|
||||||
|
|
||||||
@ -80,28 +108,43 @@ See issue https://github.com/TriliumNext/Notes/issues/72 for more information on
|
|||||||
|
|
||||||
To install TriliumNext on your own server (including via Docker from [Dockerhub](https://hub.docker.com/r/triliumnext/notes)) follow [the server installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
|
To install TriliumNext on your own server (including via Docker from [Dockerhub](https://hub.docker.com/r/triliumnext/notes)) follow [the server installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
|
||||||
|
|
||||||
## 📝 Documentation
|
|
||||||
|
|
||||||
[See wiki for complete list of documentation pages.](https://triliumnext.github.io/Docs)
|
|
||||||
|
|
||||||
You can also read [Patterns of personal knowledge base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge) to get some inspiration on how you might use TriliumNext.
|
|
||||||
|
|
||||||
## 💻 Contribute
|
## 💻 Contribute
|
||||||
|
|
||||||
### Code
|
### Code
|
||||||
|
|
||||||
|
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/TriliumNext/Notes.git
|
git clone https://github.com/TriliumNext/Notes.git
|
||||||
cd Notes
|
cd Notes
|
||||||
npm install
|
pnpm install
|
||||||
npm run server:start
|
pnpm run server:start
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/TriliumNext/Notes.git
|
||||||
|
cd Notes
|
||||||
|
pnpm install
|
||||||
|
pnpm nx run edit-docs:edit-docs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building the Executable
|
||||||
|
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/TriliumNext/Notes.git
|
||||||
|
cd Notes
|
||||||
|
pnpm install
|
||||||
|
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
|
||||||
```
|
```
|
||||||
|
|
||||||
For more details, see the [development docs](https://github.com/TriliumNext/Notes/blob/develop/docs/Developer%20Guide/Developer%20Guide/Building%20and%20deployment/Running%20a%20development%20build.md).
|
For more details, see the [development docs](https://github.com/TriliumNext/Notes/blob/develop/docs/Developer%20Guide/Developer%20Guide/Building%20and%20deployment/Running%20a%20development%20build.md).
|
||||||
|
|
||||||
### Documentation
|
### Developer Documentation
|
||||||
|
|
||||||
See the [documentation guide](https://github.com/TriliumNext/Notes/blob/develop/docs/Developer%20Guide/Developer%20Guide/Documentation.md) for details.
|
Please view the [documentation guide](./docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md) for details. If you have more questions, feel free to reach out via the links described in the "Discuss with us" section above.
|
||||||
|
|
||||||
## 👏 Shoutouts
|
## 👏 Shoutouts
|
||||||
|
|
||||||
@ -119,4 +162,6 @@ Support for the TriliumNext organization will be possible in the near future. Fo
|
|||||||
|
|
||||||
## 🔑 License
|
## 🔑 License
|
||||||
|
|
||||||
|
Copyright 2017-2025 zadam, Elian Doran, and other contributors
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|||||||
@ -1,548 +0,0 @@
|
|||||||
/*
|
|
||||||
* CKEditor 5 (v41.0.0) content styles.
|
|
||||||
* Generated on Fri, 26 Jan 2024 10:23:49 GMT.
|
|
||||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--ck-color-image-caption-background: hsl(0, 0%, 97%);
|
|
||||||
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
|
||||||
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
|
|
||||||
--ck-color-mention-text: hsl(341, 100%, 30%);
|
|
||||||
--ck-color-selector-caption-background: hsl(0, 0%, 97%);
|
|
||||||
--ck-color-selector-caption-text: hsl(0, 0%, 20%);
|
|
||||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
|
||||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
|
||||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
|
||||||
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
|
|
||||||
--ck-highlight-pen-green: hsl(112, 100%, 27%);
|
|
||||||
--ck-highlight-pen-red: hsl(0, 85%, 49%);
|
|
||||||
--ck-image-style-spacing: 1.5em;
|
|
||||||
--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);
|
|
||||||
--ck-todo-list-checkmark-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
|
||||||
.ck-content .table .ck-table-resized {
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
|
||||||
.ck-content .table table {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
|
||||||
.ck-content .table td,
|
|
||||||
.ck-content .table th {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table {
|
|
||||||
margin: 0.9em auto;
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 1px double hsl(0, 0%, 70%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table table td,
|
|
||||||
.ck-content .table table th {
|
|
||||||
min-width: 2em;
|
|
||||||
padding: .4em;
|
|
||||||
border: 1px solid hsl(0, 0%, 75%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table table th {
|
|
||||||
font-weight: bold;
|
|
||||||
background: hsla(0, 0%, 0%, 5%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content[dir="rtl"] .table th {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content[dir="ltr"] .table th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
|
||||||
.ck-content .table > figcaption {
|
|
||||||
display: table-caption;
|
|
||||||
caption-side: top;
|
|
||||||
word-break: break-word;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--ck-color-selector-caption-text);
|
|
||||||
background-color: var(--ck-color-selector-caption-background);
|
|
||||||
padding: .6em;
|
|
||||||
font-size: .75em;
|
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break {
|
|
||||||
position: relative;
|
|
||||||
clear: both;
|
|
||||||
padding: 5px 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break__label {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
padding: .3em .6em;
|
|
||||||
display: block;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border: 1px solid hsl(0, 0%, 77%);
|
|
||||||
border-radius: 2px;
|
|
||||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
|
||||||
font-size: 0.75em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: hsl(0, 0%, 20%);
|
|
||||||
background: hsl(0, 0%, 100%);
|
|
||||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
|
|
||||||
.ck-content .media {
|
|
||||||
clear: both;
|
|
||||||
margin: 0.9em 0;
|
|
||||||
display: block;
|
|
||||||
min-width: 15em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list li {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list li .todo-list {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: var(--ck-todo-list-checkmark-size);
|
|
||||||
height: var(--ck-todo-list-checkmark-size);
|
|
||||||
vertical-align: middle;
|
|
||||||
border: 0;
|
|
||||||
left: -25px;
|
|
||||||
margin-right: -15px;
|
|
||||||
right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content[dir=rtl] .todo-list .todo-list__label > input {
|
|
||||||
left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
right: -25px;
|
|
||||||
margin-left: -15px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input::before {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: border-box;
|
|
||||||
content: '';
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 1px solid hsl(0, 0%, 20%);
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: 250ms ease-in-out box-shadow;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input::after {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: content-box;
|
|
||||||
pointer-events: none;
|
|
||||||
content: '';
|
|
||||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
|
||||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent;
|
|
||||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input[checked]::before {
|
|
||||||
background: hsl(126, 64%, 41%);
|
|
||||||
border-color: hsl(126, 64%, 41%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input[checked]::after {
|
|
||||||
border-color: hsl(0, 0%, 100%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label .todo-list__label__description {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input,
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before {
|
|
||||||
box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: var(--ck-todo-list-checkmark-size);
|
|
||||||
height: var(--ck-todo-list-checkmark-size);
|
|
||||||
vertical-align: middle;
|
|
||||||
border: 0;
|
|
||||||
left: -25px;
|
|
||||||
margin-right: -15px;
|
|
||||||
right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input {
|
|
||||||
left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
right: -25px;
|
|
||||||
margin-left: -15px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: border-box;
|
|
||||||
content: '';
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 1px solid hsl(0, 0%, 20%);
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: 250ms ease-in-out box-shadow;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: content-box;
|
|
||||||
pointer-events: none;
|
|
||||||
content: '';
|
|
||||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
|
||||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent;
|
|
||||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before {
|
|
||||||
background: hsl(126, 64%, 41%);
|
|
||||||
border-color: hsl(126, 64%, 41%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after {
|
|
||||||
border-color: hsl(0, 0%, 100%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol {
|
|
||||||
list-style-type: decimal;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol {
|
|
||||||
list-style-type: lower-latin;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol ol {
|
|
||||||
list-style-type: lower-roman;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol ol ol {
|
|
||||||
list-style-type: upper-latin;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol ol ol ol {
|
|
||||||
list-style-type: upper-roman;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul ul {
|
|
||||||
list-style-type: circle;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul ul ul {
|
|
||||||
list-style-type: square;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul ul ul ul {
|
|
||||||
list-style-type: square;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image {
|
|
||||||
display: table;
|
|
||||||
clear: both;
|
|
||||||
text-align: center;
|
|
||||||
margin: 0.9em auto;
|
|
||||||
min-width: 50px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image img {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image-inline {
|
|
||||||
/*
|
|
||||||
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
|
|
||||||
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
|
|
||||||
* This strange behavior does not happen with inline-flex.
|
|
||||||
*/
|
|
||||||
display: inline-flex;
|
|
||||||
max-width: 100%;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image-inline picture {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image-inline picture,
|
|
||||||
.ck-content .image-inline img {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content img.image_resized {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content .image.image_resized {
|
|
||||||
max-width: 100%;
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content .image.image_resized img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content .image.image_resized > figcaption {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
|
|
||||||
.ck-content .image > figcaption {
|
|
||||||
display: table-caption;
|
|
||||||
caption-side: bottom;
|
|
||||||
word-break: break-word;
|
|
||||||
color: var(--ck-color-image-caption-text);
|
|
||||||
background-color: var(--ck-color-image-caption-background);
|
|
||||||
padding: .6em;
|
|
||||||
font-size: .75em;
|
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-block-align-left,
|
|
||||||
.ck-content .image-style-block-align-right {
|
|
||||||
max-width: calc(100% - var(--ck-image-style-spacing));
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-left,
|
|
||||||
.ck-content .image-style-align-right {
|
|
||||||
clear: none;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-side {
|
|
||||||
float: right;
|
|
||||||
margin-left: var(--ck-image-style-spacing);
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-left {
|
|
||||||
float: left;
|
|
||||||
margin-right: var(--ck-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-center {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-right {
|
|
||||||
float: right;
|
|
||||||
margin-left: var(--ck-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-block-align-right {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-block-align-left {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content p + .image-style-align-left,
|
|
||||||
.ck-content p + .image-style-align-right,
|
|
||||||
.ck-content p + .image-style-side {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-inline.image-style-align-left,
|
|
||||||
.ck-content .image-inline.image-style-align-right {
|
|
||||||
margin-top: var(--ck-inline-image-style-spacing);
|
|
||||||
margin-bottom: var(--ck-inline-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-inline.image-style-align-left {
|
|
||||||
margin-right: var(--ck-inline-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-inline.image-style-align-right {
|
|
||||||
margin-left: var(--ck-inline-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-yellow {
|
|
||||||
background-color: var(--ck-highlight-marker-yellow);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-green {
|
|
||||||
background-color: var(--ck-highlight-marker-green);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-pink {
|
|
||||||
background-color: var(--ck-highlight-marker-pink);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-blue {
|
|
||||||
background-color: var(--ck-highlight-marker-blue);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .pen-red {
|
|
||||||
color: var(--ck-highlight-pen-red);
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .pen-green {
|
|
||||||
color: var(--ck-highlight-pen-green);
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
|
||||||
.ck-content blockquote {
|
|
||||||
overflow: hidden;
|
|
||||||
padding-right: 1.5em;
|
|
||||||
padding-left: 1.5em;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
font-style: italic;
|
|
||||||
border-left: solid 5px hsl(0, 0%, 80%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
|
||||||
.ck-content[dir="rtl"] blockquote {
|
|
||||||
border-left: 0;
|
|
||||||
border-right: solid 5px hsl(0, 0%, 80%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
|
|
||||||
.ck-content code {
|
|
||||||
background-color: hsla(0, 0%, 78%, 0.3);
|
|
||||||
padding: .15em;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-tiny {
|
|
||||||
font-size: .7em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-small {
|
|
||||||
font-size: .85em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-big {
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-huge {
|
|
||||||
font-size: 1.8em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-mention/theme/mention.css */
|
|
||||||
.ck-content .mention {
|
|
||||||
background: var(--ck-color-mention-background);
|
|
||||||
color: var(--ck-color-mention-text);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
|
|
||||||
.ck-content hr {
|
|
||||||
margin: 15px 0;
|
|
||||||
height: 4px;
|
|
||||||
background: hsl(0, 0%, 87%);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
|
||||||
.ck-content pre {
|
|
||||||
padding: 1em;
|
|
||||||
text-align: left;
|
|
||||||
direction: ltr;
|
|
||||||
tab-size: 4;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-style: normal;
|
|
||||||
min-width: 200px;
|
|
||||||
border: 0px;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
.ck-content pre:not(.hljs) {
|
|
||||||
color: hsl(0, 0%, 20.8%);
|
|
||||||
background: hsla(0, 0%, 78%, 0.3);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
|
||||||
.ck-content pre code {
|
|
||||||
background: unset;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,593 +0,0 @@
|
|||||||
/* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */
|
|
||||||
|
|
||||||
.printed-content .ck-widget__selection-handle, .printed-content .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-break {
|
|
||||||
page-break-after: always;
|
|
||||||
}
|
|
||||||
|
|
||||||
.printed-content .page-break:after,
|
|
||||||
.printed-content .page-break > * {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck-content li p {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admonition {
|
|
||||||
--accent-color: var(--card-border-color);
|
|
||||||
border: 1px solid var(--accent-color);
|
|
||||||
box-shadow: var(--card-box-shadow);
|
|
||||||
background: var(--card-background-color);
|
|
||||||
border-radius: 0.5em;
|
|
||||||
padding: 1em;
|
|
||||||
margin: 1.25em 0;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admonition p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admonition p, h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admonition.note { --accent-color: #69c7ff; }
|
|
||||||
.admonition.tip { --accent-color: #40c025; }
|
|
||||||
.admonition.important { --accent-color: #9839f7; }
|
|
||||||
.admonition.caution { --accent-color: #ff2e2e; }
|
|
||||||
.admonition.warning { --accent-color: #e2aa03; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* CKEditor 5 (v41.0.0) content styles.
|
|
||||||
* Generated on Fri, 26 Jan 2024 10:23:49 GMT.
|
|
||||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--ck-color-image-caption-background: hsl(0, 0%, 97%);
|
|
||||||
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
|
||||||
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
|
|
||||||
--ck-color-mention-text: hsl(341, 100%, 30%);
|
|
||||||
--ck-color-selector-caption-background: hsl(0, 0%, 97%);
|
|
||||||
--ck-color-selector-caption-text: hsl(0, 0%, 20%);
|
|
||||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
|
||||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
|
||||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
|
||||||
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
|
|
||||||
--ck-highlight-pen-green: hsl(112, 100%, 27%);
|
|
||||||
--ck-highlight-pen-red: hsl(0, 85%, 49%);
|
|
||||||
--ck-image-style-spacing: 1.5em;
|
|
||||||
--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);
|
|
||||||
--ck-todo-list-checkmark-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
|
||||||
.ck-content .table .ck-table-resized {
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
|
||||||
.ck-content .table table {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
|
|
||||||
.ck-content .table td,
|
|
||||||
.ck-content .table th {
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table {
|
|
||||||
margin: 0.9em auto;
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 1px double hsl(0, 0%, 70%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table table td,
|
|
||||||
.ck-content .table table th {
|
|
||||||
min-width: 2em;
|
|
||||||
padding: .4em;
|
|
||||||
border: 1px solid hsl(0, 0%, 75%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content .table table th {
|
|
||||||
font-weight: bold;
|
|
||||||
background: hsla(0, 0%, 0%, 5%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content[dir="rtl"] .table th {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/table.css */
|
|
||||||
.ck-content[dir="ltr"] .table th {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
|
|
||||||
.ck-content .table > figcaption {
|
|
||||||
display: table-caption;
|
|
||||||
caption-side: top;
|
|
||||||
word-break: break-word;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--ck-color-selector-caption-text);
|
|
||||||
background-color: var(--ck-color-selector-caption-background);
|
|
||||||
padding: .6em;
|
|
||||||
font-size: .75em;
|
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break {
|
|
||||||
position: relative;
|
|
||||||
clear: both;
|
|
||||||
padding: 5px 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break__label {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
padding: .3em .6em;
|
|
||||||
display: block;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border: 1px solid hsl(0, 0%, 77%);
|
|
||||||
border-radius: 2px;
|
|
||||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
|
||||||
font-size: 0.75em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: hsl(0, 0%, 20%);
|
|
||||||
background: hsl(0, 0%, 100%);
|
|
||||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
|
|
||||||
.ck-content .media {
|
|
||||||
clear: both;
|
|
||||||
margin: 0.9em 0;
|
|
||||||
display: block;
|
|
||||||
min-width: 15em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list li {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list li .todo-list {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: var(--ck-todo-list-checkmark-size);
|
|
||||||
height: var(--ck-todo-list-checkmark-size);
|
|
||||||
vertical-align: middle;
|
|
||||||
border: 0;
|
|
||||||
left: -25px;
|
|
||||||
margin-right: -15px;
|
|
||||||
right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content[dir=rtl] .todo-list .todo-list__label > input {
|
|
||||||
left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
right: -25px;
|
|
||||||
margin-left: -15px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input::before {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: border-box;
|
|
||||||
content: '';
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 1px solid hsl(0, 0%, 20%);
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: 250ms ease-in-out box-shadow;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input::after {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: content-box;
|
|
||||||
pointer-events: none;
|
|
||||||
content: '';
|
|
||||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
|
||||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent;
|
|
||||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input[checked]::before {
|
|
||||||
background: hsl(126, 64%, 41%);
|
|
||||||
border-color: hsl(126, 64%, 41%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label > input[checked]::after {
|
|
||||||
border-color: hsl(0, 0%, 100%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label .todo-list__label__description {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input,
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before {
|
|
||||||
box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
width: var(--ck-todo-list-checkmark-size);
|
|
||||||
height: var(--ck-todo-list-checkmark-size);
|
|
||||||
vertical-align: middle;
|
|
||||||
border: 0;
|
|
||||||
left: -25px;
|
|
||||||
margin-right: -15px;
|
|
||||||
right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input {
|
|
||||||
left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
right: -25px;
|
|
||||||
margin-left: -15px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: border-box;
|
|
||||||
content: '';
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: 1px solid hsl(0, 0%, 20%);
|
|
||||||
border-radius: 2px;
|
|
||||||
transition: 250ms ease-in-out box-shadow;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
box-sizing: content-box;
|
|
||||||
pointer-events: none;
|
|
||||||
content: '';
|
|
||||||
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
|
|
||||||
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
|
|
||||||
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent;
|
|
||||||
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before {
|
|
||||||
background: hsl(126, 64%, 41%);
|
|
||||||
border-color: hsl(126, 64%, 41%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after {
|
|
||||||
border-color: hsl(0, 0%, 100%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/todolist.css */
|
|
||||||
.ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol {
|
|
||||||
list-style-type: decimal;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol {
|
|
||||||
list-style-type: lower-latin;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol ol {
|
|
||||||
list-style-type: lower-roman;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol ol ol {
|
|
||||||
list-style-type: upper-latin;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ol ol ol ol ol {
|
|
||||||
list-style-type: upper-roman;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul ul {
|
|
||||||
list-style-type: circle;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul ul ul {
|
|
||||||
list-style-type: square;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-list/theme/list.css */
|
|
||||||
.ck-content ul ul ul ul {
|
|
||||||
list-style-type: square;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image {
|
|
||||||
display: table;
|
|
||||||
clear: both;
|
|
||||||
text-align: center;
|
|
||||||
margin: 0.9em auto;
|
|
||||||
min-width: 50px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image img {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image-inline {
|
|
||||||
/*
|
|
||||||
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
|
|
||||||
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
|
|
||||||
* This strange behavior does not happen with inline-flex.
|
|
||||||
*/
|
|
||||||
display: inline-flex;
|
|
||||||
max-width: 100%;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image-inline picture {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/image.css */
|
|
||||||
.ck-content .image-inline picture,
|
|
||||||
.ck-content .image-inline img {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content img.image_resized {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content .image.image_resized {
|
|
||||||
max-width: 100%;
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content .image.image_resized img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
|
|
||||||
.ck-content .image.image_resized > figcaption {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
|
|
||||||
.ck-content .image > figcaption {
|
|
||||||
display: table-caption;
|
|
||||||
caption-side: bottom;
|
|
||||||
word-break: break-word;
|
|
||||||
color: var(--ck-color-image-caption-text);
|
|
||||||
background-color: var(--ck-color-image-caption-background);
|
|
||||||
padding: .6em;
|
|
||||||
font-size: .75em;
|
|
||||||
outline-offset: -1px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-block-align-left,
|
|
||||||
.ck-content .image-style-block-align-right {
|
|
||||||
max-width: calc(100% - var(--ck-image-style-spacing));
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-left,
|
|
||||||
.ck-content .image-style-align-right {
|
|
||||||
clear: none;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-side {
|
|
||||||
float: right;
|
|
||||||
margin-left: var(--ck-image-style-spacing);
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-left {
|
|
||||||
float: left;
|
|
||||||
margin-right: var(--ck-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-center {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-align-right {
|
|
||||||
float: right;
|
|
||||||
margin-left: var(--ck-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-block-align-right {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-style-block-align-left {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content p + .image-style-align-left,
|
|
||||||
.ck-content p + .image-style-align-right,
|
|
||||||
.ck-content p + .image-style-side {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-inline.image-style-align-left,
|
|
||||||
.ck-content .image-inline.image-style-align-right {
|
|
||||||
margin-top: var(--ck-inline-image-style-spacing);
|
|
||||||
margin-bottom: var(--ck-inline-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-inline.image-style-align-left {
|
|
||||||
margin-right: var(--ck-inline-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
|
|
||||||
.ck-content .image-inline.image-style-align-right {
|
|
||||||
margin-left: var(--ck-inline-image-style-spacing);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-yellow {
|
|
||||||
background-color: var(--ck-highlight-marker-yellow);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-green {
|
|
||||||
background-color: var(--ck-highlight-marker-green);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-pink {
|
|
||||||
background-color: var(--ck-highlight-marker-pink);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .marker-blue {
|
|
||||||
background-color: var(--ck-highlight-marker-blue);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .pen-red {
|
|
||||||
color: var(--ck-highlight-pen-red);
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
|
|
||||||
.ck-content .pen-green {
|
|
||||||
color: var(--ck-highlight-pen-green);
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
|
||||||
.ck-content blockquote {
|
|
||||||
overflow: hidden;
|
|
||||||
padding-right: 1.5em;
|
|
||||||
padding-left: 1.5em;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
font-style: italic;
|
|
||||||
border-left: solid 5px hsl(0, 0%, 80%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
|
|
||||||
.ck-content[dir="rtl"] blockquote {
|
|
||||||
border-left: 0;
|
|
||||||
border-right: solid 5px hsl(0, 0%, 80%);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
|
|
||||||
.ck-content code {
|
|
||||||
background-color: hsla(0, 0%, 78%, 0.3);
|
|
||||||
padding: .15em;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-tiny {
|
|
||||||
font-size: .7em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-small {
|
|
||||||
font-size: .85em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-big {
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
|
|
||||||
.ck-content .text-huge {
|
|
||||||
font-size: 1.8em;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-mention/theme/mention.css */
|
|
||||||
.ck-content .mention {
|
|
||||||
background: var(--ck-color-mention-background);
|
|
||||||
color: var(--ck-color-mention-text);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
|
|
||||||
.ck-content hr {
|
|
||||||
margin: 15px 0;
|
|
||||||
height: 4px;
|
|
||||||
background: hsl(0, 0%, 87%);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
|
||||||
.ck-content pre {
|
|
||||||
padding: 1em;
|
|
||||||
text-align: left;
|
|
||||||
direction: ltr;
|
|
||||||
tab-size: 4;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-style: normal;
|
|
||||||
min-width: 200px;
|
|
||||||
border: 0px;
|
|
||||||
border-radius: 6px;
|
|
||||||
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
.ck-content pre:not(.hljs) {
|
|
||||||
color: hsl(0, 0%, 20.8%);
|
|
||||||
background: hsla(0, 0%, 78%, 0.3);
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
|
|
||||||
.ck-content pre code {
|
|
||||||
background: unset;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
|
|
||||||
.ck-content .page-break::after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"templates": {
|
|
||||||
"default": {
|
|
||||||
"includeDate": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -36,12 +36,12 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "1.52.0",
|
"@playwright/test": "1.52.0",
|
||||||
"@stylistic/eslint-plugin": "4.2.0",
|
"@stylistic/eslint-plugin": "4.4.0",
|
||||||
"@types/express": "5.0.1",
|
"@types/express": "5.0.1",
|
||||||
"@types/node": "22.15.17",
|
"@types/node": "22.15.29",
|
||||||
"@types/yargs": "17.0.33",
|
"@types/yargs": "17.0.33",
|
||||||
"@vitest/coverage-v8": "3.1.3",
|
"@vitest/coverage-v8": "3.1.4",
|
||||||
"eslint": "9.26.0",
|
"eslint": "9.27.0",
|
||||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||||
"esm": "3.2.25",
|
"esm": "3.2.25",
|
||||||
"jsdoc": "4.0.4",
|
"jsdoc": "4.0.4",
|
||||||
@ -49,7 +49,7 @@
|
|||||||
"rcedit": "4.0.1",
|
"rcedit": "4.0.1",
|
||||||
"rimraf": "6.0.1",
|
"rimraf": "6.0.1",
|
||||||
"tslib": "2.8.1",
|
"tslib": "2.8.1",
|
||||||
"typedoc": "0.28.4",
|
"typedoc": "0.28.5",
|
||||||
"typedoc-plugin-missing-exports": "4.0.0"
|
"typedoc-plugin-missing-exports": "4.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
|||||||
43
_regroup/test-etapi/api-metrics.http
Normal file
43
_regroup/test-etapi/api-metrics.http
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
### Test regular API metrics endpoint (requires session authentication)
|
||||||
|
|
||||||
|
### Get metrics from regular API (default Prometheus format)
|
||||||
|
GET {{triliumHost}}/api/metrics
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("API metrics endpoint returns Prometheus format by default", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
|
||||||
|
client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
|
||||||
|
client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
|
||||||
|
client.assert(response.body.includes("# HELP"), "Should contain HELP comments");
|
||||||
|
client.assert(response.body.includes("# TYPE"), "Should contain TYPE comments");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics in JSON format
|
||||||
|
GET {{triliumHost}}/api/metrics?format=json
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("API metrics endpoint returns JSON when requested", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("application/json"), "Content-Type should be application/json");
|
||||||
|
client.assert(response.body.version, "Version info not present");
|
||||||
|
client.assert(response.body.database, "Database info not present");
|
||||||
|
client.assert(response.body.timestamp, "Timestamp not present");
|
||||||
|
client.assert(typeof response.body.database.totalNotes === 'number', "Total notes should be a number");
|
||||||
|
client.assert(typeof response.body.database.activeNotes === 'number', "Active notes should be a number");
|
||||||
|
client.assert(response.body.noteTypes, "Note types breakdown not present");
|
||||||
|
client.assert(response.body.attachmentTypes, "Attachment types breakdown not present");
|
||||||
|
client.assert(response.body.statistics, "Statistics not present");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Test invalid format parameter
|
||||||
|
GET {{triliumHost}}/api/metrics?format=xml
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Invalid format parameter returns error", function() {
|
||||||
|
client.assert(response.status === 500, "Response status should be 500");
|
||||||
|
client.assert(response.body.message.includes("prometheus"), "Error message should mention supported formats");
|
||||||
|
});
|
||||||
|
%}
|
||||||
82
_regroup/test-etapi/metrics.http
Normal file
82
_regroup/test-etapi/metrics.http
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
### Test ETAPI metrics endpoint
|
||||||
|
|
||||||
|
# First login to get a token
|
||||||
|
POST {{triliumHost}}/etapi/auth/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"password": "{{password}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Login successful", function() {
|
||||||
|
client.assert(response.status === 201, "Response status is not 201");
|
||||||
|
client.assert(response.body.authToken, "Auth token not present");
|
||||||
|
client.global.set("authToken", response.body.authToken);
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics with authentication (default Prometheus format)
|
||||||
|
GET {{triliumHost}}/etapi/metrics
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint returns Prometheus format by default", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
|
||||||
|
client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
|
||||||
|
client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
|
||||||
|
client.assert(response.body.includes("# HELP"), "Should contain HELP comments");
|
||||||
|
client.assert(response.body.includes("# TYPE"), "Should contain TYPE comments");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics in JSON format
|
||||||
|
GET {{triliumHost}}/etapi/metrics?format=json
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint returns JSON when requested", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("application/json"), "Content-Type should be application/json");
|
||||||
|
client.assert(response.body.version, "Version info not present");
|
||||||
|
client.assert(response.body.database, "Database info not present");
|
||||||
|
client.assert(response.body.timestamp, "Timestamp not present");
|
||||||
|
client.assert(typeof response.body.database.totalNotes === 'number', "Total notes should be a number");
|
||||||
|
client.assert(typeof response.body.database.activeNotes === 'number', "Active notes should be a number");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics in Prometheus format explicitly
|
||||||
|
GET {{triliumHost}}/etapi/metrics?format=prometheus
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint returns Prometheus format when requested", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
|
||||||
|
client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
|
||||||
|
client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Test invalid format parameter
|
||||||
|
GET {{triliumHost}}/etapi/metrics?format=xml
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Invalid format parameter returns error", function() {
|
||||||
|
client.assert(response.status === 400, "Response status should be 400");
|
||||||
|
client.assert(response.body.code === "INVALID_FORMAT", "Error code should be INVALID_FORMAT");
|
||||||
|
client.assert(response.body.message.includes("prometheus"), "Error message should mention supported formats");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Test without authentication (should fail)
|
||||||
|
GET {{triliumHost}}/etapi/metrics
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint requires authentication", function() {
|
||||||
|
client.assert(response.status === 401, "Response status should be 401");
|
||||||
|
});
|
||||||
|
%}
|
||||||
@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/spec-es6" isTestSource="true" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/libraries" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/libraries" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/docs" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/bin/better-sqlite3" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/data" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.flatpak-builder" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" name="@types/jquery" level="application" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"url": "https://github.com/TriliumNext/Notes"
|
"url": "https://github.com/TriliumNext/Notes"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/js": "9.26.0",
|
"@eslint/js": "9.27.0",
|
||||||
"@excalidraw/excalidraw": "0.18.0",
|
"@excalidraw/excalidraw": "0.18.0",
|
||||||
"@fullcalendar/core": "6.1.17",
|
"@fullcalendar/core": "6.1.17",
|
||||||
"@fullcalendar/daygrid": "6.1.17",
|
"@fullcalendar/daygrid": "6.1.17",
|
||||||
@ -22,28 +22,33 @@
|
|||||||
"@mind-elixir/node-menu": "1.0.5",
|
"@mind-elixir/node-menu": "1.0.5",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@triliumnext/ckeditor5": "workspace:*",
|
"@triliumnext/ckeditor5": "workspace:*",
|
||||||
|
"@triliumnext/codemirror": "workspace:*",
|
||||||
"@triliumnext/commons": "workspace:*",
|
"@triliumnext/commons": "workspace:*",
|
||||||
|
"@triliumnext/highlightjs": "workspace:*",
|
||||||
|
"autocomplete.js": "0.38.1",
|
||||||
"bootstrap": "5.3.6",
|
"bootstrap": "5.3.6",
|
||||||
|
"boxicons": "2.1.4",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"dayjs-plugin-utc": "0.1.2",
|
"dayjs-plugin-utc": "0.1.2",
|
||||||
"debounce": "2.2.0",
|
"debounce": "2.2.0",
|
||||||
"draggabilly": "3.0.0",
|
"draggabilly": "3.0.0",
|
||||||
"eslint-linter-browserify": "9.26.0",
|
"force-graph": "1.49.6",
|
||||||
"force-graph": "1.49.5",
|
"globals": "16.2.0",
|
||||||
"globals": "16.1.0",
|
"i18next": "25.2.1",
|
||||||
"i18next": "25.1.2",
|
|
||||||
"i18next-http-backend": "3.0.2",
|
"i18next-http-backend": "3.0.2",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"jquery-hotkeys": "0.2.2",
|
"jquery-hotkeys": "0.2.2",
|
||||||
"jquery.fancytree": "2.38.5",
|
"jquery.fancytree": "2.38.5",
|
||||||
"jsplumb": "2.15.6",
|
"jsplumb": "2.15.6",
|
||||||
|
"katex": "0.16.22",
|
||||||
"knockout": "3.5.1",
|
"knockout": "3.5.1",
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-gpx": "2.2.0",
|
"leaflet-gpx": "2.2.0",
|
||||||
"mark.js": "8.11.1",
|
"mark.js": "8.11.1",
|
||||||
"marked": "15.0.11",
|
"marked": "15.0.12",
|
||||||
"mermaid": "11.6.0",
|
"mermaid": "11.6.0",
|
||||||
"mind-elixir": "4.5.2",
|
"mind-elixir": "4.6.0",
|
||||||
|
"normalize.css": "8.0.1",
|
||||||
"panzoom": "9.4.3",
|
"panzoom": "9.4.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
@ -55,13 +60,15 @@
|
|||||||
"@ckeditor/ckeditor5-inspector": "4.1.0",
|
"@ckeditor/ckeditor5-inspector": "4.1.0",
|
||||||
"@types/bootstrap": "5.2.10",
|
"@types/bootstrap": "5.2.10",
|
||||||
"@types/jquery": "3.5.32",
|
"@types/jquery": "3.5.32",
|
||||||
"@types/leaflet": "1.9.17",
|
"@types/leaflet": "1.9.18",
|
||||||
"@types/leaflet-gpx": "1.3.7",
|
"@types/leaflet-gpx": "1.3.7",
|
||||||
"@types/react": "19.1.3",
|
"@types/mark.js": "8.11.12",
|
||||||
"@types/react-dom": "19.1.3",
|
"@types/react": "19.1.6",
|
||||||
|
"@types/react-dom": "19.1.5",
|
||||||
"copy-webpack-plugin": "13.0.0",
|
"copy-webpack-plugin": "13.0.0",
|
||||||
"happy-dom": "17.4.7",
|
"happy-dom": "17.5.6",
|
||||||
"script-loader": "0.7.2"
|
"script-loader": "0.7.2",
|
||||||
|
"vite-plugin-static-copy": "3.0.0"
|
||||||
},
|
},
|
||||||
"nx": {
|
"nx": {
|
||||||
"name": "client"
|
"name": "client"
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.j
|
|||||||
import type { NativeImage, TouchBar } from "electron";
|
import type { NativeImage, TouchBar } from "electron";
|
||||||
import TouchBarComponent from "./touch_bar.js";
|
import TouchBarComponent from "./touch_bar.js";
|
||||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||||
|
import type CodeMirror from "@triliumnext/codemirror";
|
||||||
|
|
||||||
interface Layout {
|
interface Layout {
|
||||||
getRootWidget: (appContext: AppContext) => RootWidget;
|
getRootWidget: (appContext: AppContext) => RootWidget;
|
||||||
@ -191,7 +192,7 @@ export type CommandMappings = {
|
|||||||
ExecuteCommandData<CKTextEditor> & {
|
ExecuteCommandData<CKTextEditor> & {
|
||||||
callback?: GetTextEditorCallback;
|
callback?: GetTextEditorCallback;
|
||||||
};
|
};
|
||||||
executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirrorInstance>;
|
executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirror>;
|
||||||
/**
|
/**
|
||||||
* Called upon when attempting to retrieve the content element of a {@link NoteContext}.
|
* Called upon when attempting to retrieve the content element of a {@link NoteContext}.
|
||||||
* Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}.
|
* Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}.
|
||||||
@ -283,6 +284,9 @@ export type CommandMappings = {
|
|||||||
type EventMappings = {
|
type EventMappings = {
|
||||||
initialRenderComplete: {};
|
initialRenderComplete: {};
|
||||||
frocaReloaded: {};
|
frocaReloaded: {};
|
||||||
|
setLeftPaneVisibility: {
|
||||||
|
leftPaneVisible: boolean | null;
|
||||||
|
}
|
||||||
protectedSessionStarted: {};
|
protectedSessionStarted: {};
|
||||||
notesReloaded: {
|
notesReloaded: {
|
||||||
noteIds: string[];
|
noteIds: string[];
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import type { ViewScope } from "../services/link.js";
|
|||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||||
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
import type { CKTextEditor } from "@triliumnext/ckeditor5";
|
||||||
|
import type CodeMirror from "@triliumnext/codemirror";
|
||||||
|
|
||||||
export interface SetNoteOpts {
|
export interface SetNoteOpts {
|
||||||
triggerSwitchEvent?: unknown;
|
triggerSwitchEvent?: unknown;
|
||||||
@ -158,6 +159,9 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveToRecentNotes(resolvedNotePath: string) {
|
saveToRecentNotes(resolvedNotePath: string) {
|
||||||
|
if (options.is("databaseReadonly")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
// we include the note in the recent list only if the user stayed on the note at least 5 seconds
|
// we include the note in the recent list only if the user stayed on the note at least 5 seconds
|
||||||
if (resolvedNotePath && resolvedNotePath === this.notePath) {
|
if (resolvedNotePath && resolvedNotePath === this.notePath) {
|
||||||
@ -253,6 +257,10 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.is("databaseReadonly")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.note.isLabelTruthy("readOnly")) {
|
if (this.note.isLabelTruthy("readOnly")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -331,7 +339,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
|||||||
|
|
||||||
async getCodeEditor() {
|
async getCodeEditor() {
|
||||||
return this.timeout(
|
return this.timeout(
|
||||||
new Promise<CodeMirrorInstance>((resolve) =>
|
new Promise<CodeMirror>((resolve) =>
|
||||||
appContext.triggerCommand("executeWithCodeEditor", {
|
appContext.triggerCommand("executeWithCodeEditor", {
|
||||||
resolve,
|
resolve,
|
||||||
ntxId: this.ntxId
|
ntxId: this.ntxId
|
||||||
|
|||||||
@ -78,15 +78,15 @@ export default class RootCommandExecutor extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hideLeftPaneCommand() {
|
hideLeftPaneCommand() {
|
||||||
options.save(`leftPaneVisible`, "false");
|
appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
showLeftPaneCommand() {
|
showLeftPaneCommand() {
|
||||||
options.save(`leftPaneVisible`, "true");
|
appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLeftPaneCommand() {
|
toggleLeftPaneCommand() {
|
||||||
options.toggle("leftPaneVisible");
|
appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
async showBackendLogCommand() {
|
async showBackendLogCommand() {
|
||||||
|
|||||||
@ -44,6 +44,9 @@ export default class TabManager extends Component {
|
|||||||
if (!appContext.isMainWindow) {
|
if (!appContext.isMainWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (options.is("databaseReadonly")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const openNoteContexts = this.noteContexts
|
const openNoteContexts = this.noteContexts
|
||||||
.map((nc) => nc.getPojoState())
|
.map((nc) => nc.getPojoState())
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export default class TouchBarComponent extends Component {
|
|||||||
#refreshTouchBar() {
|
#refreshTouchBar() {
|
||||||
const { TouchBar } = this.remote;
|
const { TouchBar } = this.remote;
|
||||||
const parentComponent = this.lastFocusedComponent;
|
const parentComponent = this.lastFocusedComponent;
|
||||||
let touchBar = null;
|
let touchBar: Electron.CrossProcessExports.TouchBar | null = null;
|
||||||
|
|
||||||
if (this.$activeModal?.length) {
|
if (this.$activeModal?.length) {
|
||||||
touchBar = this.#buildModalTouchBar();
|
touchBar = this.#buildModalTouchBar();
|
||||||
|
|||||||
@ -11,6 +11,9 @@ import options from "./services/options.js";
|
|||||||
import type ElectronRemote from "@electron/remote";
|
import type ElectronRemote from "@electron/remote";
|
||||||
import type Electron from "electron";
|
import type Electron from "electron";
|
||||||
import "./stylesheets/bootstrap.scss";
|
import "./stylesheets/bootstrap.scss";
|
||||||
|
import "boxicons/css/boxicons.min.css";
|
||||||
|
import "jquery-hotkeys";
|
||||||
|
import "autocomplete.js/index_jquery.js";
|
||||||
|
|
||||||
await appContext.earlyInit();
|
await appContext.earlyInit();
|
||||||
|
|
||||||
|
|||||||
@ -789,7 +789,7 @@ class FNote {
|
|||||||
*/
|
*/
|
||||||
async getRelationTargets(name: string) {
|
async getRelationTargets(name: string) {
|
||||||
const relations = this.getRelations(name);
|
const relations = this.getRelations(name);
|
||||||
const targets = [];
|
const targets: (FNote | null)[] = [];
|
||||||
|
|
||||||
for (const relation of relations) {
|
for (const relation of relations) {
|
||||||
targets.push(await this.froca.getNote(relation.value));
|
targets.push(await this.froca.getNote(relation.value));
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
async function validatorHtml(text, options) {
|
|
||||||
const result = /<script[^>]*>([\s\S]+)<\/script>/ig.exec(text);
|
|
||||||
|
|
||||||
if (result !== null) {
|
|
||||||
// preceding code is copied over but any (non-newline) character is replaced with space
|
|
||||||
// this will preserve line numbers etc.
|
|
||||||
const prefix = text.substr(0, result.index).replace(/./g, " ");
|
|
||||||
|
|
||||||
const js = prefix + result[1];
|
|
||||||
|
|
||||||
return await validatorJavaScript(js, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validatorJavaScript(text, options) {
|
|
||||||
if (glob.isMobile()
|
|
||||||
|| glob.getActiveContextNote() == null
|
|
||||||
|| glob.getActiveContextNote().mime === 'application/json') {
|
|
||||||
// eslint doesn't seem to validate pure JSON well
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.length > 20000) {
|
|
||||||
console.log("Skipping linting because of large size: ", text.length);
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const errors = await glob.linter(text, glob.getActiveContextNote().mime);
|
|
||||||
|
|
||||||
console.log(errors);
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
if (errors) {
|
|
||||||
parseErrors(errors, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("lint", "javascript", validatorJavaScript);
|
|
||||||
CodeMirror.registerHelper("lint", "html", validatorHtml);
|
|
||||||
|
|
||||||
function parseErrors(errors, output) {
|
|
||||||
for (const error of errors) {
|
|
||||||
const startLine = error.line - 1;
|
|
||||||
const endLine = error.endLine !== undefined ? error.endLine - 1 : startLine;
|
|
||||||
const startCol = error.column - 1;
|
|
||||||
const endCol = error.endColumn !== undefined ? error.endColumn - 1 : startCol + 1;
|
|
||||||
|
|
||||||
output.push({
|
|
||||||
message: error.message,
|
|
||||||
severity: error.severity === 1 ? "warning" : "error",
|
|
||||||
from: CodeMirror.Pos(startLine, startCol),
|
|
||||||
to: CodeMirror.Pos(endLine, endCol)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* highlight.js terraform syntax highlighting definition
|
|
||||||
*
|
|
||||||
* @see https://github.com/highlightjs/highlight.js
|
|
||||||
*
|
|
||||||
* :TODO:
|
|
||||||
*
|
|
||||||
* @package: highlightjs-terraform
|
|
||||||
* @author: Nikos Tsirmirakis <nikos.tsirmirakis@winopsdba.com>
|
|
||||||
* @since: 2019-03-20
|
|
||||||
*
|
|
||||||
* Description: Terraform (HCL) language definition
|
|
||||||
* Category: scripting
|
|
||||||
*/
|
|
||||||
|
|
||||||
var module = module ? module : {}; // shim for browser use
|
|
||||||
|
|
||||||
function hljsDefineTerraform(hljs) {
|
|
||||||
var NUMBERS = {
|
|
||||||
className: 'number',
|
|
||||||
begin: '\\b\\d+(\\.\\d+)?',
|
|
||||||
relevance: 0
|
|
||||||
};
|
|
||||||
var STRINGS = {
|
|
||||||
className: 'string',
|
|
||||||
begin: '"',
|
|
||||||
end: '"',
|
|
||||||
contains: [{
|
|
||||||
className: 'variable',
|
|
||||||
begin: '\\${',
|
|
||||||
end: '\\}',
|
|
||||||
relevance: 9,
|
|
||||||
contains: [{
|
|
||||||
className: 'string',
|
|
||||||
begin: '"',
|
|
||||||
end: '"'
|
|
||||||
}, {
|
|
||||||
className: 'meta',
|
|
||||||
begin: '[A-Za-z_0-9]*' + '\\(',
|
|
||||||
end: '\\)',
|
|
||||||
contains: [
|
|
||||||
NUMBERS, {
|
|
||||||
className: 'string',
|
|
||||||
begin: '"',
|
|
||||||
end: '"',
|
|
||||||
contains: [{
|
|
||||||
className: 'variable',
|
|
||||||
begin: '\\${',
|
|
||||||
end: '\\}',
|
|
||||||
contains: [{
|
|
||||||
className: 'string',
|
|
||||||
begin: '"',
|
|
||||||
end: '"',
|
|
||||||
contains: [{
|
|
||||||
className: 'variable',
|
|
||||||
begin: '\\${',
|
|
||||||
end: '\\}'
|
|
||||||
}]
|
|
||||||
}, {
|
|
||||||
className: 'meta',
|
|
||||||
begin: '[A-Za-z_0-9]*' + '\\(',
|
|
||||||
end: '\\)'
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
'self']
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
aliases: ['tf', 'hcl'],
|
|
||||||
keywords: 'resource variable provider output locals module data terraform|10',
|
|
||||||
literal: 'false true null',
|
|
||||||
contains: [
|
|
||||||
hljs.COMMENT('\\#', '$'),
|
|
||||||
NUMBERS,
|
|
||||||
STRINGS
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hljs.registerLanguage('terraform', hljsDefineTerraform);
|
|
||||||
@ -2,6 +2,8 @@ import appContext from "./components/app_context.js";
|
|||||||
import noteAutocompleteService from "./services/note_autocomplete.js";
|
import noteAutocompleteService from "./services/note_autocomplete.js";
|
||||||
import glob from "./services/glob.js";
|
import glob from "./services/glob.js";
|
||||||
import "./stylesheets/bootstrap.scss";
|
import "./stylesheets/bootstrap.scss";
|
||||||
|
import "boxicons/css/boxicons.min.css";
|
||||||
|
import "autocomplete.js/index_jquery.js";
|
||||||
|
|
||||||
glob.setupGlobs();
|
glob.setupGlobs();
|
||||||
|
|
||||||
|
|||||||
5
apps/client/src/runtime.ts
Normal file
5
apps/client/src/runtime.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import $ from "jquery";
|
||||||
|
(window as any).$ = $;
|
||||||
|
(window as any).jQuery = $;
|
||||||
|
|
||||||
|
$("body").show();
|
||||||
@ -4,6 +4,7 @@ import froca from "./froca.js";
|
|||||||
import linkService from "./link.js";
|
import linkService from "./link.js";
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
|
import toast from "./toast.js";
|
||||||
|
|
||||||
let clipboardBranchIds: string[] = [];
|
let clipboardBranchIds: string[] = [];
|
||||||
let clipboardMode: string | null = null;
|
let clipboardMode: string | null = null;
|
||||||
@ -79,7 +80,7 @@ async function copy(branchIds: string[]) {
|
|||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
// https://github.com/zadam/trilium/issues/2401
|
// https://github.com/zadam/trilium/issues/2401
|
||||||
const { clipboard } = require("electron");
|
const { clipboard } = require("electron");
|
||||||
const links = [];
|
const links: string[] = [];
|
||||||
|
|
||||||
for (const branch of froca.getBranches(clipboardBranchIds)) {
|
for (const branch of froca.getBranches(clipboardBranchIds)) {
|
||||||
const $link = await linkService.createLink(`${branch.parentNoteId}/${branch.noteId}`, { referenceLink: true });
|
const $link = await linkService.createLink(`${branch.parentNoteId}/${branch.noteId}`, { referenceLink: true });
|
||||||
@ -108,6 +109,39 @@ function isClipboardEmpty() {
|
|||||||
return clipboardBranchIds.length === 0;
|
return clipboardBranchIds.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function copyText(text: string) {
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let succeeded = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
succeeded = true;
|
||||||
|
} else {
|
||||||
|
// Fallback method: https://stackoverflow.com/a/72239825
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = text;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
succeeded = document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
succeeded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (succeeded) {
|
||||||
|
toast.showMessage(t("clipboard.copy_success"));
|
||||||
|
} else {
|
||||||
|
toast.showError(t("clipboard.copy_failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
pasteAfter,
|
pasteAfter,
|
||||||
pasteInto,
|
pasteInto,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import renderService from "./render.js";
|
import renderService from "./render.js";
|
||||||
import protectedSessionService from "./protected_session.js";
|
import protectedSessionService from "./protected_session.js";
|
||||||
import protectedSessionHolder from "./protected_session_holder.js";
|
import protectedSessionHolder from "./protected_session_holder.js";
|
||||||
import libraryLoader from "./library_loader.js";
|
|
||||||
import openService from "./open.js";
|
import openService from "./open.js";
|
||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
@ -10,12 +9,13 @@ import treeService from "./tree.js";
|
|||||||
import FNote from "../entities/fnote.js";
|
import FNote from "../entities/fnote.js";
|
||||||
import FAttachment from "../entities/fattachment.js";
|
import FAttachment from "../entities/fattachment.js";
|
||||||
import imageContextMenuService from "../menus/image_context_menu.js";
|
import imageContextMenuService from "../menus/image_context_menu.js";
|
||||||
import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js";
|
import { applySingleBlockSyntaxHighlight, formatCodeBlocks } from "./syntax_highlight.js";
|
||||||
import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
|
import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
|
||||||
import { normalizeMimeTypeForCKEditor } from "./mime_type_definitions.js";
|
|
||||||
import renderDoc from "./doc_renderer.js";
|
import renderDoc from "./doc_renderer.js";
|
||||||
import { t } from "i18next";
|
import { t } from "../services/i18n.js";
|
||||||
import WheelZoom from 'vanilla-js-wheel-zoom';
|
import WheelZoom from 'vanilla-js-wheel-zoom';
|
||||||
|
import { renderMathInElement } from "./math.js";
|
||||||
|
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
|
||||||
|
|
||||||
let idCounter = 1;
|
let idCounter = 1;
|
||||||
|
|
||||||
@ -94,8 +94,6 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HT
|
|||||||
$renderedContent.append($('<div class="ck-content">').html(blob.content));
|
$renderedContent.append($('<div class="ck-content">').html(blob.content));
|
||||||
|
|
||||||
if ($renderedContent.find("span.math-tex").length > 0) {
|
if ($renderedContent.find("span.math-tex").length > 0) {
|
||||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
|
||||||
|
|
||||||
renderMathInElement($renderedContent[0], { trust: true });
|
renderMathInElement($renderedContent[0], { trust: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +106,7 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HT
|
|||||||
await linkService.loadReferenceLinkTitle($(el));
|
await linkService.loadReferenceLinkTitle($(el));
|
||||||
}
|
}
|
||||||
|
|
||||||
await applySyntaxHighlight($renderedContent);
|
await formatCodeBlocks($renderedContent);
|
||||||
} else if (note instanceof FNote) {
|
} else if (note instanceof FNote) {
|
||||||
await renderChildrenList($renderedContent, note);
|
await renderChildrenList($renderedContent, note);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import { getCurrentLanguage } from "./i18n.js";
|
import { getCurrentLanguage } from "./i18n.js";
|
||||||
import { applySyntaxHighlight } from "./syntax_highlight.js";
|
import { formatCodeBlocks } from "./syntax_highlight.js";
|
||||||
|
|
||||||
export default function renderDoc(note: FNote) {
|
export default function renderDoc(note: FNote) {
|
||||||
return new Promise<JQuery<HTMLElement>>((resolve) => {
|
return new Promise<JQuery<HTMLElement>>((resolve) => {
|
||||||
@ -41,12 +41,13 @@ function processContent(url: string, $content: JQuery<HTMLElement>) {
|
|||||||
$img.attr("src", dir + "/" + $img.attr("src"));
|
$img.attr("src", dir + "/" + $img.attr("src"));
|
||||||
});
|
});
|
||||||
|
|
||||||
applySyntaxHighlight($content);
|
formatCodeBlocks($content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUrl(docNameValue: string, language: string) {
|
function getUrl(docNameValue: string, language: string) {
|
||||||
// Cannot have spaces in the URL due to how JQuery.load works.
|
// Cannot have spaces in the URL due to how JQuery.load works.
|
||||||
docNameValue = docNameValue.replaceAll(" ", "%20");
|
docNameValue = docNameValue.replaceAll(" ", "%20");
|
||||||
|
|
||||||
return `${window.glob.appPath}/doc_notes/${language}/${docNameValue}.html`;
|
const basePath = window.glob.isDev ? new URL(window.glob.assetPath).pathname : window.glob.assetPath;
|
||||||
|
return `${basePath}/doc_notes/${language}/${docNameValue}.html`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
|
|||||||
// To this we count: standard parent-child relationships and template/inherit relations (attribute inheritance follows them).
|
// To this we count: standard parent-child relationships and template/inherit relations (attribute inheritance follows them).
|
||||||
// Here we watch for changes which might violate this principle - e.g., an introduction of a new "inherit" relation might
|
// Here we watch for changes which might violate this principle - e.g., an introduction of a new "inherit" relation might
|
||||||
// mean we need to load the target of the relation (and then perhaps transitively the whole note path of this target).
|
// mean we need to load the target of the relation (and then perhaps transitively the whole note path of this target).
|
||||||
const missingNoteIds = [];
|
const missingNoteIds: string[] = [];
|
||||||
|
|
||||||
for (const { entityName, entity } of entityChanges) {
|
for (const { entityName, entity } of entityChanges) {
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import libraryLoader from "./library_loader.js";
|
|
||||||
import ws from "./ws.js";
|
import ws from "./ws.js";
|
||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import linkService from "./link.js";
|
import linkService from "./link.js";
|
||||||
import { lint } from "./eslint.js";
|
|
||||||
|
|
||||||
function setupGlobs() {
|
function setupGlobs() {
|
||||||
window.glob.isDesktop = utils.isDesktop;
|
window.glob.isDesktop = utils.isDesktop;
|
||||||
@ -18,8 +16,6 @@ function setupGlobs() {
|
|||||||
|
|
||||||
// required for ESLint plugin and CKEditor
|
// required for ESLint plugin and CKEditor
|
||||||
window.glob.getActiveContextNote = () => appContext.tabManager.getActiveContextNote();
|
window.glob.getActiveContextNote = () => appContext.tabManager.getActiveContextNote();
|
||||||
window.glob.requireLibrary = libraryLoader.requireLibrary;
|
|
||||||
window.glob.linter = lint;
|
|
||||||
window.glob.appContext = appContext; // for debugging
|
window.glob.appContext = appContext; // for debugging
|
||||||
window.glob.froca = froca;
|
window.glob.froca = froca;
|
||||||
window.glob.treeCache = froca; // compatibility for CKEditor builds for a while
|
window.glob.treeCache = froca; // compatibility for CKEditor builds for a while
|
||||||
@ -66,7 +62,7 @@ function setupGlobs() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const appCssNoteId of glob.appCssNoteIds || []) {
|
for (const appCssNoteId of glob.appCssNoteIds || []) {
|
||||||
libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`, false);
|
requireCss(`api/notes/download/${appCssNoteId}`, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.initHelpButtons($(window));
|
utils.initHelpButtons($(window));
|
||||||
@ -78,6 +74,18 @@ function setupGlobs() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function requireCss(url: string, prependAssetPath = true) {
|
||||||
|
const cssLinks = Array.from(document.querySelectorAll("link")).map((el) => el.href);
|
||||||
|
|
||||||
|
if (!cssLinks.some((l) => l.endsWith(url))) {
|
||||||
|
if (prependAssetPath) {
|
||||||
|
url = `${window.glob.assetPath}/${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("head").append($('<link rel="stylesheet" type="text/css" />').attr("href", url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
setupGlobs
|
setupGlobs
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,158 +0,0 @@
|
|||||||
import mimeTypesService from "./mime_types.js";
|
|
||||||
import optionsService from "./options.js";
|
|
||||||
import { getStylesheetUrl } from "./syntax_highlight.js";
|
|
||||||
|
|
||||||
export interface Library {
|
|
||||||
js?: string[] | (() => string[]);
|
|
||||||
css?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const CODE_MIRROR: Library = {
|
|
||||||
js: () => {
|
|
||||||
const scriptsToLoad = [
|
|
||||||
"node_modules/codemirror/lib/codemirror.js",
|
|
||||||
"node_modules/codemirror/addon/display/placeholder.js",
|
|
||||||
"node_modules/codemirror/addon/edit/matchbrackets.js",
|
|
||||||
"node_modules/codemirror/addon/edit/matchtags.js",
|
|
||||||
"node_modules/codemirror/addon/fold/xml-fold.js",
|
|
||||||
"node_modules/codemirror/addon/lint/lint.js",
|
|
||||||
"node_modules/codemirror/addon/mode/loadmode.js",
|
|
||||||
"node_modules/codemirror/addon/mode/multiplex.js",
|
|
||||||
"node_modules/codemirror/addon/mode/overlay.js",
|
|
||||||
"node_modules/codemirror/addon/mode/simple.js",
|
|
||||||
"node_modules/codemirror/addon/search/match-highlighter.js",
|
|
||||||
"node_modules/codemirror/mode/meta.js",
|
|
||||||
"node_modules/codemirror/keymap/vim.js",
|
|
||||||
"libraries/codemirror/eslint.js"
|
|
||||||
];
|
|
||||||
|
|
||||||
const mimeTypes = mimeTypesService.getMimeTypes();
|
|
||||||
for (const mimeType of mimeTypes) {
|
|
||||||
if (mimeType.enabled && mimeType.codeMirrorSource) {
|
|
||||||
scriptsToLoad.push(mimeType.codeMirrorSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scriptsToLoad;
|
|
||||||
},
|
|
||||||
css: ["node_modules/codemirror/lib/codemirror.css", "node_modules/codemirror/addon/lint/lint.css"]
|
|
||||||
};
|
|
||||||
|
|
||||||
const KATEX: Library = {
|
|
||||||
js: ["node_modules/katex/dist/katex.min.js", "node_modules/katex/dist/contrib/mhchem.min.js", "node_modules/katex/dist/contrib/auto-render.min.js"],
|
|
||||||
css: ["node_modules/katex/dist/katex.min.css"]
|
|
||||||
};
|
|
||||||
|
|
||||||
const HIGHLIGHT_JS: Library = {
|
|
||||||
js: () => {
|
|
||||||
const mimeTypes = mimeTypesService.getMimeTypes();
|
|
||||||
const scriptsToLoad = new Set<string>();
|
|
||||||
scriptsToLoad.add("node_modules/@highlightjs/cdn-assets/highlight.min.js");
|
|
||||||
for (const mimeType of mimeTypes) {
|
|
||||||
const id = mimeType.highlightJs;
|
|
||||||
if (!mimeType.enabled || !id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mimeType.highlightJsSource === "libraries") {
|
|
||||||
scriptsToLoad.add(`libraries/highlightjs/${id}.js`);
|
|
||||||
} else {
|
|
||||||
// Built-in module.
|
|
||||||
scriptsToLoad.add(`node_modules/@highlightjs/cdn-assets/languages/${id}.min.js`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTheme = String(optionsService.get("codeBlockTheme"));
|
|
||||||
loadHighlightingTheme(currentTheme);
|
|
||||||
|
|
||||||
return Array.from(scriptsToLoad);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function requireLibrary(library: Library) {
|
|
||||||
if (library.css) {
|
|
||||||
library.css.map((cssUrl) => requireCss(cssUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (library.js) {
|
|
||||||
for (const scriptUrl of await unwrapValue(library.js)) {
|
|
||||||
await requireScript(scriptUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function unwrapValue<T>(value: T | (() => T) | Promise<T>) {
|
|
||||||
if (value && typeof value === "object" && "then" in value) {
|
|
||||||
return (await (value as Promise<() => T>))();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "function") {
|
|
||||||
return (value as () => T)();
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we save the promises in case of the same script being required concurrently multiple times
|
|
||||||
const loadedScriptPromises: Record<string, JQuery.jqXHR> = {};
|
|
||||||
|
|
||||||
async function requireScript(url: string) {
|
|
||||||
url = `${window.glob.assetPath}/${url}`;
|
|
||||||
|
|
||||||
if (!loadedScriptPromises[url]) {
|
|
||||||
loadedScriptPromises[url] = $.ajax({
|
|
||||||
url: url,
|
|
||||||
dataType: "script",
|
|
||||||
cache: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await loadedScriptPromises[url];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function requireCss(url: string, prependAssetPath = true) {
|
|
||||||
const cssLinks = Array.from(document.querySelectorAll("link")).map((el) => el.href);
|
|
||||||
|
|
||||||
if (!cssLinks.some((l) => l.endsWith(url))) {
|
|
||||||
if (prependAssetPath) {
|
|
||||||
url = `${window.glob.assetPath}/${url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("head").append($('<link rel="stylesheet" type="text/css" />').attr("href", url));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let highlightingThemeEl: JQuery<HTMLElement> | null = null;
|
|
||||||
function loadHighlightingTheme(theme: string) {
|
|
||||||
if (!theme) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theme === "none") {
|
|
||||||
// Deactivate the theme.
|
|
||||||
if (highlightingThemeEl) {
|
|
||||||
highlightingThemeEl.remove();
|
|
||||||
highlightingThemeEl = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!highlightingThemeEl) {
|
|
||||||
highlightingThemeEl = $(`<link rel="stylesheet" type="text/css" />`);
|
|
||||||
$("head").append(highlightingThemeEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = getStylesheetUrl(theme);
|
|
||||||
if (url) {
|
|
||||||
highlightingThemeEl.attr("href", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
requireCss,
|
|
||||||
requireLibrary,
|
|
||||||
loadHighlightingTheme,
|
|
||||||
CODE_MIRROR,
|
|
||||||
KATEX,
|
|
||||||
HIGHLIGHT_JS
|
|
||||||
};
|
|
||||||
@ -59,6 +59,7 @@ export interface ViewScope {
|
|||||||
* toc will appear and then close immediately, because getToc(html) function will consume time
|
* toc will appear and then close immediately, because getToc(html) function will consume time
|
||||||
*/
|
*/
|
||||||
tocPreviousVisible?: boolean;
|
tocPreviousVisible?: boolean;
|
||||||
|
tocCollapsedHeadings?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateLinkOptions {
|
interface CreateLinkOptions {
|
||||||
@ -215,9 +216,9 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
|
|||||||
const viewScope: ViewScope = {
|
const viewScope: ViewScope = {
|
||||||
viewMode: "default"
|
viewMode: "default"
|
||||||
};
|
};
|
||||||
let ntxId = null;
|
let ntxId: string | null = null;
|
||||||
let hoistedNoteId = null;
|
let hoistedNoteId: string | null = null;
|
||||||
let searchString = null;
|
let searchString: string | null = null;
|
||||||
|
|
||||||
if (paramString) {
|
if (paramString) {
|
||||||
for (const pair of paramString.split("&")) {
|
for (const pair of paramString.split("&")) {
|
||||||
|
|||||||
5
apps/client/src/services/math.ts
Normal file
5
apps/client/src/services/math.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import katex from "katex";
|
||||||
|
import "katex/contrib/mhchem";
|
||||||
|
import "katex/dist/katex.min.css";
|
||||||
|
export { default as renderMathInElement } from "katex/contrib/auto-render";
|
||||||
|
export default katex;
|
||||||
@ -1,221 +0,0 @@
|
|||||||
// TODO: deduplicate with /src/services/import/mime_type_definitions.ts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics.
|
|
||||||
*/
|
|
||||||
export const MIME_TYPE_AUTO = "text-x-trilium-auto";
|
|
||||||
|
|
||||||
export interface MimeTypeDefinition {
|
|
||||||
default?: boolean;
|
|
||||||
title: string;
|
|
||||||
mime: string;
|
|
||||||
/** The name of the language/mime type as defined by highlight.js (or one of the aliases), in order to be used for syntax highlighting such as inside code blocks. */
|
|
||||||
highlightJs?: string;
|
|
||||||
/** If specified, will load the corresponding highlight.js file from the `libraries/highlightjs/${id}.js` instead of `node_modules/@highlightjs/cdn-assets/languages/${id}.min.js`. */
|
|
||||||
highlightJsSource?: "libraries";
|
|
||||||
/** If specified, will load the corresponding highlight file from the given path instead of `node_modules`. */
|
|
||||||
codeMirrorSource?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For highlight.js-supported languages, see https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([
|
|
||||||
{ title: "Plain text", mime: "text/plain", highlightJs: "plaintext", default: true },
|
|
||||||
|
|
||||||
// Keep sorted alphabetically.
|
|
||||||
{ title: "APL", mime: "text/apl" },
|
|
||||||
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
|
|
||||||
{ title: "ASP.NET", mime: "application/x-aspx" },
|
|
||||||
{ title: "Asterisk", mime: "text/x-asterisk" },
|
|
||||||
{ title: "Batch file (DOS)", mime: "application/x-bat", highlightJs: "dos", codeMirrorSource: "libraries/codemirror/batch.js" },
|
|
||||||
{ title: "Brainfuck", mime: "text/x-brainfuck", highlightJs: "brainfuck" },
|
|
||||||
{ title: "C", mime: "text/x-csrc", highlightJs: "c", default: true },
|
|
||||||
{ title: "C#", mime: "text/x-csharp", highlightJs: "csharp", default: true },
|
|
||||||
{ title: "C++", mime: "text/x-c++src", highlightJs: "cpp", default: true },
|
|
||||||
{ title: "Clojure", mime: "text/x-clojure", highlightJs: "clojure" },
|
|
||||||
{ title: "ClojureScript", mime: "text/x-clojurescript" },
|
|
||||||
{ title: "Closure Stylesheets (GSS)", mime: "text/x-gss" },
|
|
||||||
{ title: "CMake", mime: "text/x-cmake", highlightJs: "cmake" },
|
|
||||||
{ title: "Cobol", mime: "text/x-cobol" },
|
|
||||||
{ title: "CoffeeScript", mime: "text/coffeescript", highlightJs: "coffeescript" },
|
|
||||||
{ title: "Common Lisp", mime: "text/x-common-lisp", highlightJs: "lisp" },
|
|
||||||
{ title: "CQL", mime: "text/x-cassandra" },
|
|
||||||
{ title: "Crystal", mime: "text/x-crystal", highlightJs: "crystal" },
|
|
||||||
{ title: "CSS", mime: "text/css", highlightJs: "css", default: true },
|
|
||||||
{ title: "Cypher", mime: "application/x-cypher-query" },
|
|
||||||
{ title: "Cython", mime: "text/x-cython" },
|
|
||||||
{ title: "D", mime: "text/x-d", highlightJs: "d" },
|
|
||||||
{ title: "Dart", mime: "application/dart", highlightJs: "dart" },
|
|
||||||
{ title: "diff", mime: "text/x-diff", highlightJs: "diff" },
|
|
||||||
{ title: "Django", mime: "text/x-django", highlightJs: "django" },
|
|
||||||
{ title: "Dockerfile", mime: "text/x-dockerfile", highlightJs: "dockerfile" },
|
|
||||||
{ title: "DTD", mime: "application/xml-dtd" },
|
|
||||||
{ title: "Dylan", mime: "text/x-dylan" },
|
|
||||||
{ title: "EBNF", mime: "text/x-ebnf", highlightJs: "ebnf" },
|
|
||||||
{ title: "ECL", mime: "text/x-ecl" },
|
|
||||||
{ title: "edn", mime: "application/edn" },
|
|
||||||
{ title: "Eiffel", mime: "text/x-eiffel" },
|
|
||||||
{ title: "Elm", mime: "text/x-elm", highlightJs: "elm" },
|
|
||||||
{ title: "Embedded Javascript", mime: "application/x-ejs" },
|
|
||||||
{ title: "Embedded Ruby", mime: "application/x-erb", highlightJs: "erb" },
|
|
||||||
{ title: "Erlang", mime: "text/x-erlang", highlightJs: "erlang" },
|
|
||||||
{ title: "Esper", mime: "text/x-esper" },
|
|
||||||
{ title: "F#", mime: "text/x-fsharp", highlightJs: "fsharp" },
|
|
||||||
{ title: "Factor", mime: "text/x-factor" },
|
|
||||||
{ title: "FCL", mime: "text/x-fcl" },
|
|
||||||
{ title: "Forth", mime: "text/x-forth" },
|
|
||||||
{ title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" },
|
|
||||||
{ title: "Gas", mime: "text/x-gas" },
|
|
||||||
{ title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" },
|
|
||||||
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" },
|
|
||||||
{ title: "Go", mime: "text/x-go", highlightJs: "go", default: true },
|
|
||||||
{ title: "Groovy", mime: "text/x-groovy", highlightJs: "groovy", default: true },
|
|
||||||
{ title: "HAML", mime: "text/x-haml", highlightJs: "haml" },
|
|
||||||
{ title: "Haskell (Literate)", mime: "text/x-literate-haskell" },
|
|
||||||
{ title: "Haskell", mime: "text/x-haskell", highlightJs: "haskell", default: true },
|
|
||||||
{ title: "Haxe", mime: "text/x-haxe", highlightJs: "haxe" },
|
|
||||||
{ title: "HTML", mime: "text/html", highlightJs: "xml", default: true },
|
|
||||||
{ title: "HTTP", mime: "message/http", highlightJs: "http", default: true },
|
|
||||||
{ title: "HXML", mime: "text/x-hxml" },
|
|
||||||
{ title: "IDL", mime: "text/x-idl" },
|
|
||||||
{ title: "Java Server Pages", mime: "application/x-jsp", highlightJs: "java" },
|
|
||||||
{ title: "Java", mime: "text/x-java", highlightJs: "java", default: true },
|
|
||||||
{ title: "Jinja2", mime: "text/jinja2" },
|
|
||||||
{ title: "JS backend", mime: "application/javascript;env=backend", highlightJs: "javascript", default: true },
|
|
||||||
{ title: "JS frontend", mime: "application/javascript;env=frontend", highlightJs: "javascript", default: true },
|
|
||||||
{ title: "JSON-LD", mime: "application/ld+json", highlightJs: "json" },
|
|
||||||
{ title: "JSON", mime: "application/json", highlightJs: "json", default: true },
|
|
||||||
{ title: "JSX", mime: "text/jsx", highlightJs: "javascript" },
|
|
||||||
{ title: "Julia", mime: "text/x-julia", highlightJs: "julia" },
|
|
||||||
{ title: "Kotlin", mime: "text/x-kotlin", highlightJs: "kotlin", default: true },
|
|
||||||
{ title: "LaTeX", mime: "text/x-latex", highlightJs: "latex" },
|
|
||||||
{ title: "LESS", mime: "text/x-less", highlightJs: "less" },
|
|
||||||
{ title: "LiveScript", mime: "text/x-livescript", highlightJs: "livescript" },
|
|
||||||
{ title: "Lua", mime: "text/x-lua", highlightJs: "lua" },
|
|
||||||
{ title: "MariaDB SQL", mime: "text/x-mariadb", highlightJs: "sql" },
|
|
||||||
{ title: "Markdown", mime: "text/x-markdown", highlightJs: "markdown", default: true },
|
|
||||||
{ title: "Mathematica", mime: "text/x-mathematica", highlightJs: "mathematica" },
|
|
||||||
{ title: "mbox", mime: "application/mbox" },
|
|
||||||
{ title: "MIPS Assembler", mime: "text/x-asm-mips", highlightJs: "mipsasm" },
|
|
||||||
{ title: "mIRC", mime: "text/mirc" },
|
|
||||||
{ title: "Modelica", mime: "text/x-modelica" },
|
|
||||||
{ title: "MS SQL", mime: "text/x-mssql", highlightJs: "sql" },
|
|
||||||
{ title: "mscgen", mime: "text/x-mscgen" },
|
|
||||||
{ title: "msgenny", mime: "text/x-msgenny" },
|
|
||||||
{ title: "MUMPS", mime: "text/x-mumps" },
|
|
||||||
{ title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" },
|
|
||||||
{ title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" },
|
|
||||||
{ title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" },
|
|
||||||
{ title: "NTriples", mime: "application/n-triples" },
|
|
||||||
{ title: "Objective-C", mime: "text/x-objectivec", highlightJs: "objectivec" },
|
|
||||||
{ title: "OCaml", mime: "text/x-ocaml", highlightJs: "ocaml" },
|
|
||||||
{ title: "Octave", mime: "text/x-octave" },
|
|
||||||
{ title: "Oz", mime: "text/x-oz" },
|
|
||||||
{ title: "Pascal", mime: "text/x-pascal", highlightJs: "delphi" },
|
|
||||||
{ title: "PEG.js", mime: "null" },
|
|
||||||
{ title: "Perl", mime: "text/x-perl", default: true },
|
|
||||||
{ title: "PGP", mime: "application/pgp" },
|
|
||||||
{ title: "PHP", mime: "text/x-php", default: true, highlightJs: "php" },
|
|
||||||
{ title: "Pig", mime: "text/x-pig" },
|
|
||||||
{ title: "PLSQL", mime: "text/x-plsql", highlightJs: "sql" },
|
|
||||||
{ title: "PostgreSQL", mime: "text/x-pgsql", highlightJs: "pgsql" },
|
|
||||||
{ title: "PowerShell", mime: "application/x-powershell", highlightJs: "powershell" },
|
|
||||||
{ title: "Properties files", mime: "text/x-properties", highlightJs: "properties" },
|
|
||||||
{ title: "ProtoBuf", mime: "text/x-protobuf", highlightJs: "protobuf" },
|
|
||||||
{ title: "Pug", mime: "text/x-pug" },
|
|
||||||
{ title: "Puppet", mime: "text/x-puppet", highlightJs: "puppet" },
|
|
||||||
{ title: "Python", mime: "text/x-python", highlightJs: "python", default: true },
|
|
||||||
{ title: "Q", mime: "text/x-q", highlightJs: "q" },
|
|
||||||
{ title: "R", mime: "text/x-rsrc", highlightJs: "r" },
|
|
||||||
{ title: "reStructuredText", mime: "text/x-rst" },
|
|
||||||
{ title: "RPM Changes", mime: "text/x-rpm-changes" },
|
|
||||||
{ title: "RPM Spec", mime: "text/x-rpm-spec" },
|
|
||||||
{ title: "Ruby", mime: "text/x-ruby", highlightJs: "ruby", default: true },
|
|
||||||
{ title: "Rust", mime: "text/x-rustsrc", highlightJs: "rust" },
|
|
||||||
{ title: "SAS", mime: "text/x-sas", highlightJs: "sas" },
|
|
||||||
{ title: "Sass", mime: "text/x-sass", highlightJs: "scss" },
|
|
||||||
{ title: "Scala", mime: "text/x-scala" },
|
|
||||||
{ title: "Scheme", mime: "text/x-scheme" },
|
|
||||||
{ title: "SCSS", mime: "text/x-scss", highlightJs: "scss" },
|
|
||||||
{ title: "Shell (bash)", mime: "text/x-sh", highlightJs: "bash", default: true },
|
|
||||||
{ title: "Sieve", mime: "application/sieve" },
|
|
||||||
{ title: "Slim", mime: "text/x-slim" },
|
|
||||||
{ title: "Smalltalk", mime: "text/x-stsrc", highlightJs: "smalltalk" },
|
|
||||||
{ title: "Smarty", mime: "text/x-smarty" },
|
|
||||||
{ title: "SML", mime: "text/x-sml", highlightJs: "sml" },
|
|
||||||
{ title: "Solr", mime: "text/x-solr" },
|
|
||||||
{ title: "Soy", mime: "text/x-soy" },
|
|
||||||
{ title: "SPARQL", mime: "application/sparql-query" },
|
|
||||||
{ title: "Spreadsheet", mime: "text/x-spreadsheet" },
|
|
||||||
{ title: "SQL", mime: "text/x-sql", highlightJs: "sql", default: true },
|
|
||||||
{ title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium", highlightJs: "sql", default: true },
|
|
||||||
{ title: "SQLite", mime: "text/x-sqlite", highlightJs: "sql" },
|
|
||||||
{ title: "Squirrel", mime: "text/x-squirrel" },
|
|
||||||
{ title: "sTeX", mime: "text/x-stex" },
|
|
||||||
{ title: "Stylus", mime: "text/x-styl", highlightJs: "stylus" },
|
|
||||||
{ title: "Swift", mime: "text/x-swift", default: true },
|
|
||||||
{ title: "SystemVerilog", mime: "text/x-systemverilog" },
|
|
||||||
{ title: "Tcl", mime: "text/x-tcl", highlightJs: "tcl" },
|
|
||||||
{ title: "Terraform (HCL)", mime: "text/x-hcl", highlightJs: "terraform", highlightJsSource: "libraries", codeMirrorSource: "libraries/codemirror/hcl.js" },
|
|
||||||
{ title: "Textile", mime: "text/x-textile" },
|
|
||||||
{ title: "TiddlyWiki ", mime: "text/x-tiddlywiki" },
|
|
||||||
{ title: "Tiki wiki", mime: "text/tiki" },
|
|
||||||
{ title: "TOML", mime: "text/x-toml", highlightJs: "ini" },
|
|
||||||
{ title: "Tornado", mime: "text/x-tornado" },
|
|
||||||
{ title: "troff", mime: "text/troff" },
|
|
||||||
{ title: "TTCN_CFG", mime: "text/x-ttcn-cfg" },
|
|
||||||
{ title: "TTCN", mime: "text/x-ttcn" },
|
|
||||||
{ title: "Turtle", mime: "text/turtle" },
|
|
||||||
{ title: "Twig", mime: "text/x-twig", highlightJs: "twig" },
|
|
||||||
{ title: "TypeScript-JSX", mime: "text/typescript-jsx", highlightJs: "typescript" },
|
|
||||||
{ title: "TypeScript", mime: "application/typescript", highlightJs: "typescript" },
|
|
||||||
{ title: "VB.NET", mime: "text/x-vb", highlightJs: "vbnet" },
|
|
||||||
{ title: "VBScript", mime: "text/vbscript", highlightJs: "vbscript" },
|
|
||||||
{ title: "Velocity", mime: "text/velocity" },
|
|
||||||
{ title: "Verilog", mime: "text/x-verilog", highlightJs: "verilog" },
|
|
||||||
{ title: "VHDL", mime: "text/x-vhdl", highlightJs: "vhdl" },
|
|
||||||
{ title: "Vue.js Component", mime: "text/x-vue" },
|
|
||||||
{ title: "Web IDL", mime: "text/x-webidl" },
|
|
||||||
{ title: "XML", mime: "text/xml", highlightJs: "xml", default: true },
|
|
||||||
{ title: "XQuery", mime: "application/xquery", highlightJs: "xquery" },
|
|
||||||
{ title: "xu", mime: "text/x-xu" },
|
|
||||||
{ title: "Yacas", mime: "text/x-yacas" },
|
|
||||||
{ title: "YAML", mime: "text/x-yaml", highlightJs: "yaml", default: true },
|
|
||||||
{ title: "Z80", mime: "text/x-z80" }
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a MIME type in the usual format (e.g. `text/csrc`), it returns a MIME type that can be passed down to the CKEditor
|
|
||||||
* code plugin.
|
|
||||||
*
|
|
||||||
* @param mimeType The MIME type to normalize, in the usual format (e.g. `text/c-src`).
|
|
||||||
* @returns the normalized MIME type (e.g. `text-c-src`).
|
|
||||||
*/
|
|
||||||
export function normalizeMimeTypeForCKEditor(mimeType: string) {
|
|
||||||
return mimeType.toLowerCase().replace(/[\W_]+/g, "-");
|
|
||||||
}
|
|
||||||
|
|
||||||
let byHighlightJsNameMappings: Record<string, MimeTypeDefinition> | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a Highlight.js language tag (e.g. `css`), it returns a corresponding {@link MimeTypeDefinition} if found.
|
|
||||||
*
|
|
||||||
* If there are multiple {@link MimeTypeDefinition}s for the language tag, then only the first one is retrieved. For example for `javascript`, the "JS frontend" mime type is returned.
|
|
||||||
*
|
|
||||||
* @param highlightJsName a language tag.
|
|
||||||
* @returns the corresponding {@link MimeTypeDefinition} if found, or `undefined` otherwise.
|
|
||||||
*/
|
|
||||||
export function getMimeTypeFromHighlightJs(highlightJsName: string) {
|
|
||||||
if (!byHighlightJsNameMappings) {
|
|
||||||
byHighlightJsNameMappings = {};
|
|
||||||
for (const mimeType of MIME_TYPES_DICT) {
|
|
||||||
if (mimeType.highlightJs && !byHighlightJsNameMappings[mimeType.highlightJs]) {
|
|
||||||
byHighlightJsNameMappings[mimeType.highlightJs] = mimeType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return byHighlightJsNameMappings[highlightJsName];
|
|
||||||
}
|
|
||||||
@ -1,13 +1,6 @@
|
|||||||
import { MIME_TYPE_AUTO, MIME_TYPES_DICT, normalizeMimeTypeForCKEditor, type MimeTypeDefinition } from "./mime_type_definitions.js";
|
import { normalizeMimeTypeForCKEditor, type MimeType, MIME_TYPE_AUTO, MIME_TYPES_DICT } from "@triliumnext/commons";
|
||||||
import options from "./options.js";
|
import options from "./options.js";
|
||||||
|
|
||||||
interface MimeType extends MimeTypeDefinition {
|
|
||||||
/**
|
|
||||||
* True if this mime type was enabled by the user in the "Available MIME types in the dropdown" option in the Code Notes settings.
|
|
||||||
*/
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mimeTypes: MimeType[] | null = null;
|
let mimeTypes: MimeType[] | null = null;
|
||||||
|
|
||||||
function loadMimeTypes() {
|
function loadMimeTypes() {
|
||||||
@ -45,8 +38,8 @@ export function getHighlightJsNameForMime(mimeType: string) {
|
|||||||
for (const mimeType of mimeTypes) {
|
for (const mimeType of mimeTypes) {
|
||||||
// The mime stored by CKEditor is text-x-csrc instead of text/x-csrc so we keep this format for faster lookup.
|
// The mime stored by CKEditor is text-x-csrc instead of text/x-csrc so we keep this format for faster lookup.
|
||||||
const normalizedMime = normalizeMimeTypeForCKEditor(mimeType.mime);
|
const normalizedMime = normalizeMimeTypeForCKEditor(mimeType.mime);
|
||||||
if (mimeType.highlightJs) {
|
if (mimeType.mdLanguageCode) {
|
||||||
mimeToHighlightJsMapping[normalizedMime] = mimeType.highlightJs;
|
mimeToHighlightJsMapping[normalizedMime] = mimeType.mdLanguageCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,10 @@ import appContext from "../components/app_context.js";
|
|||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import { t } from "./i18n.js";
|
import { t } from "./i18n.js";
|
||||||
|
|
||||||
|
// Track all elements that open tooltips
|
||||||
|
let openTooltipElements: JQuery<HTMLElement>[] = [];
|
||||||
|
let dismissTimer: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
function setupGlobalTooltip() {
|
function setupGlobalTooltip() {
|
||||||
$(document).on("mouseenter", "a", mouseEnterHandler);
|
$(document).on("mouseenter", "a", mouseEnterHandler);
|
||||||
|
|
||||||
@ -23,7 +27,12 @@ function setupGlobalTooltip() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dismissAllTooltips() {
|
function dismissAllTooltips() {
|
||||||
$(".note-tooltip").remove();
|
clearTimeout(dismissTimer);
|
||||||
|
openTooltipElements.forEach($el => {
|
||||||
|
$el.tooltip("dispose");
|
||||||
|
$el.removeAttr("aria-describedby");
|
||||||
|
});
|
||||||
|
openTooltipElements = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupElementTooltip($el: JQuery<HTMLElement>) {
|
function setupElementTooltip($el: JQuery<HTMLElement>) {
|
||||||
@ -86,8 +95,8 @@ async function mouseEnterHandler(this: HTMLElement) {
|
|||||||
// we need to check if we're still hovering over the element
|
// we need to check if we're still hovering over the element
|
||||||
// since the operation to get tooltip content was async, it is possible that
|
// since the operation to get tooltip content was async, it is possible that
|
||||||
// we now create tooltip which won't close because it won't receive mouseleave event
|
// we now create tooltip which won't close because it won't receive mouseleave event
|
||||||
if ($(this).filter(":hover").length > 0) {
|
if ($link.filter(":hover").length > 0) {
|
||||||
$(this).tooltip({
|
$link.tooltip({
|
||||||
container: "body",
|
container: "body",
|
||||||
// https://github.com/zadam/trilium/issues/2794 https://github.com/zadam/trilium/issues/2988
|
// https://github.com/zadam/trilium/issues/2794 https://github.com/zadam/trilium/issues/2988
|
||||||
// with bottom this flickering happens a bit less
|
// with bottom this flickering happens a bit less
|
||||||
@ -103,7 +112,9 @@ async function mouseEnterHandler(this: HTMLElement) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
dismissAllTooltips();
|
dismissAllTooltips();
|
||||||
$(this).tooltip("show");
|
$link.tooltip("show");
|
||||||
|
|
||||||
|
openTooltipElements.push($link);
|
||||||
|
|
||||||
// Dismiss the tooltip immediately if a link was clicked inside the tooltip.
|
// Dismiss the tooltip immediately if a link was clicked inside the tooltip.
|
||||||
$(`.${tooltipClass} a`).on("click", (e) => {
|
$(`.${tooltipClass} a`).on("click", (e) => {
|
||||||
@ -115,15 +126,16 @@ async function mouseEnterHandler(this: HTMLElement) {
|
|||||||
// click on links within tooltip etc. without tooltip disappearing
|
// click on links within tooltip etc. without tooltip disappearing
|
||||||
// - once the user moves the cursor away from both link and the tooltip, hide the tooltip
|
// - once the user moves the cursor away from both link and the tooltip, hide the tooltip
|
||||||
const checkTooltip = () => {
|
const checkTooltip = () => {
|
||||||
if (!$(this).filter(":hover").length && !$(`.${linkId}:hover`).length) {
|
|
||||||
|
if (!$link.filter(":hover").length && !$(`.${linkId}:hover`).length) {
|
||||||
// cursor is neither over the link nor over the tooltip, user likely is not interested
|
// cursor is neither over the link nor over the tooltip, user likely is not interested
|
||||||
dismissAllTooltips();
|
dismissAllTooltips();
|
||||||
} else {
|
} else {
|
||||||
setTimeout(checkTooltip, 1000);
|
dismissTimer = setTimeout(checkTooltip, 1000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setTimeout(checkTooltip, 1000);
|
dismissTimer = setTimeout(checkTooltip, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +188,25 @@ function renderFootnote($link: JQuery<HTMLElement>, url: string) {
|
|||||||
.closest(".footnote-item") // find the parent container of the footnote
|
.closest(".footnote-item") // find the parent container of the footnote
|
||||||
.find(".footnote-content"); // find the actual text content of the footnote
|
.find(".footnote-content"); // find the actual text content of the footnote
|
||||||
|
|
||||||
return $footnoteContent.html() || "";
|
const isEditable = $link.closest(".ck-content").hasClass("note-detail-editable-text-editor");
|
||||||
|
if (isEditable) {
|
||||||
|
/* Remove widget buttons for tables, formulas, and images in editable notes. */
|
||||||
|
$footnoteContent.find('.ck-widget__selection-handle').remove();
|
||||||
|
$footnoteContent.find('.ck-widget__type-around').remove();
|
||||||
|
$footnoteContent.find('.ck-widget__resizer').remove();
|
||||||
|
|
||||||
|
/* Handling in-line math formulas */
|
||||||
|
$footnoteContent.find('.ck-math-tex.ck-math-tex-inline.ck-widget').each(function () {
|
||||||
|
const $katex = $(this).find('.katex').first();
|
||||||
|
if ($katex.length) {
|
||||||
|
$(this).replaceWith($('<span class="math-tex"></span>').append($('<span></span>').append($katex.clone())));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let footnoteContent = $footnoteContent.html();
|
||||||
|
footnoteContent = `<div class="ck-content">${footnoteContent}</div>`
|
||||||
|
return footnoteContent || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
|
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
|
||||||
type Multiplicity = "single" | "multi";
|
type Multiplicity = "single" | "multi";
|
||||||
|
|
||||||
interface DefinitionObject {
|
export interface DefinitionObject {
|
||||||
isPromoted?: boolean;
|
isPromoted?: boolean;
|
||||||
labelType?: LabelType;
|
labelType?: LabelType;
|
||||||
multiplicity?: Multiplicity;
|
multiplicity?: Multiplicity;
|
||||||
|
|||||||
@ -3,7 +3,11 @@ import Split from "split.js"
|
|||||||
|
|
||||||
export const DEFAULT_GUTTER_SIZE = 5;
|
export const DEFAULT_GUTTER_SIZE = 5;
|
||||||
|
|
||||||
|
let leftPaneWidth: number;
|
||||||
|
let reservedPx: number;
|
||||||
|
let layoutOrientation: string;
|
||||||
let leftInstance: ReturnType<typeof Split> | null;
|
let leftInstance: ReturnType<typeof Split> | null;
|
||||||
|
let rightPaneWidth: number;
|
||||||
let rightInstance: ReturnType<typeof Split> | null;
|
let rightInstance: ReturnType<typeof Split> | null;
|
||||||
|
|
||||||
function setupLeftPaneResizer(leftPaneVisible: boolean) {
|
function setupLeftPaneResizer(leftPaneVisible: boolean) {
|
||||||
@ -14,27 +18,34 @@ function setupLeftPaneResizer(leftPaneVisible: boolean) {
|
|||||||
|
|
||||||
$("#left-pane").toggle(leftPaneVisible);
|
$("#left-pane").toggle(leftPaneVisible);
|
||||||
|
|
||||||
|
layoutOrientation = layoutOrientation ?? options.get("layoutOrientation");
|
||||||
|
reservedPx = reservedPx ?? (layoutOrientation === "vertical" ? ($("#launcher-pane").outerWidth() || 0) : 0);
|
||||||
|
// Window resizing causes `window.innerWidth` to change, so `reservedWidth` needs to be recalculated each time.
|
||||||
|
const reservedWidth = reservedPx / window.innerWidth * 100;
|
||||||
if (!leftPaneVisible) {
|
if (!leftPaneVisible) {
|
||||||
$("#rest-pane").css("width", "100%");
|
$("#rest-pane").css("width", layoutOrientation === "vertical" ? `${100 - reservedWidth}%` : "100%");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let leftPaneWidth = options.getInt("leftPaneWidth");
|
leftPaneWidth = leftPaneWidth ?? (options.getInt("leftPaneWidth") ?? 0);
|
||||||
if (!leftPaneWidth || leftPaneWidth < 5) {
|
if (!leftPaneWidth || leftPaneWidth < 5) {
|
||||||
leftPaneWidth = 5;
|
leftPaneWidth = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const restPaneWidth = 100 - leftPaneWidth - reservedWidth;
|
||||||
if (leftPaneVisible) {
|
if (leftPaneVisible) {
|
||||||
// Delayed initialization ensures that all DOM elements are fully rendered and part of the layout,
|
// Delayed initialization ensures that all DOM elements are fully rendered and part of the layout,
|
||||||
// preventing Split.js from retrieving incorrect dimensions due to #left-pane not being rendered yet,
|
// preventing Split.js from retrieving incorrect dimensions due to #left-pane not being rendered yet,
|
||||||
// which would cause the minSize setting to have no effect.
|
// which would cause the minSize setting to have no effect.
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
leftInstance = Split(["#left-pane", "#rest-pane"], {
|
leftInstance = Split(["#left-pane", "#rest-pane"], {
|
||||||
sizes: [leftPaneWidth, 100 - leftPaneWidth],
|
sizes: [leftPaneWidth, restPaneWidth],
|
||||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||||
minSize: [150, 300],
|
minSize: [150, 300],
|
||||||
onDragEnd: (sizes) => options.save("leftPaneWidth", Math.round(sizes[0]))
|
onDragEnd: (sizes) => {
|
||||||
|
leftPaneWidth = Math.round(sizes[0]);
|
||||||
|
options.save("leftPaneWidth", Math.round(sizes[0]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -54,7 +65,7 @@ function setupRightPaneResizer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rightPaneWidth = options.getInt("rightPaneWidth");
|
rightPaneWidth = rightPaneWidth ?? (options.getInt("rightPaneWidth") ?? 0);
|
||||||
if (!rightPaneWidth || rightPaneWidth < 5) {
|
if (!rightPaneWidth || rightPaneWidth < 5) {
|
||||||
rightPaneWidth = 5;
|
rightPaneWidth = 5;
|
||||||
}
|
}
|
||||||
@ -63,8 +74,11 @@ function setupRightPaneResizer() {
|
|||||||
rightInstance = Split(["#center-pane", "#right-pane"], {
|
rightInstance = Split(["#center-pane", "#right-pane"], {
|
||||||
sizes: [100 - rightPaneWidth, rightPaneWidth],
|
sizes: [100 - rightPaneWidth, rightPaneWidth],
|
||||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||||
minSize: [ 300, 180 ],
|
minSize: [300, 180],
|
||||||
onDragEnd: (sizes) => options.save("rightPaneWidth", Math.round(sizes[1]))
|
onDragEnd: (sizes) => {
|
||||||
|
rightPaneWidth = Math.round(sizes[1]);
|
||||||
|
options.save("rightPaneWidth", Math.round(sizes[1]));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,8 +58,11 @@ async function getWithSilentNotFound<T>(url: string, componentId?: string) {
|
|||||||
return await call<T>("GET", url, componentId, { silentNotFound: true });
|
return await call<T>("GET", url, componentId, { silentNotFound: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get<T>(url: string, componentId?: string) {
|
/**
|
||||||
return await call<T>("GET", url, componentId);
|
* @param raw if `true`, the value will be returned as a string instead of a JavaScript object if JSON, XMLDocument if XML, etc.
|
||||||
|
*/
|
||||||
|
async function get<T>(url: string, componentId?: string, raw?: boolean) {
|
||||||
|
return await call<T>("GET", url, componentId, { raw });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function post<T>(url: string, data?: unknown, componentId?: string) {
|
async function post<T>(url: string, data?: unknown, componentId?: string) {
|
||||||
@ -102,6 +105,8 @@ let maxKnownEntityChangeId = 0;
|
|||||||
interface CallOptions {
|
interface CallOptions {
|
||||||
data?: unknown;
|
data?: unknown;
|
||||||
silentNotFound?: boolean;
|
silentNotFound?: boolean;
|
||||||
|
// If `true`, the value will be returned as a string instead of a JavaScript object if JSON, XMLDocument if XML, etc.
|
||||||
|
raw?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function call<T>(method: string, url: string, componentId?: string, options: CallOptions = {}) {
|
async function call<T>(method: string, url: string, componentId?: string, options: CallOptions = {}) {
|
||||||
@ -132,7 +137,7 @@ async function call<T>(method: string, url: string, componentId?: string, option
|
|||||||
});
|
});
|
||||||
})) as any;
|
})) as any;
|
||||||
} else {
|
} else {
|
||||||
resp = await ajax(url, method, data, headers, !!options.silentNotFound);
|
resp = await ajax(url, method, data, headers, !!options.silentNotFound, options.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxEntityChangeIdStr = resp.headers["trilium-max-entity-change-id"];
|
const maxEntityChangeIdStr = resp.headers["trilium-max-entity-change-id"];
|
||||||
@ -144,7 +149,10 @@ async function call<T>(method: string, url: string, componentId?: string, option
|
|||||||
return resp.body as T;
|
return resp.body as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ajax(url: string, method: string, data: unknown, headers: Headers, silentNotFound: boolean): Promise<Response> {
|
/**
|
||||||
|
* @param raw if `true`, the value will be returned as a string instead of a JavaScript object if JSON, XMLDocument if XML, etc.
|
||||||
|
*/
|
||||||
|
function ajax(url: string, method: string, data: unknown, headers: Headers, silentNotFound: boolean, raw?: boolean): Promise<Response> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const options: JQueryAjaxSettings = {
|
const options: JQueryAjaxSettings = {
|
||||||
url: window.glob.baseApiUrl + url,
|
url: window.glob.baseApiUrl + url,
|
||||||
@ -186,6 +194,10 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (raw) {
|
||||||
|
options.dataType = "text";
|
||||||
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
try {
|
try {
|
||||||
options.data = JSON.stringify(data);
|
options.data = JSON.stringify(data);
|
||||||
|
|||||||
@ -1,28 +1,21 @@
|
|||||||
import library_loader from "./library_loader.js";
|
import { ensureMimeTypes, highlight, highlightAuto, loadTheme, Themes, type AutoHighlightResult, type HighlightResult, type Theme } from "@triliumnext/highlightjs";
|
||||||
import mime_types from "./mime_types.js";
|
import mime_types from "./mime_types.js";
|
||||||
import options from "./options.js";
|
import options from "./options.js";
|
||||||
|
import { t } from "./i18n.js";
|
||||||
|
import { copyText } from "./clipboard.js";
|
||||||
|
|
||||||
export function getStylesheetUrl(theme: string) {
|
let highlightingLoaded = false;
|
||||||
if (!theme) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultPrefix = "default:";
|
|
||||||
if (theme.startsWith(defaultPrefix)) {
|
|
||||||
return `${window.glob.assetPath}/node_modules/@highlightjs/cdn-assets/styles/${theme.substr(defaultPrefix.length)}.min.css`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies all the code blocks (as `pre code`) under the specified hierarchy and uses the highlight.js library to obtain the highlighted text which is then applied on to the code blocks.
|
* Identifies all the code blocks (as `pre code`) under the specified hierarchy and uses the highlight.js library to obtain the highlighted text which is then applied on to the code blocks.
|
||||||
|
* Additionally, adds a "Copy to clipboard" button.
|
||||||
*
|
*
|
||||||
* @param $container the container under which to look for code blocks and to apply syntax highlighting to them.
|
* @param $container the container under which to look for code blocks and to apply syntax highlighting to them.
|
||||||
*/
|
*/
|
||||||
export async function applySyntaxHighlight($container: JQuery<HTMLElement>) {
|
export async function formatCodeBlocks($container: JQuery<HTMLElement>) {
|
||||||
if (!isSyntaxHighlightEnabled()) {
|
const syntaxHighlightingEnabled = isSyntaxHighlightEnabled();
|
||||||
return;
|
if (syntaxHighlightingEnabled) {
|
||||||
|
await ensureMimeTypesForHighlighting();
|
||||||
}
|
}
|
||||||
|
|
||||||
const codeBlocks = $container.find("pre code");
|
const codeBlocks = $container.find("pre code");
|
||||||
@ -32,10 +25,22 @@ export async function applySyntaxHighlight($container: JQuery<HTMLElement>) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
applySingleBlockSyntaxHighlight($(codeBlock), normalizedMimeType);
|
applyCopyToClipboardButton($(codeBlock));
|
||||||
|
|
||||||
|
if (syntaxHighlightingEnabled) {
|
||||||
|
applySingleBlockSyntaxHighlight($(codeBlock), normalizedMimeType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyCopyToClipboardButton($codeBlock: JQuery<HTMLElement>) {
|
||||||
|
const $copyButton = $("<button>")
|
||||||
|
.addClass("bx component icon-action tn-tool-button bx-copy copy-button")
|
||||||
|
.attr("title", t("code_block.copy_title"))
|
||||||
|
.on("click", () => copyText($codeBlock.text()));
|
||||||
|
$codeBlock.parent().append($copyButton);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies syntax highlight to the given code block (assumed to be <pre><code>), using highlight.js.
|
* Applies syntax highlight to the given code block (assumed to be <pre><code>), using highlight.js.
|
||||||
*/
|
*/
|
||||||
@ -43,20 +48,13 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
|
|||||||
$codeBlock.parent().toggleClass("hljs");
|
$codeBlock.parent().toggleClass("hljs");
|
||||||
const text = $codeBlock.text();
|
const text = $codeBlock.text();
|
||||||
|
|
||||||
if (!window.hljs) {
|
let highlightedText: HighlightResult | AutoHighlightResult | null = null;
|
||||||
await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
|
|
||||||
}
|
|
||||||
|
|
||||||
let highlightedText = null;
|
|
||||||
if (normalizedMimeType === mime_types.MIME_TYPE_AUTO) {
|
if (normalizedMimeType === mime_types.MIME_TYPE_AUTO) {
|
||||||
highlightedText = hljs.highlightAuto(text);
|
await ensureMimeTypesForHighlighting();
|
||||||
|
highlightedText = highlightAuto(text);
|
||||||
} else if (normalizedMimeType) {
|
} else if (normalizedMimeType) {
|
||||||
const language = mime_types.getHighlightJsNameForMime(normalizedMimeType);
|
await ensureMimeTypesForHighlighting();
|
||||||
if (language) {
|
highlightedText = highlight(text, { language: normalizedMimeType });
|
||||||
highlightedText = hljs.highlight(text, { language });
|
|
||||||
} else {
|
|
||||||
console.warn(`Unknown mime type: ${normalizedMimeType}.`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highlightedText) {
|
if (highlightedText) {
|
||||||
@ -64,13 +62,42 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function ensureMimeTypesForHighlighting() {
|
||||||
|
if (highlightingLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load theme.
|
||||||
|
const currentThemeName = String(options.get("codeBlockTheme"));
|
||||||
|
loadHighlightingTheme(currentThemeName);
|
||||||
|
|
||||||
|
// Load mime types.
|
||||||
|
const mimeTypes = mime_types.getMimeTypes();
|
||||||
|
await ensureMimeTypes(mimeTypes);
|
||||||
|
|
||||||
|
highlightingLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadHighlightingTheme(themeName: string) {
|
||||||
|
const themePrefix = "default:";
|
||||||
|
let theme: Theme | null = null;
|
||||||
|
if (themeName.includes(themePrefix)) {
|
||||||
|
theme = Themes[themeName.substring(themePrefix.length)];
|
||||||
|
}
|
||||||
|
if (!theme) {
|
||||||
|
theme = Themes.default;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTheme(theme);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether syntax highlighting should be enabled for code blocks, by querying the value of the `codeblockTheme` option.
|
* Indicates whether syntax highlighting should be enabled for code blocks, by querying the value of the `codeblockTheme` option.
|
||||||
* @returns whether syntax highlighting should be enabled for code blocks.
|
* @returns whether syntax highlighting should be enabled for code blocks.
|
||||||
*/
|
*/
|
||||||
export function isSyntaxHighlightEnabled() {
|
export function isSyntaxHighlightEnabled() {
|
||||||
const theme = options.get("codeBlockTheme");
|
const theme = options.get("codeBlockTheme");
|
||||||
return theme && theme !== "none";
|
return !!theme && theme !== "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -34,8 +34,8 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root
|
|||||||
path.push("root");
|
path.push("root");
|
||||||
}
|
}
|
||||||
|
|
||||||
const effectivePathSegments = [];
|
const effectivePathSegments: string[] = [];
|
||||||
let childNoteId = null;
|
let childNoteId: string | null = null;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -197,7 +197,7 @@ function getNotePath(node: Fancytree.FancytreeNode) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = [];
|
const path: string[] = [];
|
||||||
|
|
||||||
while (node) {
|
while (node) {
|
||||||
if (node.data.noteId) {
|
if (node.data.noteId) {
|
||||||
@ -236,7 +236,7 @@ async function getNoteTitle(noteId: string, parentNoteId: string | null = null)
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getNotePathTitleComponents(notePath: string) {
|
async function getNotePathTitleComponents(notePath: string) {
|
||||||
const titleComponents = [];
|
const titleComponents: string[] = [];
|
||||||
|
|
||||||
if (notePath.startsWith("root/")) {
|
if (notePath.startsWith("root/")) {
|
||||||
notePath = notePath.substr(5);
|
notePath = notePath.substr(5);
|
||||||
|
|||||||
@ -51,7 +51,7 @@ function getMonthsInDateRange(startDate: string, endDate: string) {
|
|||||||
const end = endDate.split("-");
|
const end = endDate.split("-");
|
||||||
const startYear = parseInt(start[0]);
|
const startYear = parseInt(start[0]);
|
||||||
const endYear = parseInt(end[0]);
|
const endYear = parseInt(end[0]);
|
||||||
const dates = [];
|
const dates: string[] = [];
|
||||||
|
|
||||||
for (let i = startYear; i <= endYear; i++) {
|
for (let i = startYear; i <= endYear; i++) {
|
||||||
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
|
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
|
||||||
@ -84,7 +84,7 @@ function formatTimeInterval(ms: number) {
|
|||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
const days = Math.floor(hours / 24);
|
const days = Math.floor(hours / 24);
|
||||||
const plural = (count: number, name: string) => `${count} ${name}${count > 1 ? "s" : ""}`;
|
const plural = (count: number, name: string) => `${count} ${name}${count > 1 ? "s" : ""}`;
|
||||||
const segments = [];
|
const segments: string[] = [];
|
||||||
|
|
||||||
if (days > 0) {
|
if (days > 0) {
|
||||||
segments.push(plural(days, "day"));
|
segments.push(plural(days, "day"));
|
||||||
@ -149,7 +149,7 @@ function isMac() {
|
|||||||
|
|
||||||
export const hasTouchBar = (isMac() && isElectron());
|
export const hasTouchBar = (isMac() && isElectron());
|
||||||
|
|
||||||
function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement>) {
|
function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement> | JQueryEventObject) {
|
||||||
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
|
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import "jquery";
|
||||||
|
import "jquery-hotkeys";
|
||||||
import utils from "./services/utils.js";
|
import utils from "./services/utils.js";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import "./stylesheets/bootstrap.scss";
|
import "./stylesheets/bootstrap.scss";
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import "./stylesheets/bootstrap.scss";
|
import "normalize.css";
|
||||||
|
import "@triliumnext/ckeditor5/content.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch note with given ID from backend
|
* Fetch note with given ID from backend
|
||||||
|
|||||||
@ -272,4 +272,179 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: var(--muted-text-color);
|
color: var(--muted-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thinking display styles */
|
||||||
|
.llm-thinking-container {
|
||||||
|
margin: 1rem 0;
|
||||||
|
animation: fadeInUp 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-bubble {
|
||||||
|
background-color: var(--accented-background-color, var(--main-background-color));
|
||||||
|
border: 1px solid var(--subtle-border-color, var(--main-border-color));
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-bubble:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-bubble::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, var(--hover-item-background-color, rgba(0, 0, 0, 0.03)), transparent);
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-header {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-header:hover {
|
||||||
|
background-color: var(--hover-item-background-color, rgba(0, 0, 0, 0.03));
|
||||||
|
padding: 0.25rem;
|
||||||
|
margin: -0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-dots span {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: var(--link-color, var(--hover-item-text-color));
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: thinkingPulse 1.4s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-dots span:nth-child(1) {
|
||||||
|
animation-delay: -0.32s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-dots span:nth-child(2) {
|
||||||
|
animation-delay: -0.16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-dots span:nth-child(3) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--link-color, var(--hover-item-text-color)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-toggle {
|
||||||
|
color: var(--muted-text-color) !important;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-toggle:hover {
|
||||||
|
color: var(--main-text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-toggle.expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-content {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
border-top: 1px solid var(--subtle-border-color, var(--main-border-color));
|
||||||
|
animation: expandDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-text {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--main-text-color);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: var(--input-background-color);
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--subtle-border-color, var(--main-border-color));
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-text:hover {
|
||||||
|
border-color: var(--main-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes thinkingPulse {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
transform: scale(0.8);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
left: -100%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes expandDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.thinking-bubble {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thinking-text {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -15,6 +15,18 @@
|
|||||||
src: url(../fonts/JetBrainsMono-Light.woff2) format("woff");
|
src: url(../fonts/JetBrainsMono-Light.woff2) format("woff");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--admonition-note-accent-color: #69c7ff;
|
||||||
|
--admonition-tip-accent-color: #40c025;
|
||||||
|
--admonition-important-accent-color: #9839f7;
|
||||||
|
--admonition-caution-accent-color: #ff2e2e;
|
||||||
|
--admonition-warning-accent-color: #e2aa03;
|
||||||
|
--bs-body-font-family: var(--main-font-family) !important;
|
||||||
|
--bs-body-font-weight: var(--main-font-weight) !important;
|
||||||
|
--bs-body-color: var(--main-text-color) !important;
|
||||||
|
--bs-body-bg: var(--main-background-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
--bs-table-bg: transparent !important;
|
--bs-table-bg: transparent !important;
|
||||||
}
|
}
|
||||||
@ -420,33 +432,24 @@ body.desktop #context-menu-container .dropdown-item > span {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror {
|
.cm-editor {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: inherit;
|
outline: none !important;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body .CodeMirror {
|
body .cm-editor {
|
||||||
font-size: var(--monospace-font-size);
|
font-size: var(--monospace-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-gutters {
|
body .cm-editor .cm-gutters {
|
||||||
background-color: inherit !important;
|
background-color: inherit !important;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-matchhighlight {
|
body .cm-editor .cm-placeholder {
|
||||||
background-color: #eeeeee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-matchhighlight.ck-find-result{
|
|
||||||
background: var(--ck-color-highlight-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-matchhighlight.ck-find-result_selected {
|
|
||||||
background-color: #ff9633;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror pre.CodeMirror-placeholder {
|
|
||||||
color: #999 !important;
|
color: #999 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,11 +460,11 @@ body .CodeMirror {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sql-console-query .CodeMirror {
|
#sql-console-query .cm-editor {
|
||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sql-console-query .CodeMirror-scroll {
|
#sql-console-query .cm-editor .cm-scroller {
|
||||||
min-height: inherit !important;
|
min-height: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,12 +527,40 @@ button.btn-sm {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre:not(.CodeMirror-line):not(.hljs) {
|
pre:not(.hljs) {
|
||||||
color: var(--main-text-color) !important;
|
color: var(--main-text-color) !important;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root pre {
|
||||||
|
--padding-size: 1em;
|
||||||
|
--copy-button-margin-size: .35em;
|
||||||
|
--copy-button-width: 37px;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
padding: var(--padding-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre > button.copy-button {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--copy-button-margin-size);
|
||||||
|
right: var(--copy-button-margin-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root pre:has(> button.copy-button) {
|
||||||
|
padding-right: calc(var(--copy-button-width) + (var(--copy-button-margin-size) * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
pre > button.copy-button:hover {
|
||||||
|
color: inherit !important;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre > button.copy-button:active {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
.pointer {
|
.pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -599,11 +630,6 @@ table.promoted-attributes-in-tooltip th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-trigger {
|
.tooltip-trigger {
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: transparent;
|
background: transparent;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@ -1016,9 +1042,10 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
|||||||
font-size: var(--detail-font-size) !important;
|
font-size: var(--detail-font-size) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ck-mentions .ck-button.ck-on {
|
.ck-mentions {
|
||||||
background-color: var(--active-item-background-color) !important;
|
--ck-color-list-button-on-background: var(--active-item-background-color);
|
||||||
color: var(--active-item-text-color) !important;
|
--ck-color-list-button-on-background-focus: var(--ck-color-list-button-on-background);
|
||||||
|
--ck-color-list-button-on-text: var(--active-item-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ck-mentions .ck-button b {
|
.ck-mentions .ck-button b {
|
||||||
@ -1222,6 +1249,10 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
|||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background-color: var(--selection-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
[data-bs-toggle="tooltip"]:not(.button-widget) span {
|
[data-bs-toggle="tooltip"]:not(.button-widget) span {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
border-bottom: 1px dotted;
|
border-bottom: 1px dotted;
|
||||||
@ -1778,7 +1809,7 @@ body.zen .title-row {
|
|||||||
height: unset !important;
|
height: unset !important;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
padding-left: env(titlebar-area-x);
|
padding-left: env(titlebar-area-x);
|
||||||
padding-right: 2.5em;
|
padding-right: calc(100vw - env(titlebar-area-width, 100vw) + 2.5em);
|
||||||
}
|
}
|
||||||
|
|
||||||
body.zen .floating-buttons {
|
body.zen .floating-buttons {
|
||||||
@ -2018,11 +2049,11 @@ footer.file-footer button {
|
|||||||
left: 1em;
|
left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admonition.note { --accent-color: #69c7ff; }
|
.admonition.note { --accent-color: var(--admonition-note-accent-color); }
|
||||||
.admonition.tip { --accent-color: #40c025; }
|
.admonition.tip { --accent-color: var(--admonition-tip-accent-color); }
|
||||||
.admonition.important { --accent-color: #9839f7; }
|
.admonition.important { --accent-color: var(--admonition-important-accent-color); }
|
||||||
.admonition.caution { --accent-color: #ff2e2e; }
|
.admonition.caution { --accent-color: var(--admonition-caution-accent-color); }
|
||||||
.admonition.warning { --accent-color: #e2aa03; }
|
.admonition.warning { --accent-color: var(--admonition-warning-accent-color); }
|
||||||
|
|
||||||
.admonition.note::before { content: "\eb21"; }
|
.admonition.note::before { content: "\eb21"; }
|
||||||
.admonition.tip::before { content: "\ea0d"; }
|
.admonition.tip::before { content: "\ea0d"; }
|
||||||
|
|||||||
@ -81,10 +81,6 @@ body ::-webkit-calendar-picker-indicator {
|
|||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body .CodeMirror {
|
|
||||||
filter: invert(90%) hue-rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw.theme--dark {
|
.excalidraw.theme--dark {
|
||||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,12 @@
|
|||||||
* Color scheme
|
* Color scheme
|
||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ⚠️ NOTICE: This theme is currently in the beta stage of development.
|
||||||
|
* The names and purposes of these CSS variables are subject to frequent changes.
|
||||||
|
*/
|
||||||
|
|
||||||
--theme-style: dark;
|
--theme-style: dark;
|
||||||
--native-titlebar-background: #00000000;
|
--native-titlebar-background: #00000000;
|
||||||
|
|
||||||
@ -89,6 +95,7 @@
|
|||||||
--menu-item-arrow-color: #ffffffa3;
|
--menu-item-arrow-color: #ffffffa3;
|
||||||
--menu-item-delimiter-color: #ffffff1c;
|
--menu-item-delimiter-color: #ffffff1c;
|
||||||
--menu-item-group-header-color: #ffffff91;
|
--menu-item-group-header-color: #ffffff91;
|
||||||
|
--menu-section-background-color: #fefefe08;
|
||||||
|
|
||||||
--modal-backdrop-color: #000;
|
--modal-backdrop-color: #000;
|
||||||
--modal-shadow-color: rgba(0, 0, 0, .5);
|
--modal-shadow-color: rgba(0, 0, 0, .5);
|
||||||
@ -195,6 +202,8 @@
|
|||||||
--scrollbar-background-color: transparent;
|
--scrollbar-background-color: transparent;
|
||||||
--scrollbar-border-color: unset; /* Deprecated */
|
--scrollbar-border-color: unset; /* Deprecated */
|
||||||
|
|
||||||
|
--selection-background-color: #3399FF70;
|
||||||
|
|
||||||
--link-color: lightskyblue;
|
--link-color: lightskyblue;
|
||||||
|
|
||||||
--mermaid-theme: dark;
|
--mermaid-theme: dark;
|
||||||
@ -234,6 +243,11 @@
|
|||||||
--help-code-background: #565656;
|
--help-code-background: #565656;
|
||||||
|
|
||||||
--ck-editor-popup-border-color: var(--modal-border-color);
|
--ck-editor-popup-border-color: var(--modal-border-color);
|
||||||
|
|
||||||
|
--ck-editor-toolbar-button-on-background: #ffffff3b;
|
||||||
|
--ck-editor-toolbar-button-on-color: white;
|
||||||
|
--ck-editor-toolbar-button-on-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
|
||||||
|
--ck-editor-toolbar-dropdown-button-open-background: #ffffff14;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -244,10 +258,6 @@ body ::-webkit-calendar-picker-indicator {
|
|||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
body .CodeMirror {
|
|
||||||
filter: invert(90%) hue-rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.excalidraw.theme--dark {
|
.excalidraw.theme--dark {
|
||||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,12 @@
|
|||||||
* Color scheme
|
* Color scheme
|
||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ⚠️ NOTICE: This theme is currently in the beta stage of development.
|
||||||
|
* The names and purposes of these CSS variables are subject to frequent changes.
|
||||||
|
*/
|
||||||
|
|
||||||
--theme-style: light;
|
--theme-style: light;
|
||||||
--native-titlebar-background: #ffffff00;
|
--native-titlebar-background: #ffffff00;
|
||||||
|
|
||||||
@ -83,6 +89,7 @@
|
|||||||
--menu-item-arrow-color: #00000080;
|
--menu-item-arrow-color: #00000080;
|
||||||
--menu-item-delimiter-color: #00000030;
|
--menu-item-delimiter-color: #00000030;
|
||||||
--menu-item-group-header-color: #00000061;
|
--menu-item-group-header-color: #00000061;
|
||||||
|
--menu-section-background-color: #00000006;
|
||||||
|
|
||||||
--modal-backdrop-color: #7c7c7c;
|
--modal-backdrop-color: #7c7c7c;
|
||||||
--modal-shadow-color: #00000033;
|
--modal-shadow-color: #00000033;
|
||||||
@ -194,6 +201,8 @@
|
|||||||
--scrollbar-background-color: transparent;
|
--scrollbar-background-color: transparent;
|
||||||
--scrollbar-border-color: unset; /* Deprecated */
|
--scrollbar-border-color: unset; /* Deprecated */
|
||||||
|
|
||||||
|
--selection-background-color: #3399FF70;
|
||||||
|
|
||||||
--link-color: blue;
|
--link-color: blue;
|
||||||
|
|
||||||
--mermaid-theme: default;
|
--mermaid-theme: default;
|
||||||
@ -234,4 +243,9 @@
|
|||||||
--help-code-background: #d7d5d5;
|
--help-code-background: #d7d5d5;
|
||||||
|
|
||||||
--ck-editor-popup-border-color: var(--dropdown-border-color);
|
--ck-editor-popup-border-color: var(--dropdown-border-color);
|
||||||
|
--ck-editor-toolbar-button-on-background: #00000030;
|
||||||
|
--ck-editor-toolbar-button-on-color: black;
|
||||||
|
--ck-editor-toolbar-button-on-shadow: none;
|
||||||
|
--ck-editor-toolbar-dropdown-button-open-background: #0000000f;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,8 @@ body.mobile .dropdown-menu .dropdown-menu {
|
|||||||
border-radius: unset !important;
|
border-radius: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.desktop .dropdown-menu::before {
|
body.desktop .dropdown-menu::before,
|
||||||
|
:root .ck.ck-dropdown__panel::before {
|
||||||
content: "";
|
content: "";
|
||||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||||
border-radius: var(--dropdown-border-radius);
|
border-radius: var(--dropdown-border-radius);
|
||||||
|
|||||||
@ -79,7 +79,7 @@ button.btn.btn-success kbd {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
:root .icon-action:not(.global-menu-button),
|
:root .icon-action:not(.global-menu-button),
|
||||||
:root .tn-tool-button,
|
:root .btn.tn-tool-button,
|
||||||
:root .btn-group .tn-tool-button:not(:last-child),
|
:root .btn-group .tn-tool-button:not(:last-child),
|
||||||
:root .btn-group .tn-tool-button:last-child {
|
:root .btn-group .tn-tool-button:last-child {
|
||||||
width: var(--icon-button-size);
|
width: var(--icon-button-size);
|
||||||
@ -110,7 +110,7 @@ button.btn.btn-success kbd {
|
|||||||
:root .icon-action:not(.global-menu-button)::before,
|
:root .icon-action:not(.global-menu-button)::before,
|
||||||
:root .tn-tool-button::before {
|
:root .tn-tool-button::before {
|
||||||
display: block;
|
display: block;
|
||||||
line-height: var(--icon-button-size);
|
line-height: 1;
|
||||||
font-size: calc(var(--icon-button-size) * var(--icon-button-icon-ratio));
|
font-size: calc(var(--icon-button-size) * var(--icon-button-icon-ratio));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
* Toolbar
|
* Toolbar
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.ck.ck-toolbar {
|
.ck.ck-toolbar,
|
||||||
|
.ck.ck-block-toolbar-button {
|
||||||
--ck-color-toolbar-background: transparent;
|
--ck-color-toolbar-background: transparent;
|
||||||
|
|
||||||
--ck-color-button-default-background: transparent;
|
--ck-color-button-default-background: transparent;
|
||||||
@ -19,19 +20,62 @@
|
|||||||
|
|
||||||
--ck-color-button-on-background: transparent;
|
--ck-color-button-on-background: transparent;
|
||||||
--ck-color-button-on-hover-background: var(--hover-item-background-color);
|
--ck-color-button-on-hover-background: var(--hover-item-background-color);
|
||||||
|
--ck-color-button-default-active-background: var(--hover-item-background-color);
|
||||||
|
|
||||||
--ck-focus-ring: 1px solid var(--input-focus-outline-color);
|
--ck-color-split-button-hover-background: var(--ck-editor-toolbar-dropdown-button-open-background);
|
||||||
|
|
||||||
|
--ck-focus-ring: 1px solid transparent;
|
||||||
--ck-color-focus-border: var(--input-focus-outline-color);
|
--ck-color-focus-border: var(--input-focus-outline-color);
|
||||||
--ck-focus-outer-shadow: none;
|
--ck-focus-outer-shadow: none;
|
||||||
|
--ck-focus-disabled-outer-shadow: none;
|
||||||
|
|
||||||
--ck-border-radius: 6px;
|
--ck-border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toolbar button in on state */
|
||||||
|
.ck.ck-toolbar .ck-button.ck-on:not(.ck-dropdown__button):not(.ck-list-item-button):not(.ck-button_with-text) {
|
||||||
|
--ck-color-button-on-background: var(--ck-editor-toolbar-button-on-background);
|
||||||
|
--ck-color-button-on-color: var(--ck-editor-toolbar-button-on-color);
|
||||||
|
box-shadow: var(--ck-editor-toolbar-button-on-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar button with its dropdown open */
|
||||||
|
.ck.ck-toolbar .ck-button.ck-dropdown__button {
|
||||||
|
--ck-color-button-on-background: var(--ck-editor-toolbar-dropdown-button-open-background);
|
||||||
|
--ck-color-button-on-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The toolbar show / hide button for the current text block */
|
||||||
|
.ck.ck-block-toolbar-button {
|
||||||
|
--ck-color-button-on-background: transparent;
|
||||||
|
--ck-color-button-on-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root .ck.ck-toolbar .ck-button:not(.ck-disabled):active,
|
||||||
|
.ck.ck-block-toolbar-button:active {
|
||||||
|
background-color: var(--hover-item-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-toolbar .ck-button:active:not(.ck-list-item-button):not(.ck-button_with-text):not(.ck-disabled) svg:not(.ck-dropdown__arrow),
|
||||||
|
.ck.ck-block-toolbar-button:active svg {
|
||||||
|
transform: scale(.8);
|
||||||
|
}
|
||||||
|
|
||||||
/* Disabled button */
|
/* Disabled button */
|
||||||
:root .classic-toolbar-widget .ck.ck-button.ck-disabled {
|
:root .classic-toolbar-widget .ck.ck-button.ck-disabled {
|
||||||
opacity: .75;
|
opacity: .75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Focus visible */
|
||||||
|
.ck.ck-toolbar .ck-button:focus-visible {
|
||||||
|
--ck-focus-ring: 1px solid var(--input-focus-outline-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the border from hovered split arrow button */
|
||||||
|
.ck.ck-splitbutton:hover > .ck-splitbutton__arrow:not(.ck-disabled)::after {
|
||||||
|
visibility: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dropdowns
|
* Dropdowns
|
||||||
*/
|
*/
|
||||||
@ -49,16 +93,61 @@
|
|||||||
border: 1px solid var(--ck-editor-popup-border-color) !important;
|
border: 1px solid var(--ck-editor-popup-border-color) !important;
|
||||||
border-radius: var(--dropdown-border-radius) !important;
|
border-radius: var(--dropdown-border-radius) !important;
|
||||||
background: var(--menu-background-color) !important;
|
background: var(--menu-background-color) !important;
|
||||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
|
||||||
padding: var(--ck-editor-popup-padding);
|
padding: var(--ck-editor-popup-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Backdrop blur pseudo-element
|
||||||
|
*
|
||||||
|
* Since .ck-balloon-panel already uses the :after and :before pseudo-elements, there is no other
|
||||||
|
* option than using a :before on the children element to apply the backdrop blur.
|
||||||
|
* This pseudoelement will overflow and cover the entire surface of .ck-balloon-panel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root .ck-balloon-panel > .ck-toolbar,
|
||||||
|
:root .ck-balloon-panel > .ck-balloon-rotator {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root .ck-balloon-panel > .ck-toolbar::before,
|
||||||
|
:root .ck-balloon-panel > .ck-balloon-rotator::before {
|
||||||
|
--negative-padding: calc(0px - var(--ck-editor-popup-padding)); /* Compensate the parent's padding */
|
||||||
|
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: var(--negative-padding);
|
||||||
|
right: var(--negative-padding);
|
||||||
|
bottom: var(--negative-padding);
|
||||||
|
left: var(--negative-padding);
|
||||||
|
border-radius: var(--dropdown-border-radius);
|
||||||
|
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root .ck.ck-dropdown__panel {
|
||||||
|
--ck-editor-popup-padding: var(--menu-padding-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown panel containing a toolbar */
|
||||||
|
:root .ck.ck-dropdown__panel:has(>.ck-toolbar) {
|
||||||
|
--ck-editor-popup-padding: calc(var(--menu-padding-size) - var(--ck-spacing-small));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bulleted / number list toolbar */
|
||||||
|
.ck-list-styles-list {
|
||||||
|
--ck-spacing-large: var(--ck-spacing-small);
|
||||||
|
}
|
||||||
|
|
||||||
:root ul.ck.ck-list,
|
:root ul.ck.ck-list,
|
||||||
:root div.ck.ck-balloon-panel:not(.ck-tooltip) {
|
:root div.ck.ck-balloon-panel:not(.ck-tooltip) {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root .ck.ck-list {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Tooltip */
|
/* Tooltip */
|
||||||
:root div.ck.ck-balloon-panel.ck-tooltip {
|
:root div.ck.ck-balloon-panel.ck-tooltip {
|
||||||
--ck-color-panel-background: var(--toast-background); /* Arrow */
|
--ck-color-panel-background: var(--toast-background); /* Arrow */
|
||||||
@ -73,24 +162,45 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Dropdown list item */
|
/* Dropdown list item */
|
||||||
:root ul.ck.ck-list button.ck-button {
|
:root ul.ck.ck-list button.ck-button,
|
||||||
|
:root .ck.ck-collapsible > button.ck-button {
|
||||||
padding: 2px 16px;
|
padding: 2px 16px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
box-shadow: unset;
|
box-shadow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checked list item */
|
:root .ck.ck-list__item {
|
||||||
:root ul.ck.ck-list button.ck-button.ck-on:not(:hover) {
|
min-width: 10em;
|
||||||
background: transparent !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Item with icon */
|
||||||
|
:root .ck.ck-button_with-text svg:first-child {
|
||||||
|
color: var(--menu-item-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checked list item */
|
||||||
|
|
||||||
:root ul.ck.ck-list button.ck-button:hover,
|
:root ul.ck.ck-list button.ck-button:hover,
|
||||||
:root ul.ck.ck-list button.ck-button.ck-on:hover {
|
:root ul.ck.ck-list button.ck-button.ck-on:hover,
|
||||||
|
:root .ck.ck-collapsible > button.ck-button:not(.ck-disabled):hover,
|
||||||
|
:root .ck.ck-collapsible > button.ck-button:not(.ck-disabled):not(:focus):hover {
|
||||||
background: var(--hover-item-background-color);
|
background: var(--hover-item-background-color);
|
||||||
color: var(--hover-item-color);
|
color: var(--hover-item-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* List item checkmark container */
|
||||||
|
|
||||||
|
:root .ck.ck-list-item-button .ck-list-item-button__check-holder {
|
||||||
|
margin-inline-start: var(--ck-spacing-small);
|
||||||
|
margin-inline-end: var(--menu-padding-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root .ck.ck-list-item-button .ck-list-item-button__check-holder svg {
|
||||||
|
transform: scale(1.2);
|
||||||
|
color: var(--menu-item-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
/* Separator */
|
/* Separator */
|
||||||
:root .ck .ck-list__separator {
|
:root .ck .ck-list__separator {
|
||||||
margin: .5em 0;
|
margin: .5em 0;
|
||||||
@ -99,8 +209,82 @@
|
|||||||
background: var(--menu-item-delimiter-color);
|
background: var(--menu-item-delimiter-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Collapsible section */
|
||||||
|
|
||||||
|
.ck.ck-collapsible {
|
||||||
|
position: relative;
|
||||||
|
border: unset !important;
|
||||||
|
padding-top: var(--ck-editor-popup-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-collapsible::before {
|
||||||
|
/* Adds a background shade which overlaps the dropdown's padding */
|
||||||
|
|
||||||
|
--negative-padding: calc(0px - var(--ck-editor-popup-padding));
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: var(--negative-padding);
|
||||||
|
left: var(--negative-padding);
|
||||||
|
right: var(--negative-padding);
|
||||||
|
border-top: 1px solid var(--ck-editor-popup-border-color);
|
||||||
|
background: var(--menu-section-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-collapsible:last-child::before {
|
||||||
|
border-radius: 0 0 var(--dropdown-border-radius) var(--dropdown-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-collapsible.ck-collapsible_collapsed > button.ck-button {
|
||||||
|
font-weight: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck.ck-collapsible .ck-collapsible__children {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar separators */
|
||||||
|
|
||||||
|
:root .ck.ck-toolbar .ck.ck-toolbar__separator {
|
||||||
|
background: transparent;
|
||||||
|
border-left: 1px solid var(--ck-color-toolbar-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The last separator of the toolbar */
|
||||||
|
:root .classic-toolbar-widget .ck.ck-toolbar__separator:last-of-type {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Heading dropdown */
|
||||||
|
|
||||||
|
:root .ck.ck-dropdown.ck-heading-dropdown .ck-dropdown__panel .ck-list__item {
|
||||||
|
min-width: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Font size dropdown */
|
||||||
|
|
||||||
|
.ck-fontsize-option {
|
||||||
|
min-height: 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ck-fontsize-option.text-tiny {--size: .75em;}
|
||||||
|
.ck-fontsize-option.text-small {--size: .85em;}
|
||||||
|
.ck-fontsize-option.text-big {--size: 1.4em;}
|
||||||
|
.ck-fontsize-option.text-huge {--size: 1.8em;}
|
||||||
|
|
||||||
|
:root .ck-fontsize-option .ck-button__label {
|
||||||
|
font-size: var(--size);
|
||||||
|
}
|
||||||
|
|
||||||
/* Color picker dropdown */
|
/* Color picker dropdown */
|
||||||
|
|
||||||
|
/* Color palette */
|
||||||
|
:root .ck.ck-color-selector .ck-color-grid {
|
||||||
|
--ck-editor-toolbar-button-on-shadow: none; /* Remove the shadow of the selected color button */
|
||||||
|
}
|
||||||
|
|
||||||
/* Color picker button */
|
/* Color picker button */
|
||||||
:root .ck.ck-color-selector .ck-color-grids-fragment .ck-button.ck-color-selector__color-picker {
|
:root .ck.ck-color-selector .ck-color-grids-fragment .ck-button.ck-color-selector__color-picker {
|
||||||
--ck-color-base-border: transparent; /* Remove the top border */
|
--ck-color-base-border: transparent; /* Remove the top border */
|
||||||
@ -109,13 +293,69 @@
|
|||||||
border-bottom-right-radius: var(--ck-border-radius);
|
border-bottom-right-radius: var(--ck-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Current color checkmark */
|
||||||
|
:root .ck.ck-color-selector .ck-color-grid .ck-icon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root .ck.ck-color-selector .ck-color-grid .ck-icon__fill {
|
||||||
|
fill: black !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Numbered list */
|
||||||
|
|
||||||
|
:root .ck.ck-list-properties_with-numbered-properties .ck.ck-list-styles-list {
|
||||||
|
min-width: 200px;
|
||||||
|
grid-template-columns: repeat(3, auto);
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: calc(var(--ck-spacing-medium) + var(--menu-padding-size));
|
||||||
|
}
|
||||||
|
|
||||||
/* Table dropdown */
|
/* Table dropdown */
|
||||||
|
|
||||||
|
/* Table rows / columns button grid container */
|
||||||
.ck-insert-table-dropdown__grid {
|
.ck-insert-table-dropdown__grid {
|
||||||
|
--ck-insert-table-dropdown-box-width: 16px;
|
||||||
|
--ck-insert-table-dropdown-box-height: 16px;
|
||||||
|
--ck-insert-table-dropdown-box-margin: 2px;
|
||||||
--ck-color-base-border: var(--ck-color-panel-border); /* Cell box color */
|
--ck-color-base-border: var(--ck-color-panel-border); /* Cell box color */
|
||||||
--ck-color-focus-border: var(--hover-item-text-color); /* Selected cell box border color */
|
--ck-color-focus-border: var(--hover-item-text-color); /* Selected cell box border color */
|
||||||
--ck-color-focus-outer-shadow: var(--hover-item-background-color); /* Selected cell box background color */
|
--ck-color-focus-outer-shadow: var(--hover-item-background-color); /* Selected cell box background color */
|
||||||
--ck-border-radius: 0;
|
--ck-border-radius: 0;
|
||||||
|
--ck-editor-toolbar-button-on-shadow: 1px 1px 1px rgba(0, 0, 0, .35);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selected rows / column counter */
|
||||||
|
.ck.ck-insert-table-dropdown__label {
|
||||||
|
margin-top: var(--ck-spacing-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admonitions dropdown */
|
||||||
|
|
||||||
|
.ck-tn-admonition-note { --icon: "\eb21"; --accent: var(--admonition-note-accent-color);}
|
||||||
|
.ck-tn-admonition-tip { --icon: "\ea0d"; --accent: var(--admonition-tip-accent-color);}
|
||||||
|
.ck-tn-admonition-important { --icon: "\ea7c"; --accent: var(--admonition-important-accent-color);}
|
||||||
|
.ck-tn-admonition-caution { --icon: "\eac7"; --accent: var(--admonition-caution-accent-color);}
|
||||||
|
.ck-tn-admonition-warning { --icon: "\eac5"; --accent: var(--admonition-warning-accent-color);}
|
||||||
|
|
||||||
|
:root .ck.ck-tn-admonition-option .ck-button__label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin: 4px;
|
||||||
|
padding-right: 2em;
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root .ck.ck-tn-admonition-option .ck-button__label::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: var(--icon);
|
||||||
|
width: 2em;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-family: boxicons;
|
||||||
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Action buttons */
|
/* Action buttons */
|
||||||
@ -133,6 +373,20 @@
|
|||||||
background: var(--hover-item-background-color);
|
background: var(--hover-item-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mention list (the autocompletion list for emojis, labels and relations) */
|
||||||
|
|
||||||
|
:root .ck-mentions {
|
||||||
|
--ck-color-list-button-on-background: var(--hover-item-background-color);
|
||||||
|
--ck-color-list-button-on-text: var(--hover-item-text-color);
|
||||||
|
--ck-color-list-button-hover-background: var(--ck-editor-toolbar-dropdown-button-open-background);
|
||||||
|
--ck-focus-ring: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "Keep on typing to see the emoji" placeholder */
|
||||||
|
#mention-list-item-id\:__EMOJI_HINT {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EDITOR'S CONTENT
|
* EDITOR'S CONTENT
|
||||||
*/
|
*/
|
||||||
@ -142,19 +396,39 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.attachment-content-wrapper pre,
|
.attachment-content-wrapper pre,
|
||||||
.ck-content pre,
|
:root .ck-content pre,
|
||||||
.ck-mermaid__editing-view {
|
.ck-mermaid__editing-view {
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: var(--code-block-box-shadow);
|
box-shadow: var(--code-block-box-shadow);
|
||||||
padding: 0 !important;
|
padding: 0;
|
||||||
margin-top: 2px !important;
|
margin-top: 2px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ck-content pre {
|
:root .ck-content pre {
|
||||||
|
--icon-button-size: 1.8em;
|
||||||
|
--copy-button-width: var(--icon-button-size);
|
||||||
|
|
||||||
|
/* The margin of the copy button is computed so the button will appear vertically centered
|
||||||
|
* for single-line code blocks */
|
||||||
|
|
||||||
|
--copy-button-margin-size: calc((1em * 1.5 + var(--padding-size) * 2 - var(--icon-button-size)) / 2);
|
||||||
|
|
||||||
|
/* Where: │ └ Line height
|
||||||
|
* └───────── Font size
|
||||||
|
*/
|
||||||
|
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre button.copy-button.icon-action {
|
||||||
|
font-size: 1em; /* Workaround: --icon-button-size does not work properly with em units */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root pre:has(> button.copy-button) {
|
||||||
|
padding-right: calc(var(--icon-button-size) + (var(--copy-button-margin-size) * 2));
|
||||||
|
}
|
||||||
|
|
||||||
html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
||||||
/* Create some space for the top-side shadow */
|
/* Create some space for the top-side shadow */
|
||||||
margin-top: 1px !important;
|
margin-top: 1px !important;
|
||||||
@ -170,10 +444,10 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attachment-content-wrapper pre code,
|
.attachment-content-wrapper pre code,
|
||||||
.ck-content pre code,
|
:root .ck-content pre code,
|
||||||
.ck-mermaid__editing-view {
|
.ck-mermaid__editing-view {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 1em !important;
|
padding: var(--padding-size, 1em);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +519,6 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
|||||||
|
|
||||||
.note-detail-printable:not(.word-wrap) pre code {
|
.note-detail-printable:not(.word-wrap) pre code {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
margin-right: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-sample-wrapper .hljs {
|
.code-sample-wrapper .hljs {
|
||||||
|
|||||||
@ -108,6 +108,25 @@ div.editability-dropdown a.dropdown-item {
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Edited notes (for calendar notes)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* The path of the note */
|
||||||
|
.edited-notes-list small {
|
||||||
|
margin-inline-start: 4px;
|
||||||
|
font-size: inherit;
|
||||||
|
color: var(--muted-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edited-notes-list small::before {
|
||||||
|
content: "(";
|
||||||
|
}
|
||||||
|
|
||||||
|
.edited-notes-list small::after {
|
||||||
|
content: ")";
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Owned attributes
|
* Owned attributes
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -127,10 +127,12 @@ body.layout-horizontal > .horizontal {
|
|||||||
--launcher-pane-button-gap: var(--launcher-pane-vert-button-gap);
|
--launcher-pane-button-gap: var(--launcher-pane-vert-button-gap);
|
||||||
|
|
||||||
width: var(--launcher-pane-size) !important;
|
width: var(--launcher-pane-size) !important;
|
||||||
|
min-width: var(--launcher-pane-size) !important;
|
||||||
padding-bottom: var(--launcher-pane-button-gap);
|
padding-bottom: var(--launcher-pane-button-gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
#launcher-pane.vertical #launcher-container {
|
#launcher-pane.vertical #launcher-container {
|
||||||
|
width: var(--launcher-pane-size);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -1400,6 +1402,7 @@ div.floating-buttons .show-floating-buttons-button:active {
|
|||||||
div.floating-buttons-children .close-floating-buttons-button::before,
|
div.floating-buttons-children .close-floating-buttons-button::before,
|
||||||
div.floating-buttons .show-floating-buttons-button::before {
|
div.floating-buttons .show-floating-buttons-button::before {
|
||||||
display: block;
|
display: block;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "Show buttons" button */
|
/* "Show buttons" button */
|
||||||
@ -1636,7 +1639,9 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
|
|||||||
|
|
||||||
#right-pane .toc li,
|
#right-pane .toc li,
|
||||||
#right-pane .highlights-list li {
|
#right-pane .highlights-list li {
|
||||||
padding: 2px 8px;
|
padding-top: 2px;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-bottom: 2px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
text-align: unset;
|
text-align: unset;
|
||||||
transition:
|
transition:
|
||||||
|
|||||||
@ -28,7 +28,7 @@ interface NoteDefinition extends AttributeDefinitions, RelationDefinitions {
|
|||||||
* ]);
|
* ]);
|
||||||
*/
|
*/
|
||||||
export function buildNotes(notes: NoteDefinition[]) {
|
export function buildNotes(notes: NoteDefinition[]) {
|
||||||
const ids = [];
|
const ids: string[] = [];
|
||||||
|
|
||||||
for (const noteDef of notes) {
|
for (const noteDef of notes) {
|
||||||
ids.push(buildNote(noteDef).noteId);
|
ids.push(buildNote(noteDef).noteId);
|
||||||
|
|||||||
@ -274,15 +274,15 @@
|
|||||||
"revision_last_edited": "此修订版本上次编辑于 {{date}}",
|
"revision_last_edited": "此修订版本上次编辑于 {{date}}",
|
||||||
"confirm_delete_all": "您是否要删除此笔记的所有修订版本?",
|
"confirm_delete_all": "您是否要删除此笔记的所有修订版本?",
|
||||||
"no_revisions": "此笔记暂无修订版本...",
|
"no_revisions": "此笔记暂无修订版本...",
|
||||||
"restore_button": "",
|
"restore_button": "恢复",
|
||||||
"confirm_restore": "您是否要恢复此修订版本?这将使用此修订版本覆盖笔记的当前标题和内容。",
|
"confirm_restore": "您是否要恢复此修订版本?这将使用此修订版本覆盖笔记的当前标题和内容。",
|
||||||
"delete_button": "",
|
"delete_button": "删除",
|
||||||
"confirm_delete": "您是否要删除此修订版本?",
|
"confirm_delete": "您是否要删除此修订版本?",
|
||||||
"revisions_deleted": "笔记修订版本已删除。",
|
"revisions_deleted": "笔记修订版本已删除。",
|
||||||
"revision_restored": "笔记修订版本已恢复。",
|
"revision_restored": "笔记修订版本已恢复。",
|
||||||
"revision_deleted": "笔记修订版本已删除。",
|
"revision_deleted": "笔记修订版本已删除。",
|
||||||
"snapshot_interval": "笔记快照保存间隔: {{seconds}}秒。",
|
"snapshot_interval": "笔记快照保存间隔: {{seconds}}秒。",
|
||||||
"maximum_revisions": "当前笔记的最历史数量: {{number}}。",
|
"maximum_revisions": "当前笔记的最大历史数量: {{number}}。",
|
||||||
"settings": "笔记修订设置",
|
"settings": "笔记修订设置",
|
||||||
"download_button": "下载",
|
"download_button": "下载",
|
||||||
"mime": "MIME 类型:",
|
"mime": "MIME 类型:",
|
||||||
@ -806,7 +806,7 @@
|
|||||||
"open_full": "展开显示",
|
"open_full": "展开显示",
|
||||||
"collapse": "折叠到正常大小",
|
"collapse": "折叠到正常大小",
|
||||||
"title": "笔记地图",
|
"title": "笔记地图",
|
||||||
"fix-nodes": "修复节点",
|
"fix-nodes": "固定节点",
|
||||||
"link-distance": "链接距离"
|
"link-distance": "链接距离"
|
||||||
},
|
},
|
||||||
"note_paths": {
|
"note_paths": {
|
||||||
@ -1213,7 +1213,7 @@
|
|||||||
"color": "字体颜色",
|
"color": "字体颜色",
|
||||||
"bg_color": "背景颜色",
|
"bg_color": "背景颜色",
|
||||||
"visibility_title": "高亮列表可见性",
|
"visibility_title": "高亮列表可见性",
|
||||||
"visibility_description": "您可以通过添加 #hideHighlightWidget 标签来隐藏每个笔记的高亮小部件。",
|
"visibility_description": "您可以通过添加 #hideHighlightWidget 标签来隐藏单个笔记的高亮小部件。",
|
||||||
"shortcut_info": "您可以在选项 -> 快捷键中为快速切换右侧面板(包括高亮列表)配置键盘快捷键(名称为 'toggleRightPane')。"
|
"shortcut_info": "您可以在选项 -> 快捷键中为快速切换右侧面板(包括高亮列表)配置键盘快捷键(名称为 'toggleRightPane')。"
|
||||||
},
|
},
|
||||||
"table_of_contents": {
|
"table_of_contents": {
|
||||||
@ -1547,7 +1547,7 @@
|
|||||||
"close_other_tabs": "关闭其他标签页",
|
"close_other_tabs": "关闭其他标签页",
|
||||||
"close_right_tabs": "关闭右侧标签页",
|
"close_right_tabs": "关闭右侧标签页",
|
||||||
"close_all_tabs": "关闭所有标签页",
|
"close_all_tabs": "关闭所有标签页",
|
||||||
"reopen_last_tab": "重新打开最后一个关闭的标签页",
|
"reopen_last_tab": "重新打开关闭的标签页",
|
||||||
"move_tab_to_new_window": "将此标签页移动到新窗口",
|
"move_tab_to_new_window": "将此标签页移动到新窗口",
|
||||||
"copy_tab_to_new_window": "将此标签页复制到新窗口",
|
"copy_tab_to_new_window": "将此标签页复制到新窗口",
|
||||||
"new_tab": "新标签页"
|
"new_tab": "新标签页"
|
||||||
@ -1616,29 +1616,32 @@
|
|||||||
"auto-detect-language": "自动检测"
|
"auto-detect-language": "自动检测"
|
||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"title": "文本笔记的代码语法高亮",
|
"title": "代码块",
|
||||||
"description": "控制文本笔记中代码块的语法高亮,代码笔记不会受到影响。",
|
"description": "控制文本笔记中代码块的语法高亮,代码笔记不会受到影响。",
|
||||||
"color-scheme": "颜色方案"
|
"color-scheme": "颜色方案"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "自动换行"
|
"word_wrapping": "自动换行",
|
||||||
|
"theme_none": "无语法高亮",
|
||||||
|
"theme_group_light": "浅色主题",
|
||||||
|
"theme_group_dark": "深色主题"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "格式化"
|
"title": "格式"
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
"title": "编辑器"
|
"title": "编辑器"
|
||||||
},
|
},
|
||||||
"editing": {
|
"editing": {
|
||||||
"editor_type": {
|
"editor_type": {
|
||||||
"label": "格式化工具栏",
|
"label": "格式工具栏",
|
||||||
"floating": {
|
"floating": {
|
||||||
"title": "浮动",
|
"title": "浮动",
|
||||||
"description": "编辑工具出现在光标附近;"
|
"description": "编辑工具出现在光标附近;"
|
||||||
},
|
},
|
||||||
"fixed": {
|
"fixed": {
|
||||||
"title": "固定",
|
"title": "固定",
|
||||||
"description": "编辑工具出现在 \"格式化\" 功能区标签中。"
|
"description": "编辑工具出现在 \"格式\" 功能区标签中。"
|
||||||
},
|
},
|
||||||
"multiline-toolbar": "如果工具栏无法完全显示,则分多行显示。"
|
"multiline-toolbar": "如果工具栏无法完全显示,则分多行显示。"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1568,12 +1568,15 @@
|
|||||||
"auto-detect-language": "Automatisch erkannt"
|
"auto-detect-language": "Automatisch erkannt"
|
||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"title": "Code-Syntax-Hervorhebung für Textnotizen",
|
"title": "",
|
||||||
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
|
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
|
||||||
"color-scheme": "Farbschema"
|
"color-scheme": "Farbschema"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Wortumbruch"
|
"word_wrapping": "Wortumbruch",
|
||||||
|
"theme_none": "Keine Syntax-Hervorhebung",
|
||||||
|
"theme_group_light": "Helle Themen",
|
||||||
|
"theme_group_dark": "Dunkle Themen"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Format"
|
"title": "Format"
|
||||||
|
|||||||
@ -1247,13 +1247,11 @@
|
|||||||
"reprocessing_embeddings": "Reprocessing...",
|
"reprocessing_embeddings": "Reprocessing...",
|
||||||
"reprocess_started": "Embedding reprocessing started in the background",
|
"reprocess_started": "Embedding reprocessing started in the background",
|
||||||
"reprocess_error": "Error starting embedding reprocessing",
|
"reprocess_error": "Error starting embedding reprocessing",
|
||||||
|
|
||||||
"reprocess_index": "Rebuild Search Index",
|
"reprocess_index": "Rebuild Search Index",
|
||||||
"reprocess_index_description": "Optimize the search index for better performance. This uses existing embeddings without regenerating them (much faster than reprocessing all embeddings).",
|
"reprocess_index_description": "Optimize the search index for better performance. This uses existing embeddings without regenerating them (much faster than reprocessing all embeddings).",
|
||||||
"reprocessing_index": "Rebuilding...",
|
"reprocessing_index": "Rebuilding...",
|
||||||
"reprocess_index_started": "Search index optimization started in the background",
|
"reprocess_index_started": "Search index optimization started in the background",
|
||||||
"reprocess_index_error": "Error rebuilding search index",
|
"reprocess_index_error": "Error rebuilding search index",
|
||||||
|
|
||||||
"index_rebuild_progress": "Index Rebuild Progress",
|
"index_rebuild_progress": "Index Rebuild Progress",
|
||||||
"index_rebuilding": "Optimizing index ({{percentage}}%)",
|
"index_rebuilding": "Optimizing index ({{percentage}}%)",
|
||||||
"index_rebuild_complete": "Index optimization complete",
|
"index_rebuild_complete": "Index optimization complete",
|
||||||
@ -1781,7 +1779,9 @@
|
|||||||
},
|
},
|
||||||
"clipboard": {
|
"clipboard": {
|
||||||
"cut": "Note(s) have been cut into clipboard.",
|
"cut": "Note(s) have been cut into clipboard.",
|
||||||
"copied": "Note(s) have been copied into clipboard."
|
"copied": "Note(s) have been copied into clipboard.",
|
||||||
|
"copy_failed": "Cannot copy to clipboard due to permission issues.",
|
||||||
|
"copy_success": "Copied to clipboard."
|
||||||
},
|
},
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"note-revision-created": "Note revision has been created.",
|
"note-revision-created": "Note revision has been created.",
|
||||||
@ -1824,12 +1824,16 @@
|
|||||||
"auto-detect-language": "Auto-detected"
|
"auto-detect-language": "Auto-detected"
|
||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"title": "Code Syntax Highlighting for Text Notes",
|
"title": "Code Blocks",
|
||||||
"description": "Controls the syntax highlighting for code blocks inside text notes, code notes will not be affected.",
|
"description": "Controls the syntax highlighting for code blocks inside text notes, code notes will not be affected.",
|
||||||
"color-scheme": "Color Scheme"
|
"color-scheme": "Color Scheme"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Word wrapping"
|
"word_wrapping": "Word wrapping",
|
||||||
|
"theme_none": "No syntax highlighting",
|
||||||
|
"theme_group_light": "Light themes",
|
||||||
|
"theme_group_dark": "Dark themes",
|
||||||
|
"copy_title": "Copy to clipboard"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Formatting"
|
"title": "Formatting"
|
||||||
@ -1953,5 +1957,10 @@
|
|||||||
},
|
},
|
||||||
"svg": {
|
"svg": {
|
||||||
"export_to_png": "The diagram could not be exported to PNG."
|
"export_to_png": "The diagram could not be exported to PNG."
|
||||||
|
},
|
||||||
|
"code_theme": {
|
||||||
|
"title": "Appearance",
|
||||||
|
"word_wrapping": "Word wrapping",
|
||||||
|
"color-scheme": "Color scheme"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1584,12 +1584,15 @@
|
|||||||
"auto-detect-language": "Detectado automáticamente"
|
"auto-detect-language": "Detectado automáticamente"
|
||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"title": "Resaltado de sintaxis de de código para Notas de Texto",
|
"title": "",
|
||||||
"description": "Controla el resaltado de sintaxis para bloques de código dentro de las notas de texto, las notas de código no serán afectadas.",
|
"description": "Controla el resaltado de sintaxis para bloques de código dentro de las notas de texto, las notas de código no serán afectadas.",
|
||||||
"color-scheme": "Esquema de color"
|
"color-scheme": "Esquema de color"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Ajuste de palabras"
|
"word_wrapping": "Ajuste de palabras",
|
||||||
|
"theme_none": "Sin resaltado de sintaxis",
|
||||||
|
"theme_group_light": "Temas claros",
|
||||||
|
"theme_group_dark": "Temas oscuros"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Formato"
|
"title": "Formato"
|
||||||
|
|||||||
@ -1574,12 +1574,15 @@
|
|||||||
"auto-detect-language": "Détecté automatiquement"
|
"auto-detect-language": "Détecté automatiquement"
|
||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"title": "Coloration syntaxique du code pour les notes texte",
|
"title": "",
|
||||||
"description": "Contrôle la coloration syntaxique des blocs de code à l'intérieur des notes texte, les notes de code ne seront pas affectées.",
|
"description": "Contrôle la coloration syntaxique des blocs de code à l'intérieur des notes texte, les notes de code ne seront pas affectées.",
|
||||||
"color-scheme": "Jeu de couleurs"
|
"color-scheme": "Jeu de couleurs"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Saut à la ligne automatique suivant la largeur"
|
"word_wrapping": "Saut à la ligne automatique suivant la largeur",
|
||||||
|
"theme_none": "Pas de coloration syntaxique",
|
||||||
|
"theme_group_light": "Thèmes clairs",
|
||||||
|
"theme_group_dark": "Thèmes sombres"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Mise en forme"
|
"title": "Mise en forme"
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"revisions": {
|
"revisions": {
|
||||||
"delete_button": ""
|
"delete_button": ""
|
||||||
|
},
|
||||||
|
"code_block": {
|
||||||
|
"theme_none": "Sem destaque de sintaxe",
|
||||||
|
"theme_group_light": "Temas claros",
|
||||||
|
"theme_group_dark": "Temas escuros"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1581,11 +1581,14 @@
|
|||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"color-scheme": "Temă de culori",
|
"color-scheme": "Temă de culori",
|
||||||
"title": "Evidențiere de sintaxă pentru notițele de tip text",
|
"title": "",
|
||||||
"description": "Controlează evidențierea de sintaxă pentru blocurile de cod în interiorul notițelor text, notițele de tip cod nu vor fi afectate de aceste setări."
|
"description": "Controlează evidențierea de sintaxă pentru blocurile de cod în interiorul notițelor text, notițele de tip cod nu vor fi afectate de aceste setări."
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "Încadrare text"
|
"word_wrapping": "Încadrare text",
|
||||||
|
"theme_none": "Fără evidențiere de sintaxă",
|
||||||
|
"theme_group_dark": "Teme întunecate",
|
||||||
|
"theme_group_light": "Teme luminoase"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "Formatare"
|
"title": "Formatare"
|
||||||
|
|||||||
@ -1514,12 +1514,15 @@
|
|||||||
"auto-detect-language": "自動檢測"
|
"auto-detect-language": "自動檢測"
|
||||||
},
|
},
|
||||||
"highlighting": {
|
"highlighting": {
|
||||||
"title": "文字筆記的程式碼語法高亮",
|
"title": "",
|
||||||
"description": "控制文字筆記中程式碼塊的語法高亮,程式碼筆記不會受到影響。",
|
"description": "控制文字筆記中程式碼塊的語法高亮,程式碼筆記不會受到影響。",
|
||||||
"color-scheme": "顏色方案"
|
"color-scheme": "顏色方案"
|
||||||
},
|
},
|
||||||
"code_block": {
|
"code_block": {
|
||||||
"word_wrapping": "自動換行"
|
"word_wrapping": "自動換行",
|
||||||
|
"theme_none": "無格式高亮",
|
||||||
|
"theme_group_light": "淺色主題",
|
||||||
|
"theme_group_dark": "深色主題"
|
||||||
},
|
},
|
||||||
"classic_editor_toolbar": {
|
"classic_editor_toolbar": {
|
||||||
"title": "格式化"
|
"title": "格式化"
|
||||||
|
|||||||
4
apps/client/src/types-assets.d.ts
vendored
4
apps/client/src/types-assets.d.ts
vendored
@ -3,9 +3,7 @@ declare module "*.png" {
|
|||||||
export default path;
|
export default path;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "*.json?external" {
|
declare module "@triliumnext/ckeditor5/emoji_definitions/en.json?url" {
|
||||||
var path: string;
|
var path: string;
|
||||||
export default path;
|
export default path;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "script-loader!mark.js/dist/jquery.mark.min.js";
|
|
||||||
|
|||||||
27
apps/client/src/types-lib.d.ts
vendored
27
apps/client/src/types-lib.d.ts
vendored
@ -24,3 +24,30 @@ declare module "draggabilly" {
|
|||||||
declare module "@mind-elixir/node-menu" {
|
declare module "@mind-elixir/node-menu" {
|
||||||
export default mindmap;
|
export default mindmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module "katex/contrib/auto-render" {
|
||||||
|
var renderMathInElement: (element: HTMLElement, options: {
|
||||||
|
trust: boolean;
|
||||||
|
}) => void;
|
||||||
|
export default renderMathInElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
import * as L from "leaflet";
|
||||||
|
|
||||||
|
declare module "leaflet" {
|
||||||
|
interface GPXMarker {
|
||||||
|
startIcon?: DivIcon | Icon | string | undefined;
|
||||||
|
endIcon?: DivIcon | Icon | string | undefined;
|
||||||
|
wptIcons?: {
|
||||||
|
[key: string]: DivIcon | Icon | string;
|
||||||
|
};
|
||||||
|
wptTypeIcons?: {
|
||||||
|
[key: string]: DivIcon | Icon | string;
|
||||||
|
};
|
||||||
|
pointMatchers?: Array<{ regex: RegExp; icon: DivIcon | Icon | string}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GPXOptions {
|
||||||
|
markers?: GPXMarker | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
97
apps/client/src/types.d.ts
vendored
97
apps/client/src/types.d.ts
vendored
@ -22,7 +22,6 @@ interface CustomGlobals {
|
|||||||
getReferenceLinkTitle: (href: string) => Promise<string>;
|
getReferenceLinkTitle: (href: string) => Promise<string>;
|
||||||
getReferenceLinkTitleSync: (href: string) => string;
|
getReferenceLinkTitleSync: (href: string) => string;
|
||||||
getActiveContextNote: () => FNote | null;
|
getActiveContextNote: () => FNote | null;
|
||||||
requireLibrary: typeof library_loader.requireLibrary;
|
|
||||||
ESLINT: Library;
|
ESLINT: Library;
|
||||||
appContext: AppContext;
|
appContext: AppContext;
|
||||||
froca: Froca;
|
froca: Froca;
|
||||||
@ -94,16 +93,6 @@ declare global {
|
|||||||
getSelectedExternalLink(): string | undefined;
|
getSelectedExternalLink(): string | undefined;
|
||||||
setSelectedExternalLink(externalLink: string | null | undefined);
|
setSelectedExternalLink(externalLink: string | null | undefined);
|
||||||
setNote(noteId: string);
|
setNote(noteId: string);
|
||||||
markRegExp(regex: RegExp, opts: {
|
|
||||||
element: string;
|
|
||||||
className: string;
|
|
||||||
separateWordSearch: boolean;
|
|
||||||
caseSensitive: boolean;
|
|
||||||
done?: () => void;
|
|
||||||
});
|
|
||||||
unmark(opts?: {
|
|
||||||
done: () => void;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JQueryStatic {
|
interface JQueryStatic {
|
||||||
@ -123,92 +112,6 @@ declare global {
|
|||||||
var require: RequireMethod;
|
var require: RequireMethod;
|
||||||
var __non_webpack_require__: RequireMethod | undefined;
|
var __non_webpack_require__: RequireMethod | undefined;
|
||||||
|
|
||||||
// Libraries
|
|
||||||
// TODO: Replace once library loader is replaced with webpack.
|
|
||||||
var hljs: {
|
|
||||||
highlightAuto(text: string);
|
|
||||||
highlight(text: string, {
|
|
||||||
language: string
|
|
||||||
});
|
|
||||||
};
|
|
||||||
var renderMathInElement: (element: HTMLElement, options: {
|
|
||||||
trust: boolean;
|
|
||||||
}) => void;
|
|
||||||
|
|
||||||
interface CodeMirrorOpts {
|
|
||||||
value: string;
|
|
||||||
viewportMargin: number;
|
|
||||||
indentUnit: number;
|
|
||||||
matchBrackets: boolean;
|
|
||||||
matchTags: { bothTags: boolean };
|
|
||||||
highlightSelectionMatches: {
|
|
||||||
showToken: boolean;
|
|
||||||
annotateScrollbar: boolean;
|
|
||||||
};
|
|
||||||
lineNumbers: boolean;
|
|
||||||
lineWrapping: boolean;
|
|
||||||
keyMap?: "vim" | "default";
|
|
||||||
lint?: boolean;
|
|
||||||
gutters?: string[];
|
|
||||||
tabindex?: number;
|
|
||||||
dragDrop?: boolean;
|
|
||||||
placeholder?: string;
|
|
||||||
readOnly?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
var CodeMirror: {
|
|
||||||
(el: HTMLElement, opts: CodeMirrorOpts): CodeMirrorInstance;
|
|
||||||
keyMap: {
|
|
||||||
default: Record<string, string>;
|
|
||||||
};
|
|
||||||
modeURL: string;
|
|
||||||
modeInfo: ModeInfo[];
|
|
||||||
findModeByMIME(mime: string): ModeInfo;
|
|
||||||
autoLoadMode(instance: CodeMirrorInstance, mode: string)
|
|
||||||
registerHelper(type: string, filter: string | null, callback: (text: string, options: object) => unknown);
|
|
||||||
Pos(line: number, col: number);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ModeInfo {
|
|
||||||
name: string;
|
|
||||||
mode: string;
|
|
||||||
mime: string;
|
|
||||||
mimes: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CodeMirrorInstance {
|
|
||||||
getValue(): string;
|
|
||||||
setValue(val: string);
|
|
||||||
clearHistory();
|
|
||||||
setOption(name: string, value: string);
|
|
||||||
refresh();
|
|
||||||
focus();
|
|
||||||
getCursor(): { line: number, col: number, ch: number };
|
|
||||||
setCursor(line: number, col: number);
|
|
||||||
getSelection(): string;
|
|
||||||
lineCount(): number;
|
|
||||||
on(event: string, callback: () => void);
|
|
||||||
operation(callback: () => void);
|
|
||||||
scrollIntoView(pos: number);
|
|
||||||
doc: {
|
|
||||||
getValue(): string;
|
|
||||||
markText(
|
|
||||||
from: { line: number, ch: number } | number,
|
|
||||||
to: { line: number, ch: number } | number,
|
|
||||||
opts: {
|
|
||||||
className: string
|
|
||||||
});
|
|
||||||
setSelection(from: number, to: number);
|
|
||||||
replaceRange(text: string, from: number, to: number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var katex: {
|
|
||||||
renderToString(text: string, opts: {
|
|
||||||
throwOnError: boolean
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Panzoom
|
* Panzoom
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -718,7 +718,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildDefinitionValue() {
|
buildDefinitionValue() {
|
||||||
const props = [];
|
const props: string[] = [];
|
||||||
|
|
||||||
if (this.$inputPromoted.is(":checked")) {
|
if (this.$inputPromoted.is(":checked")) {
|
||||||
props.push("promoted");
|
props.push("promoted");
|
||||||
@ -728,10 +728,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
props.push(this.$inputMultiplicity.val());
|
props.push(this.$inputMultiplicity.val() as string);
|
||||||
|
|
||||||
if (this.attrType === "label-definition") {
|
if (this.attrType === "label-definition") {
|
||||||
props.push(this.$inputLabelType.val());
|
props.push(this.$inputLabelType.val() as string);
|
||||||
|
|
||||||
if (this.$inputLabelType.val() === "number" && this.$inputNumberPrecision.val() !== "") {
|
if (this.$inputLabelType.val() === "number" && this.$inputNumberPrecision.val() !== "") {
|
||||||
props.push(`precision=${this.$inputNumberPrecision.val()}`);
|
props.push(`precision=${this.$inputNumberPrecision.val()}`);
|
||||||
|
|||||||
@ -198,6 +198,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
|||||||
],
|
],
|
||||||
selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command)
|
selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command)
|
||||||
});
|
});
|
||||||
|
// Prevent automatic hiding of the context menu due to the button being clicked.
|
||||||
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// triggered from keyboard shortcut
|
// triggered from keyboard shortcut
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { t } from "../../services/i18n.js";
|
|
||||||
import options from "../../services/options.js";
|
|
||||||
import CommandButtonWidget from "./command_button.js";
|
|
||||||
|
|
||||||
export default class CreateAiChatButton extends CommandButtonWidget {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.icon("bx bx-bot")
|
|
||||||
.title(t("ai.create_new_ai_chat"))
|
|
||||||
.titlePlacement("bottom")
|
|
||||||
.command("createAiChat")
|
|
||||||
.class("icon-action");
|
|
||||||
}
|
|
||||||
|
|
||||||
isEnabled() {
|
|
||||||
return options.get("aiEnabled") === "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
async refreshWithNote() {
|
|
||||||
if (this.isEnabled()) {
|
|
||||||
this.$widget.show();
|
|
||||||
} else {
|
|
||||||
this.$widget.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,7 +8,10 @@ export default class CreatePaneButton extends OnClickButtonWidget {
|
|||||||
this.icon("bx-dock-right")
|
this.icon("bx-dock-right")
|
||||||
.title(t("create_pane_button.create_new_split"))
|
.title(t("create_pane_button.create_new_split"))
|
||||||
.titlePlacement("bottom")
|
.titlePlacement("bottom")
|
||||||
.onClick((widget) => widget.triggerCommand("openNewNoteSplit", { ntxId: widget.getClosestNtxId() }))
|
.onClick((widget, e) => {
|
||||||
|
widget.triggerCommand("openNewNoteSplit", { ntxId: widget.getClosestNtxId() });
|
||||||
|
e.stopPropagation();
|
||||||
|
})
|
||||||
.class("icon-action");
|
.class("icon-action");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,10 +53,6 @@ const TPL = /*html*/`
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-to-latest-version-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.global-menu .zoom-container {
|
.global-menu .zoom-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -235,7 +231,7 @@ const TPL = /*html*/`
|
|||||||
${t("global_menu.about")}
|
${t("global_menu.about")}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="dropdown-item update-to-latest-version-button" data-trigger-command="downloadLatestVersion">
|
<li class="dropdown-item update-to-latest-version-button" style="display: none;" data-trigger-command="downloadLatestVersion">
|
||||||
<span class="bx bx-sync"></span>
|
<span class="bx bx-sync"></span>
|
||||||
|
|
||||||
<span class="version-text"></span>
|
<span class="version-text"></span>
|
||||||
|
|||||||
@ -19,10 +19,10 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget {
|
|||||||
return "bx-sidebar";
|
return "bx-sidebar";
|
||||||
}
|
}
|
||||||
|
|
||||||
return options.is("leftPaneVisible") ? "bx-chevrons-left" : "bx-chevrons-right";
|
return this.currentLeftPaneVisible ? "bx-chevrons-left" : "bx-chevrons-right";
|
||||||
};
|
};
|
||||||
|
|
||||||
this.settings.title = () => (options.is("leftPaneVisible") ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel"));
|
this.settings.title = () => (this.currentLeftPaneVisible ? t("left_pane_toggle.hide_panel") : t("left_pane_toggle.show_panel"));
|
||||||
|
|
||||||
this.settings.command = () => (this.currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane");
|
this.settings.command = () => (this.currentLeftPaneVisible ? "hideLeftPane" : "showLeftPane");
|
||||||
|
|
||||||
@ -32,16 +32,12 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refreshIcon() {
|
refreshIcon() {
|
||||||
if (document.hasFocus() || this.currentLeftPaneVisible === true) {
|
super.refreshIcon();
|
||||||
super.refreshIcon();
|
splitService.setupLeftPaneResizer(this.currentLeftPaneVisible);
|
||||||
splitService.setupLeftPaneResizer(this.currentLeftPaneVisible);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
setLeftPaneVisibilityEvent({ leftPaneVisible }: EventData<"setLeftPaneVisibility">) {
|
||||||
if (loadResults.isOptionReloaded("leftPaneVisible") && document.hasFocus()) {
|
this.currentLeftPaneVisible = leftPaneVisible ?? !this.currentLeftPaneVisible;
|
||||||
this.currentLeftPaneVisible = options.is("leftPaneVisible");
|
this.refreshIcon();
|
||||||
this.refreshIcon();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -186,7 +186,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
|
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
|
||||||
|
|
||||||
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book"].includes(note.type));
|
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap"].includes(note.type));
|
||||||
|
|
||||||
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
|
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
|
||||||
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "geoMap"].includes(note.type));
|
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "geoMap"].includes(note.type));
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export default class LauncherContainer extends FlexContainer<LauncherWidget> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newChildren = [];
|
const newChildren: LauncherWidget[] = [];
|
||||||
|
|
||||||
for (const launcherNote of await visibleLaunchersRoot.getChildNotes()) {
|
for (const launcherNote of await visibleLaunchersRoot.getChildNotes()) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -4,28 +4,33 @@ import appContext, { type EventData } from "../../components/app_context.js";
|
|||||||
import type Component from "../../components/component.js";
|
import type Component from "../../components/component.js";
|
||||||
|
|
||||||
export default class LeftPaneContainer extends FlexContainer<Component> {
|
export default class LeftPaneContainer extends FlexContainer<Component> {
|
||||||
|
private currentLeftPaneVisible: boolean;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("column");
|
super("column");
|
||||||
|
|
||||||
|
this.currentLeftPaneVisible = options.is("leftPaneVisible");
|
||||||
|
|
||||||
this.id("left-pane");
|
this.id("left-pane");
|
||||||
this.css("height", "100%");
|
this.css("height", "100%");
|
||||||
this.collapsible();
|
this.collapsible();
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return super.isEnabled() && options.is("leftPaneVisible");
|
return super.isEnabled() && this.currentLeftPaneVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
setLeftPaneVisibilityEvent({ leftPaneVisible }: EventData<"setLeftPaneVisibility">) {
|
||||||
if (loadResults.isOptionReloaded("leftPaneVisible") && document.hasFocus()) {
|
this.currentLeftPaneVisible = leftPaneVisible ?? !this.currentLeftPaneVisible;
|
||||||
const visible = this.isEnabled();
|
const visible = this.isEnabled();
|
||||||
this.toggleInt(visible);
|
this.toggleInt(visible);
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this.triggerEvent("focusTree", {});
|
this.triggerEvent("focusTree", {});
|
||||||
} else {
|
} else {
|
||||||
this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId });
|
this.triggerEvent("focusOnDetail", { ntxId: appContext.tabManager.getActiveContext()?.ntxId });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.save("leftPaneVisible", this.currentLeftPaneVisible.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -217,7 +217,7 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refreshNotShown(data: NoteSwitchedContext | EventData<"activeContextChanged">) {
|
refreshNotShown(data: NoteSwitchedContext | EventData<"activeContextChanged">) {
|
||||||
const promises = [];
|
const promises: (Promise<unknown> | null | undefined)[] = [];
|
||||||
|
|
||||||
for (const subContext of data.noteContext.getMainContext().getSubContexts()) {
|
for (const subContext of data.noteContext.getMainContext().getSubContexts()) {
|
||||||
if (!subContext.ntxId) {
|
if (!subContext.ntxId) {
|
||||||
|
|||||||
@ -110,6 +110,8 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
if (e.clickEvent) {
|
if (e.clickEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
} else {
|
||||||
|
this.modal.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import utils from "../../services/utils.js";
|
|||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
import toastService from "../../services/toast.js";
|
import toastService from "../../services/toast.js";
|
||||||
import appContext from "../../components/app_context.js";
|
import appContext from "../../components/app_context.js";
|
||||||
import libraryLoader from "../../services/library_loader.js";
|
|
||||||
import openService from "../../services/open.js";
|
import openService from "../../services/open.js";
|
||||||
import protectedSessionHolder from "../../services/protected_session_holder.js";
|
import protectedSessionHolder from "../../services/protected_session_holder.js";
|
||||||
import BasicWidget from "../basic_widget.js";
|
import BasicWidget from "../basic_widget.js";
|
||||||
@ -12,6 +11,7 @@ import options from "../../services/options.js";
|
|||||||
import type FNote from "../../entities/fnote.js";
|
import type FNote from "../../entities/fnote.js";
|
||||||
import type { NoteType } from "../../entities/fnote.js";
|
import type { NoteType } from "../../entities/fnote.js";
|
||||||
import { Dropdown, Modal } from "bootstrap";
|
import { Dropdown, Modal } from "bootstrap";
|
||||||
|
import { renderMathInElement } from "../../services/math.js";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="revisions-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
<div class="revisions-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||||
@ -315,8 +315,6 @@ export default class RevisionsDialog extends BasicWidget {
|
|||||||
this.$content.html(`<div class="ck-content">${fullRevision.content}</div>`);
|
this.$content.html(`<div class="ck-content">${fullRevision.content}</div>`);
|
||||||
|
|
||||||
if (this.$content.find("span.math-tex").length > 0) {
|
if (this.$content.find("span.math-tex").length > 0) {
|
||||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
|
||||||
|
|
||||||
renderMathInElement(this.$content[0], { trust: true });
|
renderMathInElement(this.$content[0], { trust: true });
|
||||||
}
|
}
|
||||||
} else if (revisionItem.type === "code") {
|
} else if (revisionItem.type === "code") {
|
||||||
|
|||||||
@ -188,7 +188,7 @@ export default class FindWidget extends NoteContextAwareWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!["text", "code", "render"].includes(this.note?.type ?? "")) {
|
if (!["text", "code", "render", "mindMap"].includes(this.note?.type ?? "")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,13 +198,8 @@ export default class FindWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
let selectedText = "";
|
let selectedText = "";
|
||||||
if (this.note?.type === "code" && this.noteContext) {
|
if (this.note?.type === "code" && this.noteContext) {
|
||||||
if (isReadOnly){
|
const codeEditor = await this.noteContext.getCodeEditor();
|
||||||
const $content = await this.noteContext.getContentElement();
|
selectedText = codeEditor.getSelectedText();
|
||||||
selectedText = $content.find('.cm-matchhighlight').first().text();
|
|
||||||
} else {
|
|
||||||
const codeEditor = await this.noteContext.getCodeEditor();
|
|
||||||
selectedText = codeEditor.getSelection();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
selectedText = window.getSelection()?.toString() || "";
|
selectedText = window.getSelection()?.toString() || "";
|
||||||
}
|
}
|
||||||
@ -247,16 +242,18 @@ export default class FindWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getHandler() {
|
async getHandler() {
|
||||||
if (this.note?.type === "render") {
|
switch (this.note?.type) {
|
||||||
return this.htmlHandler;
|
case "render":
|
||||||
}
|
return this.htmlHandler;
|
||||||
|
case "code":
|
||||||
const readOnly = await this.noteContext?.isReadOnly();
|
return this.codeHandler;
|
||||||
|
case "text":
|
||||||
if (readOnly) {
|
const readOnly = await this.noteContext?.isReadOnly();
|
||||||
return this.htmlHandler;
|
return readOnly ? this.htmlHandler : this.textHandler;
|
||||||
} else {
|
case "mindMap":
|
||||||
return this.note?.type === "code" ? this.codeHandler : this.textHandler;
|
return this.htmlHandler;
|
||||||
|
default:
|
||||||
|
console.warn("FindWidget: Unsupported note type for find widget", this.note?.type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,7 +354,7 @@ export default class FindWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return super.isEnabled() && ["text", "code", "render"].includes(this.note?.type ?? "");
|
return super.isEnabled() && ["text", "code", "render", "mindMap"].includes(this.note?.type ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||||
|
|||||||
@ -17,10 +17,16 @@ interface Match {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SearchParameters {
|
||||||
|
searchTerm: string;
|
||||||
|
matchCase: boolean;
|
||||||
|
wholeWord: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default class FindInCode {
|
export default class FindInCode {
|
||||||
|
|
||||||
private parent: FindWidget;
|
private parent: FindWidget;
|
||||||
private findResult?: Match[] | null;
|
private searchParameters: SearchParameters | null = null;
|
||||||
|
|
||||||
constructor(parent: FindWidget) {
|
constructor(parent: FindWidget) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -31,217 +37,54 @@ export default class FindInCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async performFind(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
|
async performFind(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
|
||||||
let findResult: Match[] | null = null;
|
|
||||||
let totalFound = 0;
|
|
||||||
let currentFound = -1;
|
|
||||||
|
|
||||||
// See https://codemirror.net/addon/search/searchcursor.js for tips
|
|
||||||
const codeEditor = await this.getCodeEditor();
|
const codeEditor = await this.getCodeEditor();
|
||||||
if (!codeEditor) {
|
if (!codeEditor) {
|
||||||
return { totalFound: 0, currentFound: 0 };
|
return { totalFound: 0, currentFound: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = codeEditor.doc;
|
this.searchParameters = {
|
||||||
const text = doc.getValue();
|
searchTerm,
|
||||||
|
matchCase,
|
||||||
// Clear all markers
|
wholeWord,
|
||||||
if (this.findResult) {
|
|
||||||
codeEditor.operation(() => {
|
|
||||||
const findResult = this.findResult as Match[];
|
|
||||||
for (let i = 0; i < findResult.length; ++i) {
|
|
||||||
const marker = findResult[i];
|
|
||||||
marker.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchTerm !== "") {
|
|
||||||
searchTerm = utils.escapeRegExp(searchTerm);
|
|
||||||
|
|
||||||
// Find and highlight matches
|
|
||||||
// Find and highlight matches
|
|
||||||
// XXX Using \\b and not using the unicode flag probably doesn't
|
|
||||||
// work with non-ASCII alphabets, findAndReplace uses a more
|
|
||||||
// complicated regexp, see
|
|
||||||
// https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/utils.js#L145
|
|
||||||
const wholeWordChar = wholeWord ? "\\b" : "";
|
|
||||||
const re = new RegExp(wholeWordChar + searchTerm + wholeWordChar, "g" + (matchCase ? "" : "i"));
|
|
||||||
let curLine = 0;
|
|
||||||
let curChar = 0;
|
|
||||||
let curMatch: RegExpExecArray | null = null;
|
|
||||||
findResult = [];
|
|
||||||
// All those markText take several seconds on e.g., this ~500-line
|
|
||||||
// script, batch them inside an operation, so they become
|
|
||||||
// unnoticeable. Alternatively, an overlay could be used, see
|
|
||||||
// https://codemirror.net/addon/search/match-highlighter.js ?
|
|
||||||
codeEditor.operation(() => {
|
|
||||||
for (let i = 0; i < text.length; ++i) {
|
|
||||||
// Fetch the next match if it's the first time or if past the current match start
|
|
||||||
if (curMatch == null || curMatch.index < i) {
|
|
||||||
curMatch = re.exec(text);
|
|
||||||
if (curMatch == null) {
|
|
||||||
// No more matches
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create a non-selected highlight marker for the match, the
|
|
||||||
// selected marker highlight will be done later
|
|
||||||
if (i === curMatch.index) {
|
|
||||||
let fromPos = { line: curLine, ch: curChar };
|
|
||||||
// If multiline is supported, this needs to recalculate curLine since the match may span lines
|
|
||||||
let toPos = { line: curLine, ch: curChar + curMatch[0].length };
|
|
||||||
// or css = "color: #f3"
|
|
||||||
let marker = doc.markText(fromPos, toPos, { className: FIND_RESULT_CSS_CLASSNAME });
|
|
||||||
findResult?.push(marker);
|
|
||||||
|
|
||||||
// Set the first match beyond the cursor as the current match
|
|
||||||
if (currentFound === -1) {
|
|
||||||
const cursorPos = codeEditor.getCursor();
|
|
||||||
if (fromPos.line > cursorPos.line || (fromPos.line === cursorPos.line && fromPos.ch >= cursorPos.ch)) {
|
|
||||||
currentFound = totalFound;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalFound++;
|
|
||||||
}
|
|
||||||
// Do line and char position tracking
|
|
||||||
if (text[i] === "\n") {
|
|
||||||
curLine++;
|
|
||||||
curChar = 0;
|
|
||||||
} else {
|
|
||||||
curChar++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.findResult = findResult;
|
|
||||||
|
|
||||||
// Calculate curfound if not already, highlight it as selected
|
|
||||||
if (findResult && totalFound > 0) {
|
|
||||||
currentFound = Math.max(0, currentFound);
|
|
||||||
let marker = findResult[currentFound];
|
|
||||||
let pos = marker.find();
|
|
||||||
codeEditor.scrollIntoView(pos.to);
|
|
||||||
marker.clear();
|
|
||||||
findResult[currentFound] = doc.markText(pos.from, pos.to, { className: FIND_RESULT_SELECTED_CSS_CLASSNAME });
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalFound,
|
|
||||||
currentFound: Math.min(currentFound + 1, totalFound)
|
|
||||||
};
|
};
|
||||||
|
const { totalFound, currentFound } = await codeEditor.performFind(searchTerm, matchCase, wholeWord);
|
||||||
|
return { totalFound, currentFound };
|
||||||
}
|
}
|
||||||
|
|
||||||
async findNext(direction: number, currentFound: number, nextFound: number) {
|
async findNext(direction: number, currentFound: number, nextFound: number) {
|
||||||
const codeEditor = await this.getCodeEditor();
|
const codeEditor = await this.getCodeEditor();
|
||||||
if (!codeEditor || !this.findResult) {
|
if (!codeEditor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const doc = codeEditor.doc;
|
codeEditor.findNext(direction, currentFound, nextFound);
|
||||||
|
|
||||||
//
|
|
||||||
// Dehighlight current, highlight & scrollIntoView next
|
|
||||||
//
|
|
||||||
|
|
||||||
let marker = this.findResult[currentFound];
|
|
||||||
let pos = marker.find();
|
|
||||||
marker.clear();
|
|
||||||
marker = doc.markText(pos.from, pos.to, { className: FIND_RESULT_CSS_CLASSNAME });
|
|
||||||
this.findResult[currentFound] = marker;
|
|
||||||
|
|
||||||
marker = this.findResult[nextFound];
|
|
||||||
pos = marker.find();
|
|
||||||
marker.clear();
|
|
||||||
marker = doc.markText(pos.from, pos.to, { className: FIND_RESULT_SELECTED_CSS_CLASSNAME });
|
|
||||||
this.findResult[nextFound] = marker;
|
|
||||||
|
|
||||||
codeEditor.scrollIntoView(pos.from);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findBoxClosed(totalFound: number, currentFound: number) {
|
async findBoxClosed(totalFound: number, currentFound: number) {
|
||||||
const codeEditor = await this.getCodeEditor();
|
const codeEditor = await this.getCodeEditor();
|
||||||
|
codeEditor?.cleanSearch();
|
||||||
if (codeEditor && totalFound > 0) {
|
|
||||||
const doc = codeEditor.doc;
|
|
||||||
const pos = this.findResult?.[currentFound].find();
|
|
||||||
// Note setting the selection sets the cursor to
|
|
||||||
// the end of the selection and scrolls it into
|
|
||||||
// view
|
|
||||||
if (pos) {
|
|
||||||
doc.setSelection(pos.from, pos.to);
|
|
||||||
}
|
|
||||||
// Clear all markers
|
|
||||||
codeEditor.operation(() => {
|
|
||||||
if (!this.findResult) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.findResult.length; ++i) {
|
|
||||||
let marker = this.findResult[i];
|
|
||||||
marker.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.findResult = null;
|
|
||||||
|
|
||||||
codeEditor?.focus();
|
codeEditor?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async replace(replaceText: string) {
|
async replace(replaceText: string) {
|
||||||
// this.findResult may be undefined and null
|
|
||||||
if (!this.findResult || this.findResult.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let currentFound = -1;
|
|
||||||
this.findResult.forEach((marker, index) => {
|
|
||||||
const pos = marker.find();
|
|
||||||
if (pos) {
|
|
||||||
if (marker.className === FIND_RESULT_SELECTED_CSS_CLASSNAME) {
|
|
||||||
currentFound = index;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (currentFound >= 0) {
|
|
||||||
let marker = this.findResult[currentFound];
|
|
||||||
let pos = marker.find();
|
|
||||||
const codeEditor = await this.getCodeEditor();
|
|
||||||
const doc = codeEditor?.doc;
|
|
||||||
if (doc) {
|
|
||||||
doc.replaceRange(replaceText, pos.from, pos.to);
|
|
||||||
}
|
|
||||||
marker.clear();
|
|
||||||
|
|
||||||
let nextFound;
|
|
||||||
if (currentFound === this.findResult.length - 1) {
|
|
||||||
nextFound = 0;
|
|
||||||
} else {
|
|
||||||
nextFound = currentFound;
|
|
||||||
}
|
|
||||||
this.findResult.splice(currentFound, 1);
|
|
||||||
if (this.findResult.length > 0) {
|
|
||||||
this.findNext(0, nextFound, nextFound);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async replaceAll(replaceText: string) {
|
|
||||||
if (!this.findResult || this.findResult.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const codeEditor = await this.getCodeEditor();
|
const codeEditor = await this.getCodeEditor();
|
||||||
const doc = codeEditor?.doc;
|
await codeEditor?.replace(replaceText);
|
||||||
codeEditor?.operation(() => {
|
this.rerunSearch();
|
||||||
if (!this.findResult) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let currentFound = 0; currentFound < this.findResult.length; currentFound++) {
|
|
||||||
let marker = this.findResult[currentFound];
|
|
||||||
let pos = marker.find();
|
|
||||||
doc?.replaceRange(replaceText, pos.from, pos.to);
|
|
||||||
marker.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.findResult = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async replaceAll(replaceText: string) {
|
||||||
|
const codeEditor = await this.getCodeEditor();
|
||||||
|
await codeEditor?.replaceAll(replaceText);
|
||||||
|
this.rerunSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private rerunSearch() {
|
||||||
|
if (this.searchParameters) {
|
||||||
|
this.performFind(
|
||||||
|
this.searchParameters.searchTerm,
|
||||||
|
this.searchParameters.matchCase,
|
||||||
|
this.searchParameters.wholeWord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// ck-find-result and ck-find-result_selected are the styles ck-editor
|
// ck-find-result and ck-find-result_selected are the styles ck-editor
|
||||||
// uses for highlighting matches, use the same one on CodeMirror
|
// uses for highlighting matches, use the same one on CodeMirror
|
||||||
// for consistency
|
// for consistency
|
||||||
|
import type Mark from "mark.js";
|
||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import type FindWidget from "./find.js";
|
import type FindWidget from "./find.js";
|
||||||
import type { FindResult } from "./find.js";
|
import type { FindResult } from "./find.js";
|
||||||
@ -13,6 +14,7 @@ export default class FindInHtml {
|
|||||||
private parent: FindWidget;
|
private parent: FindWidget;
|
||||||
private currentIndex: number;
|
private currentIndex: number;
|
||||||
private $results: JQuery<HTMLElement> | null;
|
private $results: JQuery<HTMLElement> | null;
|
||||||
|
private mark?: Mark;
|
||||||
|
|
||||||
constructor(parent: FindWidget) {
|
constructor(parent: FindWidget) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -21,28 +23,31 @@ export default class FindInHtml {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async performFind(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
|
async performFind(searchTerm: string, matchCase: boolean, wholeWord: boolean) {
|
||||||
await import("script-loader!mark.js/dist/jquery.mark.min.js");
|
|
||||||
|
|
||||||
const $content = await this.parent?.noteContext?.getContentElement();
|
const $content = await this.parent?.noteContext?.getContentElement();
|
||||||
|
if (!$content || !$content.length) {
|
||||||
|
return Promise.resolve({ totalFound: 0, currentFound: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.mark) {
|
||||||
|
this.mark = new (await import("mark.js")).default($content[0]);
|
||||||
|
}
|
||||||
|
|
||||||
const wholeWordChar = wholeWord ? "\\b" : "";
|
const wholeWordChar = wholeWord ? "\\b" : "";
|
||||||
const regExp = new RegExp(wholeWordChar + utils.escapeRegExp(searchTerm) + wholeWordChar, matchCase ? "g" : "gi");
|
const regExp = new RegExp(wholeWordChar + utils.escapeRegExp(searchTerm) + wholeWordChar, matchCase ? "g" : "gi");
|
||||||
|
|
||||||
return new Promise<FindResult>((res) => {
|
return new Promise<FindResult>((res) => {
|
||||||
$content?.unmark({
|
this.mark!.unmark({
|
||||||
done: () => {
|
done: () => {
|
||||||
$content.markRegExp(regExp, {
|
this.mark!.markRegExp(regExp, {
|
||||||
element: "span",
|
element: "span",
|
||||||
className: FIND_RESULT_CSS_CLASSNAME,
|
className: FIND_RESULT_CSS_CLASSNAME,
|
||||||
separateWordSearch: false,
|
|
||||||
caseSensitive: matchCase,
|
|
||||||
done: async () => {
|
done: async () => {
|
||||||
this.$results = $content.find(`.${FIND_RESULT_CSS_CLASSNAME}`);
|
this.$results = $content.find(`.${FIND_RESULT_CSS_CLASSNAME}`);
|
||||||
const scrollingContainer = $content[0].closest('.scrolling-container');
|
const scrollingContainer = $content[0].closest('.scrolling-container');
|
||||||
const containerTop = scrollingContainer?.getBoundingClientRect().top ?? 0;
|
const containerTop = scrollingContainer?.getBoundingClientRect().top ?? 0;
|
||||||
const closestIndex = this.$results.toArray().findIndex(el => el.getBoundingClientRect().top >= containerTop);
|
const closestIndex = this.$results.toArray().findIndex(el => el.getBoundingClientRect().top >= containerTop);
|
||||||
this.currentIndex = closestIndex >= 0 ? closestIndex : 0;
|
this.currentIndex = closestIndex >= 0 ? closestIndex : 0;
|
||||||
|
|
||||||
await this.jumpTo();
|
await this.jumpTo();
|
||||||
|
|
||||||
res({
|
res({
|
||||||
@ -73,17 +78,14 @@ export default class FindInHtml {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async findBoxClosed(totalFound: number, currentFound: number) {
|
async findBoxClosed(totalFound: number, currentFound: number) {
|
||||||
const $content = await this.parent?.noteContext?.getContentElement();
|
this.mark?.unmark();
|
||||||
if (typeof $content?.unmark === 'function') {
|
|
||||||
$content.unmark();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async jumpTo() {
|
async jumpTo() {
|
||||||
if (this.$results?.length) {
|
if (this.$results?.length) {
|
||||||
const $current = this.$results.eq(this.currentIndex);
|
const $current = this.$results.eq(this.currentIndex);
|
||||||
this.$results.removeClass(FIND_RESULT_SELECTED_CSS_CLASSNAME);
|
this.$results.removeClass(FIND_RESULT_SELECTED_CSS_CLASSNAME);
|
||||||
$current[0].scrollIntoView();
|
$current[0].scrollIntoView({ block: 'center', inline: 'center'});
|
||||||
$current.addClass(FIND_RESULT_SELECTED_CSS_CLASSNAME);
|
$current.addClass(FIND_RESULT_SELECTED_CSS_CLASSNAME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,16 +2,6 @@ import type { FindAndReplaceState, FindCommandResult } from "@triliumnext/ckedit
|
|||||||
import type { FindResult } from "./find.js";
|
import type { FindResult } from "./find.js";
|
||||||
import type FindWidget from "./find.js";
|
import type FindWidget from "./find.js";
|
||||||
|
|
||||||
// TODO: Deduplicate.
|
|
||||||
interface Match {
|
|
||||||
className: string;
|
|
||||||
clear(): void;
|
|
||||||
find(): {
|
|
||||||
from: number;
|
|
||||||
to: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class FindInText {
|
export default class FindInText {
|
||||||
|
|
||||||
private parent: FindWidget;
|
private parent: FindWidget;
|
||||||
@ -35,7 +25,7 @@ export default class FindInText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const model = textEditor.model;
|
const model = textEditor.model;
|
||||||
let findResult = null;
|
let findResult: FindCommandResult | null = null;
|
||||||
let totalFound = 0;
|
let totalFound = 0;
|
||||||
let currentFound = -1;
|
let currentFound = -1;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { t } from "../../services/i18n.js";
|
|||||||
import LoadResults from "../../services/load_results.js";
|
import LoadResults from "../../services/load_results.js";
|
||||||
import type { AttributeRow } from "../../services/load_results.js";
|
import type { AttributeRow } from "../../services/load_results.js";
|
||||||
import FNote from "../../entities/fnote.js";
|
import FNote from "../../entities/fnote.js";
|
||||||
|
import options from "../../services/options.js";
|
||||||
|
|
||||||
export default class EditButton extends OnClickButtonWidget {
|
export default class EditButton extends OnClickButtonWidget {
|
||||||
isEnabled(): boolean {
|
isEnabled(): boolean {
|
||||||
@ -27,6 +28,10 @@ export default class EditButton extends OnClickButtonWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote(note: FNote): Promise<void> {
|
async refreshWithNote(note: FNote): Promise<void> {
|
||||||
|
if (options.is("databaseReadonly")) {
|
||||||
|
this.toggleInt(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
|
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||||
this.toggleInt(false);
|
this.toggleInt(false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import RightPanelWidget from "./right_panel_widget.js";
|
|||||||
import options from "../services/options.js";
|
import options from "../services/options.js";
|
||||||
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
import OnClickButtonWidget from "./buttons/onclick_button.js";
|
||||||
import appContext, { type EventData } from "../components/app_context.js";
|
import appContext, { type EventData } from "../components/app_context.js";
|
||||||
import libraryLoader from "../services/library_loader.js";
|
|
||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
|
import katex from "../services/math.js";
|
||||||
|
|
||||||
const TPL = /*html*/`<div class="highlights-list-widget">
|
const TPL = /*html*/`<div class="highlights-list-widget">
|
||||||
<style>
|
<style>
|
||||||
@ -175,7 +175,6 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ReferenceError && e.message.includes("katex is not defined")) {
|
if (e instanceof ReferenceError && e.message.includes("katex is not defined")) {
|
||||||
// Load KaTeX if it is not already loaded
|
// Load KaTeX if it is not already loaded
|
||||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
|
||||||
try {
|
try {
|
||||||
rendered = katex.renderToString(latexCode, {
|
rendered = katex.renderToString(latexCode, {
|
||||||
throwOnError: false
|
throwOnError: false
|
||||||
@ -244,7 +243,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
// Used to determine if a string is only a formula
|
// Used to determine if a string is only a formula
|
||||||
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;
|
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;
|
||||||
|
|
||||||
for (let match = null, hltIndex = 0; (match = combinedRegex.exec(content)) !== null; hltIndex++) {
|
for (let match: RegExpMatchArray | null = null, hltIndex = 0; (match = combinedRegex.exec(content)) !== null; hltIndex++) {
|
||||||
const subHtml = match[0];
|
const subHtml = match[0];
|
||||||
const startIndex = match.index;
|
const startIndex = match.index;
|
||||||
const endIndex = combinedRegex.lastIndex;
|
const endIndex = combinedRegex.lastIndex;
|
||||||
@ -325,8 +324,9 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const textEditor = await this.noteContext.getTextEditor();
|
const textEditor = await this.noteContext.getTextEditor();
|
||||||
if (textEditor) {
|
const el = textEditor?.editing.view.domRoots.values().next().value;
|
||||||
targetElement = $(textEditor.editing.view.domRoots.values().next().value)
|
if (el) {
|
||||||
|
targetElement = $(el)
|
||||||
.find(findSubStr)
|
.find(findSubStr)
|
||||||
.filter(function () {
|
.filter(function () {
|
||||||
// When finding span[style*="color"] but not looking for span[style*="background-color"],
|
// When finding span[style*="color"] but not looking for span[style*="background-color"],
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import BasicWidget from "../basic_widget.js";
|
|||||||
import toastService from "../../services/toast.js";
|
import toastService from "../../services/toast.js";
|
||||||
import appContext from "../../components/app_context.js";
|
import appContext from "../../components/app_context.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
|
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||||
|
|
||||||
import { TPL, addMessageToChat, showSources, hideSources, showLoadingIndicator, hideLoadingIndicator } from "./ui.js";
|
import { TPL, addMessageToChat, showSources, hideSources, showLoadingIndicator, hideLoadingIndicator } from "./ui.js";
|
||||||
import { formatMarkdown } from "./utils.js";
|
import { formatMarkdown } from "./utils.js";
|
||||||
@ -12,14 +13,17 @@ import { createChatSession, checkSessionExists, setupStreamingResponse, getDirec
|
|||||||
import { extractInChatToolSteps } from "./message_processor.js";
|
import { extractInChatToolSteps } from "./message_processor.js";
|
||||||
import { validateEmbeddingProviders } from "./validation.js";
|
import { validateEmbeddingProviders } from "./validation.js";
|
||||||
import type { MessageData, ToolExecutionStep, ChatData } from "./types.js";
|
import type { MessageData, ToolExecutionStep, ChatData } from "./types.js";
|
||||||
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
|
import { formatCodeBlocks } from "../../services/syntax_highlight.js";
|
||||||
|
import { ClassicEditor, type CKTextEditor, type MentionFeed } from "@triliumnext/ckeditor5";
|
||||||
|
import type { Suggestion } from "../../services/note_autocomplete.js";
|
||||||
|
|
||||||
import "../../stylesheets/llm_chat.css";
|
import "../../stylesheets/llm_chat.css";
|
||||||
|
|
||||||
export default class LlmChatPanel extends BasicWidget {
|
export default class LlmChatPanel extends BasicWidget {
|
||||||
private noteContextChatMessages!: HTMLElement;
|
private noteContextChatMessages!: HTMLElement;
|
||||||
private noteContextChatForm!: HTMLFormElement;
|
private noteContextChatForm!: HTMLFormElement;
|
||||||
private noteContextChatInput!: HTMLTextAreaElement;
|
private noteContextChatInput!: HTMLElement;
|
||||||
|
private noteContextChatInputEditor!: CKTextEditor;
|
||||||
private noteContextChatSendButton!: HTMLButtonElement;
|
private noteContextChatSendButton!: HTMLButtonElement;
|
||||||
private chatContainer!: HTMLElement;
|
private chatContainer!: HTMLElement;
|
||||||
private loadingIndicator!: HTMLElement;
|
private loadingIndicator!: HTMLElement;
|
||||||
@ -29,6 +33,10 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
private useAdvancedContextCheckbox!: HTMLInputElement;
|
private useAdvancedContextCheckbox!: HTMLInputElement;
|
||||||
private showThinkingCheckbox!: HTMLInputElement;
|
private showThinkingCheckbox!: HTMLInputElement;
|
||||||
private validationWarning!: HTMLElement;
|
private validationWarning!: HTMLElement;
|
||||||
|
private thinkingContainer!: HTMLElement;
|
||||||
|
private thinkingBubble!: HTMLElement;
|
||||||
|
private thinkingText!: HTMLElement;
|
||||||
|
private thinkingToggle!: HTMLElement;
|
||||||
private chatNoteId: string | null = null;
|
private chatNoteId: string | null = null;
|
||||||
private noteId: string | null = null; // The actual noteId for the Chat Note
|
private noteId: string | null = null; // The actual noteId for the Chat Note
|
||||||
private currentNoteId: string | null = null;
|
private currentNoteId: string | null = null;
|
||||||
@ -104,7 +112,7 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
const element = this.$widget[0];
|
const element = this.$widget[0];
|
||||||
this.noteContextChatMessages = element.querySelector('.note-context-chat-messages') as HTMLElement;
|
this.noteContextChatMessages = element.querySelector('.note-context-chat-messages') as HTMLElement;
|
||||||
this.noteContextChatForm = element.querySelector('.note-context-chat-form') as HTMLFormElement;
|
this.noteContextChatForm = element.querySelector('.note-context-chat-form') as HTMLFormElement;
|
||||||
this.noteContextChatInput = element.querySelector('.note-context-chat-input') as HTMLTextAreaElement;
|
this.noteContextChatInput = element.querySelector('.note-context-chat-input') as HTMLElement;
|
||||||
this.noteContextChatSendButton = element.querySelector('.note-context-chat-send-button') as HTMLButtonElement;
|
this.noteContextChatSendButton = element.querySelector('.note-context-chat-send-button') as HTMLButtonElement;
|
||||||
this.chatContainer = element.querySelector('.note-context-chat-container') as HTMLElement;
|
this.chatContainer = element.querySelector('.note-context-chat-container') as HTMLElement;
|
||||||
this.loadingIndicator = element.querySelector('.loading-indicator') as HTMLElement;
|
this.loadingIndicator = element.querySelector('.loading-indicator') as HTMLElement;
|
||||||
@ -114,6 +122,10 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
this.useAdvancedContextCheckbox = element.querySelector('.use-advanced-context-checkbox') as HTMLInputElement;
|
this.useAdvancedContextCheckbox = element.querySelector('.use-advanced-context-checkbox') as HTMLInputElement;
|
||||||
this.showThinkingCheckbox = element.querySelector('.show-thinking-checkbox') as HTMLInputElement;
|
this.showThinkingCheckbox = element.querySelector('.show-thinking-checkbox') as HTMLInputElement;
|
||||||
this.validationWarning = element.querySelector('.provider-validation-warning') as HTMLElement;
|
this.validationWarning = element.querySelector('.provider-validation-warning') as HTMLElement;
|
||||||
|
this.thinkingContainer = element.querySelector('.llm-thinking-container') as HTMLElement;
|
||||||
|
this.thinkingBubble = element.querySelector('.thinking-bubble') as HTMLElement;
|
||||||
|
this.thinkingText = element.querySelector('.thinking-text') as HTMLElement;
|
||||||
|
this.thinkingToggle = element.querySelector('.thinking-toggle') as HTMLElement;
|
||||||
|
|
||||||
// Set up event delegation for the settings link
|
// Set up event delegation for the settings link
|
||||||
this.validationWarning.addEventListener('click', (e) => {
|
this.validationWarning.addEventListener('click', (e) => {
|
||||||
@ -124,15 +136,84 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.initializeEventListeners();
|
// Set up thinking toggle functionality
|
||||||
|
this.setupThinkingToggle();
|
||||||
|
|
||||||
|
// Initialize CKEditor with mention support (async)
|
||||||
|
this.initializeCKEditor().then(() => {
|
||||||
|
this.initializeEventListeners();
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Failed to initialize CKEditor, falling back to basic event listeners:', error);
|
||||||
|
this.initializeBasicEventListeners();
|
||||||
|
});
|
||||||
|
|
||||||
return this.$widget;
|
return this.$widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async initializeCKEditor() {
|
||||||
|
const mentionSetup: MentionFeed[] = [
|
||||||
|
{
|
||||||
|
marker: "@",
|
||||||
|
feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
||||||
|
itemRenderer: (item) => {
|
||||||
|
const suggestion = item as Suggestion;
|
||||||
|
const itemElement = document.createElement("button");
|
||||||
|
itemElement.innerHTML = `${suggestion.highlightedNotePathTitle} `;
|
||||||
|
return itemElement;
|
||||||
|
},
|
||||||
|
minimumCharacters: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
this.noteContextChatInputEditor = await ClassicEditor.create(this.noteContextChatInput, {
|
||||||
|
toolbar: {
|
||||||
|
items: [] // No toolbar for chat input
|
||||||
|
},
|
||||||
|
placeholder: this.noteContextChatInput.getAttribute('data-placeholder') || 'Enter your message...',
|
||||||
|
mention: {
|
||||||
|
feeds: mentionSetup
|
||||||
|
},
|
||||||
|
licenseKey: "GPL"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set minimal height
|
||||||
|
const editorElement = this.noteContextChatInputEditor.ui.getEditableElement();
|
||||||
|
if (editorElement) {
|
||||||
|
editorElement.style.minHeight = '60px';
|
||||||
|
editorElement.style.maxHeight = '200px';
|
||||||
|
editorElement.style.overflowY = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up keybindings after editor is ready
|
||||||
|
this.setupEditorKeyBindings();
|
||||||
|
|
||||||
|
console.log('CKEditor initialized successfully for LLM chat input');
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeBasicEventListeners() {
|
||||||
|
// Fallback event listeners for when CKEditor fails to initialize
|
||||||
|
this.noteContextChatForm.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// In fallback mode, the noteContextChatInput should contain a textarea
|
||||||
|
const textarea = this.noteContextChatInput.querySelector('textarea');
|
||||||
|
if (textarea) {
|
||||||
|
const content = textarea.value;
|
||||||
|
this.sendMessage(content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
console.log(`LlmChatPanel cleanup called, removing any active WebSocket subscriptions`);
|
console.log(`LlmChatPanel cleanup called, removing any active WebSocket subscriptions`);
|
||||||
this._messageHandler = null;
|
this._messageHandler = null;
|
||||||
this._messageHandlerId = null;
|
this._messageHandlerId = null;
|
||||||
|
|
||||||
|
// Clean up CKEditor instance
|
||||||
|
if (this.noteContextChatInputEditor) {
|
||||||
|
this.noteContextChatInputEditor.destroy().catch(error => {
|
||||||
|
console.error('Error destroying CKEditor:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -531,18 +612,31 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
private async sendMessage(content: string) {
|
private async sendMessage(content: string) {
|
||||||
if (!content.trim()) return;
|
if (!content.trim()) return;
|
||||||
|
|
||||||
|
// Extract mentions from the content if using CKEditor
|
||||||
|
let mentions: Array<{noteId: string; title: string; notePath: string}> = [];
|
||||||
|
let plainTextContent = content;
|
||||||
|
|
||||||
|
if (this.noteContextChatInputEditor) {
|
||||||
|
const extracted = this.extractMentionsAndContent(content);
|
||||||
|
mentions = extracted.mentions;
|
||||||
|
plainTextContent = extracted.content;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the user message to the UI and data model
|
// Add the user message to the UI and data model
|
||||||
this.addMessageToChat('user', content);
|
this.addMessageToChat('user', plainTextContent);
|
||||||
this.messages.push({
|
this.messages.push({
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: content
|
content: plainTextContent,
|
||||||
|
mentions: mentions.length > 0 ? mentions : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save the data immediately after a user message
|
// Save the data immediately after a user message
|
||||||
await this.saveCurrentData();
|
await this.saveCurrentData();
|
||||||
|
|
||||||
// Clear input and show loading state
|
// Clear input and show loading state
|
||||||
this.noteContextChatInput.value = '';
|
if (this.noteContextChatInputEditor) {
|
||||||
|
this.noteContextChatInputEditor.setData('');
|
||||||
|
}
|
||||||
showLoadingIndicator(this.loadingIndicator);
|
showLoadingIndicator(this.loadingIndicator);
|
||||||
this.hideSources();
|
this.hideSources();
|
||||||
|
|
||||||
@ -555,9 +649,10 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
|
|
||||||
// Create the message parameters
|
// Create the message parameters
|
||||||
const messageParams = {
|
const messageParams = {
|
||||||
content,
|
content: plainTextContent,
|
||||||
useAdvancedContext,
|
useAdvancedContext,
|
||||||
showThinking
|
showThinking,
|
||||||
|
mentions: mentions.length > 0 ? mentions : undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try websocket streaming (preferred method)
|
// Try websocket streaming (preferred method)
|
||||||
@ -621,7 +716,9 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear input and show loading state
|
// Clear input and show loading state
|
||||||
this.noteContextChatInput.value = '';
|
if (this.noteContextChatInputEditor) {
|
||||||
|
this.noteContextChatInputEditor.setData('');
|
||||||
|
}
|
||||||
showLoadingIndicator(this.loadingIndicator);
|
showLoadingIndicator(this.loadingIndicator);
|
||||||
this.hideSources();
|
this.hideSources();
|
||||||
|
|
||||||
@ -898,6 +995,16 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
* Update the UI with streaming content
|
* Update the UI with streaming content
|
||||||
*/
|
*/
|
||||||
private updateStreamingUI(assistantResponse: string, isDone: boolean = false) {
|
private updateStreamingUI(assistantResponse: string, isDone: boolean = false) {
|
||||||
|
// Parse and handle thinking content if present
|
||||||
|
if (!isDone) {
|
||||||
|
const thinkingContent = this.parseThinkingContent(assistantResponse);
|
||||||
|
if (thinkingContent) {
|
||||||
|
this.updateThinkingText(thinkingContent);
|
||||||
|
// Don't display the raw response with think tags in the chat
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the existing assistant message or create a new one
|
// Get the existing assistant message or create a new one
|
||||||
let assistantMessageEl = this.noteContextChatMessages.querySelector('.assistant-message:last-child');
|
let assistantMessageEl = this.noteContextChatMessages.querySelector('.assistant-message:last-child');
|
||||||
|
|
||||||
@ -919,13 +1026,19 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
assistantMessageEl.appendChild(messageContent);
|
assistantMessageEl.appendChild(messageContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean the response to remove thinking tags before displaying
|
||||||
|
const cleanedResponse = this.removeThinkingTags(assistantResponse);
|
||||||
|
|
||||||
// Update the content
|
// Update the content
|
||||||
const messageContent = assistantMessageEl.querySelector('.message-content') as HTMLElement;
|
const messageContent = assistantMessageEl.querySelector('.message-content') as HTMLElement;
|
||||||
messageContent.innerHTML = formatMarkdown(assistantResponse);
|
messageContent.innerHTML = formatMarkdown(cleanedResponse);
|
||||||
|
|
||||||
// Apply syntax highlighting if this is the final update
|
// Apply syntax highlighting if this is the final update
|
||||||
if (isDone) {
|
if (isDone) {
|
||||||
applySyntaxHighlight($(assistantMessageEl as HTMLElement));
|
formatCodeBlocks($(assistantMessageEl as HTMLElement));
|
||||||
|
|
||||||
|
// Hide the thinking display when response is complete
|
||||||
|
this.hideThinkingDisplay();
|
||||||
|
|
||||||
// Update message in the data model for storage
|
// Update message in the data model for storage
|
||||||
// Find the last assistant message to update, or add a new one if none exists
|
// Find the last assistant message to update, or add a new one if none exists
|
||||||
@ -934,13 +1047,13 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
this.messages.lastIndexOf(assistantMessages[assistantMessages.length - 1]) : -1;
|
this.messages.lastIndexOf(assistantMessages[assistantMessages.length - 1]) : -1;
|
||||||
|
|
||||||
if (lastAssistantMsgIndex >= 0) {
|
if (lastAssistantMsgIndex >= 0) {
|
||||||
// Update existing message
|
// Update existing message with cleaned content
|
||||||
this.messages[lastAssistantMsgIndex].content = assistantResponse;
|
this.messages[lastAssistantMsgIndex].content = cleanedResponse;
|
||||||
} else {
|
} else {
|
||||||
// Add new message
|
// Add new message with cleaned content
|
||||||
this.messages.push({
|
this.messages.push({
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
content: assistantResponse
|
content: cleanedResponse
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,6 +1070,16 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
|
this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove thinking tags from response content
|
||||||
|
*/
|
||||||
|
private removeThinkingTags(content: string): string {
|
||||||
|
if (!content) return content;
|
||||||
|
|
||||||
|
// Remove <think>...</think> blocks from the content
|
||||||
|
return content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle general errors in the send message flow
|
* Handle general errors in the send message flow
|
||||||
*/
|
*/
|
||||||
@ -1203,32 +1326,308 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
* Show thinking state in the UI
|
* Show thinking state in the UI
|
||||||
*/
|
*/
|
||||||
private showThinkingState(thinkingData: string) {
|
private showThinkingState(thinkingData: string) {
|
||||||
// Thinking state is now updated via the in-chat UI in updateStreamingUI
|
// Parse the thinking content to extract text between <think> tags
|
||||||
// This method is now just a hook for the WebSocket handlers
|
const thinkingContent = this.parseThinkingContent(thinkingData);
|
||||||
|
|
||||||
// Show the loading indicator
|
if (thinkingContent) {
|
||||||
|
this.showThinkingDisplay(thinkingContent);
|
||||||
|
} else {
|
||||||
|
// Fallback: show raw thinking data
|
||||||
|
this.showThinkingDisplay(thinkingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the loading indicator as well
|
||||||
this.loadingIndicator.style.display = 'flex';
|
this.loadingIndicator.style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse thinking content from LLM response
|
||||||
|
*/
|
||||||
|
private parseThinkingContent(content: string): string | null {
|
||||||
|
if (!content) return null;
|
||||||
|
|
||||||
|
// Look for content between <think> and </think> tags
|
||||||
|
const thinkRegex = /<think>([\s\S]*?)<\/think>/gi;
|
||||||
|
const matches: string[] = [];
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while ((match = thinkRegex.exec(content)) !== null) {
|
||||||
|
matches.push(match[1].trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.length > 0) {
|
||||||
|
return matches.join('\n\n--- Next thought ---\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for incomplete thinking blocks (streaming in progress)
|
||||||
|
const incompleteThinkRegex = /<think>([\s\S]*?)$/i;
|
||||||
|
const incompleteMatch = content.match(incompleteThinkRegex);
|
||||||
|
|
||||||
|
if (incompleteMatch && incompleteMatch[1]) {
|
||||||
|
return incompleteMatch[1].trim() + '\n\n[Thinking in progress...]';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no think tags found, check if the entire content might be thinking
|
||||||
|
if (content.toLowerCase().includes('thinking') ||
|
||||||
|
content.toLowerCase().includes('reasoning') ||
|
||||||
|
content.toLowerCase().includes('let me think') ||
|
||||||
|
content.toLowerCase().includes('i need to') ||
|
||||||
|
content.toLowerCase().includes('first, ') ||
|
||||||
|
content.toLowerCase().includes('step 1') ||
|
||||||
|
content.toLowerCase().includes('analysis:')) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private initializeEventListeners() {
|
private initializeEventListeners() {
|
||||||
this.noteContextChatForm.addEventListener('submit', (e) => {
|
this.noteContextChatForm.addEventListener('submit', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const content = this.noteContextChatInput.value;
|
|
||||||
this.sendMessage(content);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add auto-resize functionality to the textarea
|
let content = '';
|
||||||
this.noteContextChatInput.addEventListener('input', () => {
|
|
||||||
this.noteContextChatInput.style.height = 'auto';
|
|
||||||
this.noteContextChatInput.style.height = `${this.noteContextChatInput.scrollHeight}px`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle Enter key (send on Enter, new line on Shift+Enter)
|
if (this.noteContextChatInputEditor && this.noteContextChatInputEditor.getData) {
|
||||||
this.noteContextChatInput.addEventListener('keydown', (e) => {
|
// Use CKEditor content
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
content = this.noteContextChatInputEditor.getData();
|
||||||
e.preventDefault();
|
} else {
|
||||||
this.noteContextChatForm.dispatchEvent(new Event('submit'));
|
// Fallback: check if there's a textarea (fallback mode)
|
||||||
|
const textarea = this.noteContextChatInput.querySelector('textarea');
|
||||||
|
if (textarea) {
|
||||||
|
content = textarea.value;
|
||||||
|
} else {
|
||||||
|
// Last resort: try to get text content from the div
|
||||||
|
content = this.noteContextChatInput.textContent || this.noteContextChatInput.innerText || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.trim()) {
|
||||||
|
this.sendMessage(content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle Enter key (send on Enter, new line on Shift+Enter) via CKEditor
|
||||||
|
// We'll set this up after CKEditor is initialized
|
||||||
|
this.setupEditorKeyBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupEditorKeyBindings() {
|
||||||
|
if (this.noteContextChatInputEditor && this.noteContextChatInputEditor.keystrokes) {
|
||||||
|
try {
|
||||||
|
this.noteContextChatInputEditor.keystrokes.set('Enter', (key, stop) => {
|
||||||
|
if (!key.shiftKey) {
|
||||||
|
stop();
|
||||||
|
this.noteContextChatForm.dispatchEvent(new Event('submit'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('CKEditor keybindings set up successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to set up CKEditor keybindings:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract note mentions and content from CKEditor
|
||||||
|
*/
|
||||||
|
private extractMentionsAndContent(editorData: string): { content: string; mentions: Array<{noteId: string; title: string; notePath: string}> } {
|
||||||
|
const mentions: Array<{noteId: string; title: string; notePath: string}> = [];
|
||||||
|
|
||||||
|
// Parse the HTML content to extract mentions
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = editorData;
|
||||||
|
|
||||||
|
// Find all mention elements - CKEditor uses specific patterns for mentions
|
||||||
|
// Look for elements with data-mention attribute or specific mention classes
|
||||||
|
const mentionElements = tempDiv.querySelectorAll('[data-mention], .mention, span[data-id]');
|
||||||
|
|
||||||
|
mentionElements.forEach(mentionEl => {
|
||||||
|
try {
|
||||||
|
// Try different ways to extract mention data based on CKEditor's format
|
||||||
|
let mentionData: any = null;
|
||||||
|
|
||||||
|
// Method 1: data-mention attribute (JSON format)
|
||||||
|
if (mentionEl.hasAttribute('data-mention')) {
|
||||||
|
mentionData = JSON.parse(mentionEl.getAttribute('data-mention') || '{}');
|
||||||
|
}
|
||||||
|
// Method 2: data-id attribute (simple format)
|
||||||
|
else if (mentionEl.hasAttribute('data-id')) {
|
||||||
|
const dataId = mentionEl.getAttribute('data-id');
|
||||||
|
const textContent = mentionEl.textContent || '';
|
||||||
|
|
||||||
|
// Parse the dataId to extract note information
|
||||||
|
if (dataId && dataId.startsWith('@')) {
|
||||||
|
const cleanId = dataId.substring(1); // Remove the @
|
||||||
|
mentionData = {
|
||||||
|
id: cleanId,
|
||||||
|
name: textContent,
|
||||||
|
notePath: cleanId // Assume the ID contains the path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Method 3: Check if this is a reference link (href=#notePath)
|
||||||
|
else if (mentionEl.tagName === 'A' && mentionEl.hasAttribute('href')) {
|
||||||
|
const href = mentionEl.getAttribute('href');
|
||||||
|
if (href && href.startsWith('#')) {
|
||||||
|
const notePath = href.substring(1);
|
||||||
|
mentionData = {
|
||||||
|
notePath: notePath,
|
||||||
|
noteTitle: mentionEl.textContent || 'Unknown Note'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mentionData && (mentionData.notePath || mentionData.link)) {
|
||||||
|
const notePath = mentionData.notePath || mentionData.link?.substring(1); // Remove # from link
|
||||||
|
const noteId = notePath ? notePath.split('/').pop() : null;
|
||||||
|
const title = mentionData.noteTitle || mentionData.name || mentionEl.textContent || 'Unknown Note';
|
||||||
|
|
||||||
|
if (noteId) {
|
||||||
|
mentions.push({
|
||||||
|
noteId: noteId,
|
||||||
|
title: title,
|
||||||
|
notePath: notePath
|
||||||
|
});
|
||||||
|
console.log(`Extracted mention: noteId=${noteId}, title=${title}, notePath=${notePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to parse mention data:', e, mentionEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to plain text for the LLM, but preserve the structure
|
||||||
|
const content = tempDiv.textContent || tempDiv.innerText || '';
|
||||||
|
|
||||||
|
console.log(`Extracted ${mentions.length} mentions from editor content`);
|
||||||
|
return { content, mentions };
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupThinkingToggle() {
|
||||||
|
if (this.thinkingToggle) {
|
||||||
|
this.thinkingToggle.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.toggleThinkingDetails();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also make the entire header clickable
|
||||||
|
const thinkingHeader = this.thinkingBubble?.querySelector('.thinking-header');
|
||||||
|
if (thinkingHeader) {
|
||||||
|
thinkingHeader.addEventListener('click', (e) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
if (!target.closest('.thinking-toggle')) {
|
||||||
|
this.toggleThinkingDetails();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleThinkingDetails() {
|
||||||
|
const content = this.thinkingBubble?.querySelector('.thinking-content') as HTMLElement;
|
||||||
|
const toggle = this.thinkingToggle?.querySelector('i');
|
||||||
|
|
||||||
|
if (content && toggle) {
|
||||||
|
const isVisible = content.style.display !== 'none';
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
|
content.style.display = 'none';
|
||||||
|
toggle.className = 'bx bx-chevron-down';
|
||||||
|
this.thinkingToggle.classList.remove('expanded');
|
||||||
|
} else {
|
||||||
|
content.style.display = 'block';
|
||||||
|
toggle.className = 'bx bx-chevron-up';
|
||||||
|
this.thinkingToggle.classList.add('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the thinking display with optional initial content
|
||||||
|
*/
|
||||||
|
private showThinkingDisplay(initialText: string = '') {
|
||||||
|
if (this.thinkingContainer) {
|
||||||
|
this.thinkingContainer.style.display = 'block';
|
||||||
|
|
||||||
|
if (initialText && this.thinkingText) {
|
||||||
|
this.updateThinkingText(initialText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to show the thinking display
|
||||||
|
this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the thinking text content
|
||||||
|
*/
|
||||||
|
private updateThinkingText(text: string) {
|
||||||
|
if (this.thinkingText) {
|
||||||
|
// Format the thinking text for better readability
|
||||||
|
const formattedText = this.formatThinkingText(text);
|
||||||
|
this.thinkingText.textContent = formattedText;
|
||||||
|
|
||||||
|
// Auto-scroll if content is expanded
|
||||||
|
const content = this.thinkingBubble?.querySelector('.thinking-content') as HTMLElement;
|
||||||
|
if (content && content.style.display !== 'none') {
|
||||||
|
this.chatContainer.scrollTop = this.chatContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format thinking text for better presentation
|
||||||
|
*/
|
||||||
|
private formatThinkingText(text: string): string {
|
||||||
|
if (!text) return text;
|
||||||
|
|
||||||
|
// Clean up the text
|
||||||
|
let formatted = text.trim();
|
||||||
|
|
||||||
|
// Add some basic formatting
|
||||||
|
formatted = formatted
|
||||||
|
// Add spacing around section markers
|
||||||
|
.replace(/(\d+\.\s)/g, '\n$1')
|
||||||
|
// Clean up excessive whitespace
|
||||||
|
.replace(/\n\s*\n\s*\n/g, '\n\n')
|
||||||
|
// Trim again
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the thinking display
|
||||||
|
*/
|
||||||
|
private hideThinkingDisplay() {
|
||||||
|
if (this.thinkingContainer) {
|
||||||
|
this.thinkingContainer.style.display = 'none';
|
||||||
|
|
||||||
|
// Reset the toggle state
|
||||||
|
const content = this.thinkingBubble?.querySelector('.thinking-content') as HTMLElement;
|
||||||
|
const toggle = this.thinkingToggle?.querySelector('i');
|
||||||
|
|
||||||
|
if (content && toggle) {
|
||||||
|
content.style.display = 'none';
|
||||||
|
toggle.className = 'bx bx-chevron-down';
|
||||||
|
this.thinkingToggle?.classList.remove('expanded');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the text content
|
||||||
|
if (this.thinkingText) {
|
||||||
|
this.thinkingText.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append to existing thinking content (for streaming updates)
|
||||||
|
*/
|
||||||
|
private appendThinkingText(additionalText: string) {
|
||||||
|
if (this.thinkingText && additionalText) {
|
||||||
|
const currentText = this.thinkingText.textContent || '';
|
||||||
|
const newText = currentText + additionalText;
|
||||||
|
this.updateThinkingText(newText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,11 @@ export interface MessageData {
|
|||||||
role: string;
|
role: string;
|
||||||
content: string;
|
content: string;
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
|
mentions?: Array<{
|
||||||
|
noteId: string;
|
||||||
|
title: string;
|
||||||
|
notePath: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatData {
|
export interface ChatData {
|
||||||
|
|||||||
@ -13,6 +13,27 @@ export const TPL = `
|
|||||||
|
|
||||||
<div class="note-context-chat-container flex-grow-1 overflow-auto p-3">
|
<div class="note-context-chat-container flex-grow-1 overflow-auto p-3">
|
||||||
<div class="note-context-chat-messages"></div>
|
<div class="note-context-chat-messages"></div>
|
||||||
|
|
||||||
|
<!-- Thinking display area -->
|
||||||
|
<div class="llm-thinking-container" style="display: none;">
|
||||||
|
<div class="thinking-bubble">
|
||||||
|
<div class="thinking-header d-flex align-items-center">
|
||||||
|
<div class="thinking-dots">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
<span class="thinking-label ms-2 text-muted small">AI is thinking...</span>
|
||||||
|
<button type="button" class="btn btn-sm btn-link p-0 ms-auto thinking-toggle" title="Toggle thinking details">
|
||||||
|
<i class="bx bx-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="thinking-content" style="display: none;">
|
||||||
|
<div class="thinking-text"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="loading-indicator" style="display: none;">
|
<div class="loading-indicator" style="display: none;">
|
||||||
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
@ -31,11 +52,11 @@ export const TPL = `
|
|||||||
|
|
||||||
<form class="note-context-chat-form d-flex flex-column border-top p-2">
|
<form class="note-context-chat-form d-flex flex-column border-top p-2">
|
||||||
<div class="d-flex chat-input-container mb-2">
|
<div class="d-flex chat-input-container mb-2">
|
||||||
<textarea
|
<div
|
||||||
class="form-control note-context-chat-input"
|
class="form-control note-context-chat-input flex-grow-1"
|
||||||
placeholder="${t('ai_llm.enter_message')}"
|
style="min-height: 60px; max-height: 200px; overflow-y: auto;"
|
||||||
rows="2"
|
data-placeholder="${t('ai_llm.enter_message')}"
|
||||||
></textarea>
|
></div>
|
||||||
<button type="submit" class="btn btn-primary note-context-chat-send-button ms-2 d-flex align-items-center justify-content-center">
|
<button type="submit" class="btn btn-primary note-context-chat-send-button ms-2 d-flex align-items-center justify-content-center">
|
||||||
<i class="bx bx-send"></i>
|
<i class="bx bx-send"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* Utility functions for LLM Chat
|
* Utility functions for LLM Chat
|
||||||
*/
|
*/
|
||||||
import { marked } from "marked";
|
import { marked } from "marked";
|
||||||
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
|
import { formatCodeBlocks } from "../../services/syntax_highlight.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format markdown content for display
|
* Format markdown content for display
|
||||||
@ -62,7 +62,7 @@ export function escapeHtml(text: string): string {
|
|||||||
* Apply syntax highlighting to content
|
* Apply syntax highlighting to content
|
||||||
*/
|
*/
|
||||||
export function applyHighlighting(element: HTMLElement): void {
|
export function applyHighlighting(element: HTMLElement): void {
|
||||||
applySyntaxHighlight($(element));
|
formatCodeBlocks($(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -16,49 +16,53 @@ export async function validateEmbeddingProviders(validationWarning: HTMLElement)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get provider precedence
|
// Get precedence list from options
|
||||||
const precedenceStr = options.get('aiProviderPrecedence') || 'openai,anthropic,ollama';
|
const precedenceStr = options.get('aiProviderPrecedence') || 'openai,anthropic,ollama';
|
||||||
let precedenceList: string[] = [];
|
let precedenceList: string[] = [];
|
||||||
|
|
||||||
if (precedenceStr) {
|
if (precedenceStr) {
|
||||||
if (precedenceStr.startsWith('[') && precedenceStr.endsWith(']')) {
|
if (precedenceStr.startsWith('[') && precedenceStr.endsWith(']')) {
|
||||||
precedenceList = JSON.parse(precedenceStr);
|
try {
|
||||||
|
precedenceList = JSON.parse(precedenceStr);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing precedence list:', e);
|
||||||
|
precedenceList = ['openai']; // Default if parsing fails
|
||||||
|
}
|
||||||
} else if (precedenceStr.includes(',')) {
|
} else if (precedenceStr.includes(',')) {
|
||||||
precedenceList = precedenceStr.split(',').map(p => p.trim());
|
precedenceList = precedenceStr.split(',').map(p => p.trim());
|
||||||
} else {
|
} else {
|
||||||
precedenceList = [precedenceStr];
|
precedenceList = [precedenceStr];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get enabled providers - this is a simplification since we don't have direct DB access
|
// Check for configuration issues with providers in the precedence list
|
||||||
// We'll determine enabled status based on the presence of keys or settings
|
const configIssues: string[] = [];
|
||||||
const enabledProviders: string[] = [];
|
|
||||||
|
// Check each provider in the precedence list for proper configuration
|
||||||
// OpenAI is enabled if API key is set
|
for (const provider of precedenceList) {
|
||||||
const openaiKey = options.get('openaiApiKey');
|
if (provider === 'openai') {
|
||||||
if (openaiKey) {
|
// Check OpenAI configuration
|
||||||
enabledProviders.push('openai');
|
const apiKey = options.get('openaiApiKey');
|
||||||
|
if (!apiKey) {
|
||||||
|
configIssues.push(`OpenAI API key is missing`);
|
||||||
|
}
|
||||||
|
} else if (provider === 'anthropic') {
|
||||||
|
// Check Anthropic configuration
|
||||||
|
const apiKey = options.get('anthropicApiKey');
|
||||||
|
if (!apiKey) {
|
||||||
|
configIssues.push(`Anthropic API key is missing`);
|
||||||
|
}
|
||||||
|
} else if (provider === 'ollama') {
|
||||||
|
// Check Ollama configuration
|
||||||
|
const baseUrl = options.get('ollamaBaseUrl');
|
||||||
|
if (!baseUrl) {
|
||||||
|
configIssues.push(`Ollama Base URL is missing`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add checks for other providers as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anthropic is enabled if API key is set
|
// Fetch embedding stats to check if there are any notes being processed
|
||||||
const anthropicKey = options.get('anthropicApiKey');
|
|
||||||
if (anthropicKey) {
|
|
||||||
enabledProviders.push('anthropic');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ollama is enabled if base URL is set
|
|
||||||
const ollamaBaseUrl = options.get('ollamaBaseUrl');
|
|
||||||
if (ollamaBaseUrl) {
|
|
||||||
enabledProviders.push('ollama');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local is always available
|
|
||||||
enabledProviders.push('local');
|
|
||||||
|
|
||||||
// Perform validation checks
|
|
||||||
const allPrecedenceEnabled = precedenceList.every((p: string) => enabledProviders.includes(p));
|
|
||||||
|
|
||||||
// Get embedding queue status
|
|
||||||
const embeddingStats = await getEmbeddingStats() as {
|
const embeddingStats = await getEmbeddingStats() as {
|
||||||
success: boolean,
|
success: boolean,
|
||||||
stats: {
|
stats: {
|
||||||
@ -73,17 +77,18 @@ export async function validateEmbeddingProviders(validationWarning: HTMLElement)
|
|||||||
const queuedNotes = embeddingStats?.stats?.queuedNotesCount || 0;
|
const queuedNotes = embeddingStats?.stats?.queuedNotesCount || 0;
|
||||||
const hasEmbeddingsInQueue = queuedNotes > 0;
|
const hasEmbeddingsInQueue = queuedNotes > 0;
|
||||||
|
|
||||||
// Show warning if there are issues
|
// Show warning if there are configuration issues or embeddings in queue
|
||||||
if (!allPrecedenceEnabled || hasEmbeddingsInQueue) {
|
if (configIssues.length > 0 || hasEmbeddingsInQueue) {
|
||||||
let message = '<i class="bx bx-error-circle me-2"></i><strong>AI Provider Configuration Issues</strong>';
|
let message = '<i class="bx bx-error-circle me-2"></i><strong>AI Provider Configuration Issues</strong>';
|
||||||
|
|
||||||
message += '<ul class="mb-1 ps-4">';
|
message += '<ul class="mb-1 ps-4">';
|
||||||
|
|
||||||
if (!allPrecedenceEnabled) {
|
// Show configuration issues
|
||||||
const disabledProviders = precedenceList.filter((p: string) => !enabledProviders.includes(p));
|
for (const issue of configIssues) {
|
||||||
message += `<li>The following providers in your precedence list are not enabled: ${disabledProviders.join(', ')}.</li>`;
|
message += `<li>${issue}</li>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show warning about embeddings queue if applicable
|
||||||
if (hasEmbeddingsInQueue) {
|
if (hasEmbeddingsInQueue) {
|
||||||
message += `<li>Currently processing embeddings for ${queuedNotes} notes. Some AI features may produce incomplete results until processing completes.</li>`;
|
message += `<li>Currently processing embeddings for ${queuedNotes} notes. Some AI features may produce incomplete results until processing completes.</li>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,10 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
|||||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||||
import SpacedUpdate from "../services/spaced_update.js";
|
import SpacedUpdate from "../services/spaced_update.js";
|
||||||
import server from "../services/server.js";
|
import server from "../services/server.js";
|
||||||
import libraryLoader from "../services/library_loader.js";
|
|
||||||
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
|
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
|
||||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||||
import noteCreateService from "../services/note_create.js";
|
import noteCreateService from "../services/note_create.js";
|
||||||
import attributeService from "../services/attributes.js";
|
import attributeService from "../services/attributes.js";
|
||||||
import attributeRenderer from "../services/attribute_renderer.js";
|
|
||||||
|
|
||||||
import EmptyTypeWidget from "./type_widgets/empty.js";
|
import EmptyTypeWidget from "./type_widgets/empty.js";
|
||||||
import EditableTextTypeWidget from "./type_widgets/editable_text.js";
|
import EditableTextTypeWidget from "./type_widgets/editable_text.js";
|
||||||
@ -30,7 +28,6 @@ import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
|
|||||||
import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
|
import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
|
||||||
import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
|
import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
|
||||||
import MindMapWidget from "./type_widgets/mind_map.js";
|
import MindMapWidget from "./type_widgets/mind_map.js";
|
||||||
import { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js";
|
|
||||||
import GeoMapTypeWidget from "./type_widgets/geo_map.js";
|
import GeoMapTypeWidget from "./type_widgets/geo_map.js";
|
||||||
import utils from "../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import type { NoteType } from "../entities/fnote.js";
|
import type { NoteType } from "../entities/fnote.js";
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { Dropdown } from "bootstrap";
|
import { Dropdown } from "bootstrap";
|
||||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||||
import { getAvailableLocales, getLocaleById } from "../services/i18n.js";
|
import { getAvailableLocales, getLocaleById, t } from "../services/i18n.js";
|
||||||
import { t } from "i18next";
|
|
||||||
import type { EventData } from "../components/app_context.js";
|
import type { EventData } from "../components/app_context.js";
|
||||||
import type FNote from "../entities/fnote.js";
|
import type FNote from "../entities/fnote.js";
|
||||||
import attributes from "../services/attributes.js";
|
import attributes from "../services/attributes.js";
|
||||||
|
|||||||
@ -27,6 +27,11 @@ import type { AttributeRow, BranchRow } from "../services/load_results.js";
|
|||||||
import type { SetNoteOpts } from "../components/note_context.js";
|
import type { SetNoteOpts } from "../components/note_context.js";
|
||||||
import type { TouchBarItem } from "../components/touch_bar.js";
|
import type { TouchBarItem } from "../components/touch_bar.js";
|
||||||
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
|
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
|
||||||
|
import "jquery.fancytree";
|
||||||
|
import "jquery.fancytree/dist/modules/jquery.fancytree.dnd5.js";
|
||||||
|
import "jquery.fancytree/dist/modules/jquery.fancytree.clones.js";
|
||||||
|
import "jquery.fancytree/dist/modules/jquery.fancytree.filter.js";
|
||||||
|
import "../stylesheets/tree.css";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div class="tree-wrapper">
|
<div class="tree-wrapper">
|
||||||
@ -345,7 +350,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
},
|
},
|
||||||
scrollParent: this.$tree,
|
scrollParent: this.$tree,
|
||||||
minExpandLevel: 2, // root can't be collapsed
|
minExpandLevel: 2, // root can't be collapsed
|
||||||
click: (event: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement>, data): boolean => {
|
click: (event, data): boolean => {
|
||||||
this.activityDetected();
|
this.activityDetected();
|
||||||
|
|
||||||
const targetType = data.targetType;
|
const targetType = data.targetType;
|
||||||
@ -740,7 +745,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
prepareChildren(parentNote: FNote) {
|
prepareChildren(parentNote: FNote) {
|
||||||
utils.assertArguments(parentNote);
|
utils.assertArguments(parentNote);
|
||||||
|
|
||||||
const noteList = [];
|
const noteList: Node[] = [];
|
||||||
|
|
||||||
const hideArchivedNotes = this.hideArchivedNotes;
|
const hideArchivedNotes = this.hideArchivedNotes;
|
||||||
|
|
||||||
@ -834,7 +839,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
getExtraClasses(note: FNote) {
|
getExtraClasses(note: FNote) {
|
||||||
utils.assertArguments(note);
|
utils.assertArguments(note);
|
||||||
|
|
||||||
const extraClasses = [];
|
const extraClasses: string[] = [];
|
||||||
|
|
||||||
if (note.isProtected) {
|
if (note.isProtected) {
|
||||||
extraClasses.push("protected");
|
extraClasses.push("protected");
|
||||||
@ -1167,16 +1172,19 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
let noneCollapsedYet = true;
|
let noneCollapsedYet = true;
|
||||||
|
|
||||||
this.tree.getRootNode().visit((node) => {
|
if (!options.is("databaseReadonly")) {
|
||||||
if (node.isExpanded() && !noteIdsToKeepExpanded.has(node.data.noteId)) {
|
// can't change expanded notes when database is readonly
|
||||||
node.setExpanded(false);
|
this.tree.getRootNode().visit((node) => {
|
||||||
|
if (node.isExpanded() && !noteIdsToKeepExpanded.has(node.data.noteId)) {
|
||||||
|
node.setExpanded(false);
|
||||||
|
|
||||||
if (noneCollapsedYet) {
|
if (noneCollapsedYet) {
|
||||||
toastService.showMessage(t("note_tree.auto-collapsing-notes-after-inactivity"));
|
toastService.showMessage(t("note_tree.auto-collapsing-notes-after-inactivity"));
|
||||||
noneCollapsedYet = false;
|
noneCollapsedYet = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}, false);
|
||||||
}, false);
|
}
|
||||||
|
|
||||||
this.filterHoistedBranch(true);
|
this.filterHoistedBranch(true);
|
||||||
}, 600 * 1000);
|
}, 600 * 1000);
|
||||||
@ -1257,8 +1265,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
const allBranchesDeleted = branchRows.every((branchRow) => !!branchRow.isDeleted);
|
const allBranchesDeleted = branchRows.every((branchRow) => !!branchRow.isDeleted);
|
||||||
|
|
||||||
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
|
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
|
||||||
let movedActiveNode = null;
|
let movedActiveNode: Fancytree.FancytreeNode | null = null;
|
||||||
let parentsOfAddedNodes = [];
|
let parentsOfAddedNodes: Fancytree.FancytreeNode[] = [];
|
||||||
|
|
||||||
for (const branchRow of branchRows) {
|
for (const branchRow of branchRows) {
|
||||||
if (branchRow.noteId) {
|
if (branchRow.noteId) {
|
||||||
|
|||||||
@ -38,7 +38,6 @@ const NOTE_TYPES: NoteTypeMapping[] = [
|
|||||||
// Misc note types
|
// Misc note types
|
||||||
{ type: "render", mime: "", title: t("note_types.render-note"), selectable: true },
|
{ type: "render", mime: "", title: t("note_types.render-note"), selectable: true },
|
||||||
{ type: "webView", mime: "", title: t("note_types.web-view"), selectable: true },
|
{ type: "webView", mime: "", title: t("note_types.web-view"), selectable: true },
|
||||||
{ type: "aiChat", mime: "application/json", title: t("note_types.ai-chat"), selectable: true },
|
|
||||||
|
|
||||||
// Code notes
|
// Code notes
|
||||||
{ type: "code", mime: "text/plain", title: t("note_types.code"), selectable: true },
|
{ type: "code", mime: "text/plain", title: t("note_types.code"), selectable: true },
|
||||||
@ -50,7 +49,8 @@ const NOTE_TYPES: NoteTypeMapping[] = [
|
|||||||
{ type: "image", title: t("note_types.image"), selectable: false },
|
{ type: "image", title: t("note_types.image"), selectable: false },
|
||||||
{ type: "launcher", mime: "", title: t("note_types.launcher"), selectable: false },
|
{ type: "launcher", mime: "", title: t("note_types.launcher"), selectable: false },
|
||||||
{ type: "noteMap", mime: "", title: t("note_types.note-map"), selectable: false },
|
{ type: "noteMap", mime: "", title: t("note_types.note-map"), selectable: false },
|
||||||
{ type: "search", title: t("note_types.saved-search"), selectable: false }
|
{ type: "search", title: t("note_types.saved-search"), selectable: false },
|
||||||
|
{ type: "aiChat", mime: "application/json", title: t("note_types.ai-chat"), selectable: false }
|
||||||
];
|
];
|
||||||
|
|
||||||
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => !nt.selectable).map((nt) => nt.type);
|
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => !nt.selectable).map((nt) => nt.type);
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const TPL = /*html*/`
|
|||||||
|
|
||||||
<div class="no-edited-notes-found">${t("edited_notes.no_edited_notes_found")}</div>
|
<div class="no-edited-notes-found">${t("edited_notes.no_edited_notes_found")}</div>
|
||||||
|
|
||||||
<div class="edited-notes-list"></div>
|
<div class="edited-notes-list use-tn-links"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const TPL = /*html*/`
|
|||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.open-full-button, .collapse-button {
|
.note-map-ribbon-widget .open-full-button, .note-map-ribbon-widget .collapse-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
|||||||
this.$notePathIntro.text(t("note_paths.intro_not_placed"));
|
this.$notePathIntro.text(t("note_paths.intro_not_placed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderedPaths = [];
|
const renderedPaths: JQuery<HTMLElement>[] = [];
|
||||||
|
|
||||||
for (const notePathRecord of sortedNotePaths) {
|
for (const notePathRecord of sortedNotePaths) {
|
||||||
const notePath = notePathRecord.notePath;
|
const notePath = notePathRecord.notePath;
|
||||||
@ -100,7 +100,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
|||||||
const $pathItem = $("<li>");
|
const $pathItem = $("<li>");
|
||||||
const pathSegments: string[] = [];
|
const pathSegments: string[] = [];
|
||||||
const lastIndex = notePath.length - 1;
|
const lastIndex = notePath.length - 1;
|
||||||
|
|
||||||
for (let i = 0; i < notePath.length; i++) {
|
for (let i = 0; i < notePath.length; i++) {
|
||||||
const noteId = notePath[i];
|
const noteId = notePath[i];
|
||||||
pathSegments.push(noteId);
|
pathSegments.push(noteId);
|
||||||
@ -109,13 +109,13 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
$noteLink.find("a").addClass("no-tooltip-preview tn-link");
|
$noteLink.find("a").addClass("no-tooltip-preview tn-link");
|
||||||
$pathItem.append($noteLink);
|
$pathItem.append($noteLink);
|
||||||
|
|
||||||
if (i != lastIndex) {
|
if (i != lastIndex) {
|
||||||
$pathItem.append(" / ");
|
$pathItem.append(" / ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const icons = [];
|
const icons: string[] = [];
|
||||||
|
|
||||||
if (this.notePath === notePath.join("/")) {
|
if (this.notePath === notePath.join("/")) {
|
||||||
$pathItem.addClass("path-current");
|
$pathItem.addClass("path-current");
|
||||||
|
|||||||
@ -122,7 +122,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const $cells = [];
|
const $cells: JQuery<HTMLElement>[] = [];
|
||||||
|
|
||||||
for (const definitionAttr of promotedDefAttrs) {
|
for (const definitionAttr of promotedDefAttrs) {
|
||||||
const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation";
|
const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user