diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 84bea09e2..f06e7b3e9 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -53,7 +53,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Install Playwright Browsers - run: npx playwright install --with-deps + run: pnpx playwright install --with-deps - name: Run the TypeScript build run: pnpm run server:build @@ -62,7 +62,7 @@ jobs: uses: docker/build-push-action@v6 with: context: apps/server - file: ${{ matrix.dockerfile }} + file: apps/server/${{ matrix.dockerfile }} load: true tags: ${{ env.TEST_TAG }} cache-from: type=gha @@ -70,7 +70,7 @@ jobs: - name: Validate container run output run: | - CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }}) + CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./apps/server/spec/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }}) echo "Container ID: $CONTAINER_ID" - name: Wait for the healthchecks to pass @@ -82,7 +82,7 @@ jobs: require-healthy: true - 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 if: ${{ !cancelled() }} with: @@ -129,7 +129,6 @@ jobs: - name: Set TEST_TAG to lowercase run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV - - name: Checkout repository uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -142,6 +141,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Run the TypeScript build + run: pnpm run server:build + - name: Update build info run: pnpm run chore:update-build-info @@ -184,7 +186,7 @@ jobs: uses: docker/build-push-action@v6 with: context: apps/server - file: ${{ matrix.dockerfile }} + file: apps/server/${{ matrix.dockerfile }} platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 47d8e57e3..597f1173d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -33,11 +33,11 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - run: npx playwright install --with-deps + - run: pnpx playwright install --with-deps - uses: nrwl/nx-set-shas@v4 # Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud # - 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 # 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 diff --git a/README.md b/README.md index 9f7fba27a..02a9c969b 100644 --- a/README.md +++ b/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) -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: Trilium Screenshot +## 🎁 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? -[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? @@ -20,7 +52,7 @@ 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. -## Documentation +## 📖 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. @@ -29,55 +61,40 @@ Below are some quick links for your convenience to navigate the documentation: - [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 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) -- [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) - -## 🎁 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. +- [Github Discussions](https://github.com/TriliumNext/Notes/discussions) (For asynchronous discussions.) +- [Github Issues](https://github.com/TriliumNext/Notes/issues) (For bug reports and feature requests.) ## 🏗 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. -* Access TriliumNext via the web interface of a server installation (see below) - * Currently only the latest versions of Chrome & Firefox are supported (and tested). -* TriliumNext is also provided as a Flatpak, but not yet published on FlatHub. +### Linux + +If your distribution is listed in the table below, use your distribution's package. + +[![Packaging status](https://repology.org/badge/vertical-allrepos/trilium-next-desktop.svg)](https://repology.org/project/trilium-next-desktop/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 @@ -91,11 +108,6 @@ 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). -## 📝 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 @@ -150,4 +162,6 @@ Support for the TriliumNext organization will be possible in the near future. Fo ## 🔑 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. diff --git a/_regroup/ckeditor5-build-trilium/ckeditor-content.css b/_regroup/ckeditor5-build-trilium/ckeditor-content.css deleted file mode 100644 index 94d440047..000000000 --- a/_regroup/ckeditor5-build-trilium/ckeditor-content.css +++ /dev/null @@ -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; - } -} diff --git a/_regroup/package.json b/_regroup/package.json index d3680bc02..2db64c988 100644 --- a/_regroup/package.json +++ b/_regroup/package.json @@ -38,10 +38,10 @@ "@playwright/test": "1.52.0", "@stylistic/eslint-plugin": "4.2.0", "@types/express": "5.0.1", - "@types/node": "22.15.17", + "@types/node": "22.15.21", "@types/yargs": "17.0.33", - "@vitest/coverage-v8": "3.1.3", - "eslint": "9.26.0", + "@vitest/coverage-v8": "3.1.4", + "eslint": "9.27.0", "eslint-plugin-simple-import-sort": "12.1.1", "esm": "3.2.25", "jsdoc": "4.0.4", diff --git a/apps/client/package.json b/apps/client/package.json index 8a98e47f1..163f6297d 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -10,7 +10,7 @@ "url": "https://github.com/TriliumNext/Notes" }, "dependencies": { - "@eslint/js": "9.26.0", + "@eslint/js": "9.27.0", "@excalidraw/excalidraw": "0.18.0", "@fullcalendar/core": "6.1.17", "@fullcalendar/daygrid": "6.1.17", @@ -22,28 +22,33 @@ "@mind-elixir/node-menu": "1.0.5", "@popperjs/core": "2.11.8", "@triliumnext/ckeditor5": "workspace:*", - "@triliumnext/commons": "workspace:*", "@triliumnext/codemirror": "workspace:*", + "@triliumnext/commons": "workspace:*", + "@triliumnext/highlightjs": "workspace:*", + "autocomplete.js": "0.38.1", "bootstrap": "5.3.6", + "boxicons": "2.1.4", "dayjs": "1.11.13", "dayjs-plugin-utc": "0.1.2", "debounce": "2.2.0", "draggabilly": "3.0.0", "force-graph": "1.49.6", "globals": "16.1.0", - "i18next": "25.1.2", + "i18next": "25.2.0", "i18next-http-backend": "3.0.2", "jquery": "3.7.1", "jquery-hotkeys": "0.2.2", "jquery.fancytree": "2.38.5", "jsplumb": "2.15.6", + "katex": "0.16.22", "knockout": "3.5.1", "leaflet": "1.9.4", "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", - "marked": "15.0.11", + "marked": "15.0.12", "mermaid": "11.6.0", "mind-elixir": "4.5.2", + "normalize.css": "8.0.1", "panzoom": "9.4.3", "react": "19.1.0", "react-dom": "19.1.0", @@ -55,13 +60,15 @@ "@ckeditor/ckeditor5-inspector": "4.1.0", "@types/bootstrap": "5.2.10", "@types/jquery": "3.5.32", - "@types/leaflet": "1.9.17", + "@types/leaflet": "1.9.18", "@types/leaflet-gpx": "1.3.7", + "@types/mark.js": "8.11.12", "@types/react": "19.1.4", "@types/react-dom": "19.1.5", "copy-webpack-plugin": "13.0.0", "happy-dom": "17.4.7", - "script-loader": "0.7.2" + "script-loader": "0.7.2", + "vite-plugin-static-copy": "3.0.0" }, "nx": { "name": "client" diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 1855876d3..b64678011 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -283,6 +283,9 @@ export type CommandMappings = { type EventMappings = { initialRenderComplete: {}; frocaReloaded: {}; + setLeftPaneVisibility: { + leftPaneVisible: boolean | null; + } protectedSessionStarted: {}; notesReloaded: { noteIds: string[]; diff --git a/apps/client/src/components/root_command_executor.ts b/apps/client/src/components/root_command_executor.ts index 1e16fae81..8e7df9494 100644 --- a/apps/client/src/components/root_command_executor.ts +++ b/apps/client/src/components/root_command_executor.ts @@ -78,15 +78,15 @@ export default class RootCommandExecutor extends Component { } hideLeftPaneCommand() { - options.save(`leftPaneVisible`, "false"); + appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: false }); } showLeftPaneCommand() { - options.save(`leftPaneVisible`, "true"); + appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: true }); } toggleLeftPaneCommand() { - options.toggle("leftPaneVisible"); + appContext.triggerEvent("setLeftPaneVisibility", { leftPaneVisible: null }); } async showBackendLogCommand() { diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 51c69aa70..1a0f7e8a9 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -11,6 +11,9 @@ import options from "./services/options.js"; import type ElectronRemote from "@electron/remote"; import type Electron from "electron"; import "./stylesheets/bootstrap.scss"; +import "boxicons/css/boxicons.min.css"; +import "jquery-hotkeys"; +import "autocomplete.js/index_jquery.js"; await appContext.earlyInit(); diff --git a/apps/client/src/libraries/highlightjs/terraform.js b/apps/client/src/libraries/highlightjs/terraform.js deleted file mode 100644 index 514e727cf..000000000 --- a/apps/client/src/libraries/highlightjs/terraform.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * highlight.js terraform syntax highlighting definition - * - * @see https://github.com/highlightjs/highlight.js - * - * :TODO: - * - * @package: highlightjs-terraform - * @author: Nikos Tsirmirakis - * @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); \ No newline at end of file diff --git a/apps/client/src/mobile.ts b/apps/client/src/mobile.ts index 5d88ec1c4..805ffe276 100644 --- a/apps/client/src/mobile.ts +++ b/apps/client/src/mobile.ts @@ -2,6 +2,8 @@ import appContext from "./components/app_context.js"; import noteAutocompleteService from "./services/note_autocomplete.js"; import glob from "./services/glob.js"; import "./stylesheets/bootstrap.scss"; +import "boxicons/css/boxicons.min.css"; +import "autocomplete.js/index_jquery.js"; glob.setupGlobs(); diff --git a/apps/client/src/runtime.ts b/apps/client/src/runtime.ts new file mode 100644 index 000000000..50c385778 --- /dev/null +++ b/apps/client/src/runtime.ts @@ -0,0 +1,5 @@ +import $ from "jquery"; +(window as any).$ = $; +(window as any).jQuery = $; + +$("body").show(); diff --git a/apps/client/src/services/content_renderer.ts b/apps/client/src/services/content_renderer.ts index 0bf7bca28..0664f6a5c 100644 --- a/apps/client/src/services/content_renderer.ts +++ b/apps/client/src/services/content_renderer.ts @@ -1,7 +1,6 @@ import renderService from "./render.js"; import protectedSessionService from "./protected_session.js"; import protectedSessionHolder from "./protected_session_holder.js"; -import libraryLoader from "./library_loader.js"; import openService from "./open.js"; import froca from "./froca.js"; import utils from "./utils.js"; @@ -12,10 +11,11 @@ import FAttachment from "../entities/fattachment.js"; import imageContextMenuService from "../menus/image_context_menu.js"; import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js"; import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js"; -import { normalizeMimeTypeForCKEditor } from "./mime_type_definitions.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 { renderMathInElement } from "./math.js"; +import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons"; let idCounter = 1; @@ -94,8 +94,6 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery').html(blob.content)); if ($renderedContent.find("span.math-tex").length > 0) { - await libraryLoader.requireLibrary(libraryLoader.KATEX); - renderMathInElement($renderedContent[0], { trust: true }); } diff --git a/apps/client/src/services/doc_renderer.ts b/apps/client/src/services/doc_renderer.ts index c8dac0ac0..4192caf8c 100644 --- a/apps/client/src/services/doc_renderer.ts +++ b/apps/client/src/services/doc_renderer.ts @@ -48,5 +48,6 @@ function getUrl(docNameValue: string, language: string) { // Cannot have spaces in the URL due to how JQuery.load works. 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`; } diff --git a/apps/client/src/services/glob.ts b/apps/client/src/services/glob.ts index a1666b041..75ed6fd55 100644 --- a/apps/client/src/services/glob.ts +++ b/apps/client/src/services/glob.ts @@ -1,7 +1,6 @@ import utils from "./utils.js"; import appContext from "../components/app_context.js"; import server from "./server.js"; -import libraryLoader from "./library_loader.js"; import ws from "./ws.js"; import froca from "./froca.js"; import linkService from "./link.js"; @@ -17,7 +16,6 @@ function setupGlobs() { // required for ESLint plugin and CKEditor window.glob.getActiveContextNote = () => appContext.tabManager.getActiveContextNote(); - window.glob.requireLibrary = libraryLoader.requireLibrary; window.glob.appContext = appContext; // for debugging window.glob.froca = froca; window.glob.treeCache = froca; // compatibility for CKEditor builds for a while @@ -64,7 +62,7 @@ function setupGlobs() { }); for (const appCssNoteId of glob.appCssNoteIds || []) { - libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`, false); + requireCss(`api/notes/download/${appCssNoteId}`, false); } utils.initHelpButtons($(window)); @@ -76,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($('').attr("href", url)); + } +} + export default { setupGlobs }; diff --git a/apps/client/src/services/library_loader.ts b/apps/client/src/services/library_loader.ts deleted file mode 100644 index a5ad0e3fc..000000000 --- a/apps/client/src/services/library_loader.ts +++ /dev/null @@ -1,126 +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 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(); - 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(value: T | (() => T) | Promise) { - 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 = {}; - -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($('').attr("href", url)); - } -} - -let highlightingThemeEl: JQuery | 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 = $(``); - $("head").append(highlightingThemeEl); - } - - const url = getStylesheetUrl(theme); - if (url) { - highlightingThemeEl.attr("href", url); - } -} - -export default { - requireCss, - requireLibrary, - loadHighlightingTheme, - KATEX, - HIGHLIGHT_JS -}; diff --git a/apps/client/src/services/link.ts b/apps/client/src/services/link.ts index a0d464741..3dac7688f 100644 --- a/apps/client/src/services/link.ts +++ b/apps/client/src/services/link.ts @@ -58,6 +58,7 @@ export interface ViewScope { * toc will appear and then close immediately, because getToc(html) function will consume time */ tocPreviousVisible?: boolean; + tocCollapsedHeadings?: Set; } interface CreateLinkOptions { diff --git a/apps/client/src/services/math.ts b/apps/client/src/services/math.ts new file mode 100644 index 000000000..2a5fc45b2 --- /dev/null +++ b/apps/client/src/services/math.ts @@ -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; diff --git a/apps/client/src/services/mime_type_definitions.ts b/apps/client/src/services/mime_type_definitions.ts deleted file mode 100644 index 10c73e113..000000000 --- a/apps/client/src/services/mime_type_definitions.ts +++ /dev/null @@ -1,223 +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: "GDScript (Godot)", mime: "text/x-gdscript" }, - { 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: "Nix", mime: "text/x-nix", highlightJs: "nix" }, - { 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 | 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]; -} diff --git a/apps/client/src/services/mime_types.ts b/apps/client/src/services/mime_types.ts index f0f318141..3b72c531c 100644 --- a/apps/client/src/services/mime_types.ts +++ b/apps/client/src/services/mime_types.ts @@ -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"; -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; function loadMimeTypes() { @@ -45,8 +38,8 @@ export function getHighlightJsNameForMime(mimeType: string) { 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. const normalizedMime = normalizeMimeTypeForCKEditor(mimeType.mime); - if (mimeType.highlightJs) { - mimeToHighlightJsMapping[normalizedMime] = mimeType.highlightJs; + if (mimeType.mdLanguageCode) { + mimeToHighlightJsMapping[normalizedMime] = mimeType.mdLanguageCode; } } } diff --git a/apps/client/src/services/note_tooltip.ts b/apps/client/src/services/note_tooltip.ts index 19ac71801..8ea7ac88f 100644 --- a/apps/client/src/services/note_tooltip.ts +++ b/apps/client/src/services/note_tooltip.ts @@ -8,6 +8,10 @@ import appContext from "../components/app_context.js"; import type FNote from "../entities/fnote.js"; import { t } from "./i18n.js"; +// Track all elements that open tooltips +let openTooltipElements: JQuery[] = []; +let dismissTimer: ReturnType; + function setupGlobalTooltip() { $(document).on("mouseenter", "a", mouseEnterHandler); @@ -23,7 +27,12 @@ function setupGlobalTooltip() { } function dismissAllTooltips() { - $(".note-tooltip").remove(); + clearTimeout(dismissTimer); + openTooltipElements.forEach($el => { + $el.tooltip("dispose"); + $el.removeAttr("aria-describedby"); + }); + openTooltipElements = []; } function setupElementTooltip($el: JQuery) { @@ -86,8 +95,8 @@ async function mouseEnterHandler(this: HTMLElement) { // 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 // we now create tooltip which won't close because it won't receive mouseleave event - if ($(this).filter(":hover").length > 0) { - $(this).tooltip({ + if ($link.filter(":hover").length > 0) { + $link.tooltip({ container: "body", // https://github.com/zadam/trilium/issues/2794 https://github.com/zadam/trilium/issues/2988 // with bottom this flickering happens a bit less @@ -103,7 +112,9 @@ async function mouseEnterHandler(this: HTMLElement) { }); dismissAllTooltips(); - $(this).tooltip("show"); + $link.tooltip("show"); + + openTooltipElements.push($link); // Dismiss the tooltip immediately if a link was clicked inside the tooltip. $(`.${tooltipClass} a`).on("click", (e) => { @@ -115,15 +126,16 @@ async function mouseEnterHandler(this: HTMLElement) { // 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 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 dismissAllTooltips(); } else { - setTimeout(checkTooltip, 1000); + dismissTimer = setTimeout(checkTooltip, 1000); } }; - setTimeout(checkTooltip, 1000); + dismissTimer = setTimeout(checkTooltip, 1000); } } @@ -176,7 +188,25 @@ function renderFootnote($link: JQuery, url: string) { .closest(".footnote-item") // find the parent container 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($('').append($('').append($katex.clone()))); + } + }); + } + + let footnoteContent = $footnoteContent.html(); + footnoteContent = `
${footnoteContent}
` + return footnoteContent || ""; } export default { diff --git a/apps/client/src/services/resizer.ts b/apps/client/src/services/resizer.ts index 1cce0f993..e0dc40995 100644 --- a/apps/client/src/services/resizer.ts +++ b/apps/client/src/services/resizer.ts @@ -3,7 +3,11 @@ import Split from "split.js" export const DEFAULT_GUTTER_SIZE = 5; +let leftPaneWidth: number; +let reservedPx: number; +let layoutOrientation: string; let leftInstance: ReturnType | null; +let rightPaneWidth: number; let rightInstance: ReturnType | null; function setupLeftPaneResizer(leftPaneVisible: boolean) { @@ -14,27 +18,34 @@ function setupLeftPaneResizer(leftPaneVisible: boolean) { $("#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) { - $("#rest-pane").css("width", "100%"); - + $("#rest-pane").css("width", layoutOrientation === "vertical" ? `${100 - reservedWidth}%` : "100%"); return; } - let leftPaneWidth = options.getInt("leftPaneWidth"); + leftPaneWidth = leftPaneWidth ?? (options.getInt("leftPaneWidth") ?? 0); if (!leftPaneWidth || leftPaneWidth < 5) { leftPaneWidth = 5; } + const restPaneWidth = 100 - leftPaneWidth - reservedWidth; if (leftPaneVisible) { // 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, // which would cause the minSize setting to have no effect. requestAnimationFrame(() => { leftInstance = Split(["#left-pane", "#rest-pane"], { - sizes: [leftPaneWidth, 100 - leftPaneWidth], + sizes: [leftPaneWidth, restPaneWidth], gutterSize: DEFAULT_GUTTER_SIZE, 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; } - let rightPaneWidth = options.getInt("rightPaneWidth"); + rightPaneWidth = rightPaneWidth ?? (options.getInt("rightPaneWidth") ?? 0); if (!rightPaneWidth || rightPaneWidth < 5) { rightPaneWidth = 5; } @@ -63,8 +74,11 @@ function setupRightPaneResizer() { rightInstance = Split(["#center-pane", "#right-pane"], { sizes: [100 - rightPaneWidth, rightPaneWidth], gutterSize: DEFAULT_GUTTER_SIZE, - minSize: [ 300, 180 ], - onDragEnd: (sizes) => options.save("rightPaneWidth", Math.round(sizes[1])) + minSize: [300, 180], + onDragEnd: (sizes) => { + rightPaneWidth = Math.round(sizes[1]); + options.save("rightPaneWidth", Math.round(sizes[1])); + } }); } } diff --git a/apps/client/src/services/syntax_highlight.ts b/apps/client/src/services/syntax_highlight.ts index ae2fbde72..7dfb29f30 100644 --- a/apps/client/src/services/syntax_highlight.ts +++ b/apps/client/src/services/syntax_highlight.ts @@ -1,19 +1,8 @@ -import library_loader from "./library_loader.js"; +import { ensureMimeTypes, highlight, highlightAuto, loadTheme, Themes } from "@triliumnext/highlightjs"; import mime_types from "./mime_types.js"; import options from "./options.js"; -export function getStylesheetUrl(theme: string) { - 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; -} +let highlightingLoaded = false; /** * 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. @@ -25,6 +14,8 @@ export async function applySyntaxHighlight($container: JQuery) { return; } + await ensureMimeTypesForHighlighting(); + const codeBlocks = $container.find("pre code"); for (const codeBlock of codeBlocks) { const normalizedMimeType = extractLanguageFromClassList(codeBlock); @@ -43,20 +34,13 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery .dropdown-menu { background-color: inherit; } +::selection { + background-color: var(--selection-background-color); +} + [data-bs-toggle="tooltip"]:not(.button-widget) span { padding-bottom: 0; border-bottom: 1px dotted; @@ -1769,7 +1776,7 @@ body.zen .title-row { height: unset !important; -webkit-app-region: drag; 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 { @@ -2009,11 +2016,11 @@ footer.file-footer button { left: 1em; } -.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; } +.admonition.note { --accent-color: var(--admonition-note-accent-color); } +.admonition.tip { --accent-color: var(--admonition-tip-accent-color); } +.admonition.important { --accent-color: var(--admonition-important-accent-color); } +.admonition.caution { --accent-color: var(--admonition-caution-accent-color); } +.admonition.warning { --accent-color: var(--admonition-warning-accent-color); } .admonition.note::before { content: "\eb21"; } .admonition.tip::before { content: "\ea0d"; } diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index c32a01e84..b49f9142a 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -195,6 +195,8 @@ --scrollbar-background-color: transparent; --scrollbar-border-color: unset; /* Deprecated */ + --selection-background-color: #3399FF70; + --link-color: lightskyblue; --mermaid-theme: dark; diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index 7793d8ece..bdf3ae228 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -194,6 +194,8 @@ --scrollbar-background-color: transparent; --scrollbar-border-color: unset; /* Deprecated */ + --selection-background-color: #3399FF70; + --link-color: blue; --mermaid-theme: default; diff --git a/apps/client/src/stylesheets/theme-next/notes/text.css b/apps/client/src/stylesheets/theme-next/notes/text.css index 22770d27a..769ba5699 100644 --- a/apps/client/src/stylesheets/theme-next/notes/text.css +++ b/apps/client/src/stylesheets/theme-next/notes/text.css @@ -73,7 +73,8 @@ } /* 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; background: transparent; border-radius: 6px !important; @@ -81,12 +82,11 @@ } /* Checked list item */ -:root ul.ck.ck-list button.ck-button.ck-on:not(:hover) { - background: transparent !important; -} :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); color: var(--hover-item-color); } @@ -99,6 +99,57 @@ 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-color-base-border); + background: rgba(0, 0, 0, .025); +} + +.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; +} + +/* 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 button */ @@ -112,12 +163,43 @@ /* Table dropdown */ .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-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-border-radius: 0; } +/* 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 */ :root .ck-link-actions button.ck-button, diff --git a/apps/client/src/stylesheets/theme-next/shell.css b/apps/client/src/stylesheets/theme-next/shell.css index 483d6910d..a77715136 100644 --- a/apps/client/src/stylesheets/theme-next/shell.css +++ b/apps/client/src/stylesheets/theme-next/shell.css @@ -127,10 +127,12 @@ body.layout-horizontal > .horizontal { --launcher-pane-button-gap: var(--launcher-pane-vert-button-gap); width: var(--launcher-pane-size) !important; + min-width: var(--launcher-pane-size) !important; padding-bottom: var(--launcher-pane-button-gap); } #launcher-pane.vertical #launcher-container { + width: var(--launcher-pane-size); height: 100%; overflow-x: hidden; overflow-y: auto; @@ -1636,7 +1638,9 @@ div.find-replace-widget div.find-widget-found-wrapper > span { #right-pane .toc li, #right-pane .highlights-list li { - padding: 2px 8px; + padding-top: 2px; + padding-right: 8px; + padding-bottom: 2px; border-radius: 4px; text-align: unset; transition: diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index bcc0bc269..455723f0e 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -1621,7 +1621,10 @@ "color-scheme": "颜色方案" }, "code_block": { - "word_wrapping": "自动换行" + "word_wrapping": "自动换行", + "theme_none": "无语法高亮", + "theme_group_light": "浅色主题", + "theme_group_dark": "深色主题" }, "classic_editor_toolbar": { "title": "格式化" diff --git a/apps/client/src/translations/de/translation.json b/apps/client/src/translations/de/translation.json index 8ec5097aa..ea17f0575 100644 --- a/apps/client/src/translations/de/translation.json +++ b/apps/client/src/translations/de/translation.json @@ -1573,7 +1573,10 @@ "color-scheme": "Farbschema" }, "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": { "title": "Format" diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 24f104441..683eb42b2 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1827,7 +1827,10 @@ "color-scheme": "Color Scheme" }, "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" }, "classic_editor_toolbar": { "title": "Formatting" diff --git a/apps/client/src/translations/es/translation.json b/apps/client/src/translations/es/translation.json index 46be1d55f..64951c3a6 100644 --- a/apps/client/src/translations/es/translation.json +++ b/apps/client/src/translations/es/translation.json @@ -1589,7 +1589,10 @@ "color-scheme": "Esquema de color" }, "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": { "title": "Formato" diff --git a/apps/client/src/translations/fr/translation.json b/apps/client/src/translations/fr/translation.json index d7597a3ac..74335e936 100644 --- a/apps/client/src/translations/fr/translation.json +++ b/apps/client/src/translations/fr/translation.json @@ -1579,7 +1579,10 @@ "color-scheme": "Jeu de couleurs" }, "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": { "title": "Mise en forme" diff --git a/apps/client/src/translations/pt_br/translation.json b/apps/client/src/translations/pt_br/translation.json index a79d14844..8410e64db 100644 --- a/apps/client/src/translations/pt_br/translation.json +++ b/apps/client/src/translations/pt_br/translation.json @@ -1,5 +1,10 @@ { "revisions": { "delete_button": "" + }, + "code_block": { + "theme_none": "Sem destaque de sintaxe", + "theme_group_light": "Temas claros", + "theme_group_dark": "Temas escuros" } } diff --git a/apps/client/src/translations/ro/translation.json b/apps/client/src/translations/ro/translation.json index efaf270f2..25b073e9e 100644 --- a/apps/client/src/translations/ro/translation.json +++ b/apps/client/src/translations/ro/translation.json @@ -1585,7 +1585,10 @@ "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": { - "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": { "title": "Formatare" diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json index 4360fcdf4..c25504510 100644 --- a/apps/client/src/translations/tw/translation.json +++ b/apps/client/src/translations/tw/translation.json @@ -1519,7 +1519,10 @@ "color-scheme": "顏色方案" }, "code_block": { - "word_wrapping": "自動換行" + "word_wrapping": "自動換行", + "theme_none": "無格式高亮", + "theme_group_light": "淺色主題", + "theme_group_dark": "深色主題" }, "classic_editor_toolbar": { "title": "格式化" diff --git a/apps/client/src/types-assets.d.ts b/apps/client/src/types-assets.d.ts index 61242b04e..ffe5456fd 100644 --- a/apps/client/src/types-assets.d.ts +++ b/apps/client/src/types-assets.d.ts @@ -3,9 +3,7 @@ declare module "*.png" { export default path; } -declare module "*.json?external" { +declare module "@triliumnext/ckeditor5/emoji_definitions/en.json?url" { var path: string; export default path; } - -declare module "script-loader!mark.js/dist/jquery.mark.min.js"; diff --git a/apps/client/src/types-lib.d.ts b/apps/client/src/types-lib.d.ts index 57b810f76..a19bffa9d 100644 --- a/apps/client/src/types-lib.d.ts +++ b/apps/client/src/types-lib.d.ts @@ -24,3 +24,10 @@ declare module "draggabilly" { declare module "@mind-elixir/node-menu" { export default mindmap; } + +declare module "katex/contrib/auto-render" { + var renderMathInElement: (element: HTMLElement, options: { + trust: boolean; + }) => void; + export default renderMathInElement; +} diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index 70e51aede..9970b99d3 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -22,7 +22,6 @@ interface CustomGlobals { getReferenceLinkTitle: (href: string) => Promise; getReferenceLinkTitleSync: (href: string) => string; getActiveContextNote: () => FNote | null; - requireLibrary: typeof library_loader.requireLibrary; ESLINT: Library; appContext: AppContext; froca: Froca; @@ -123,24 +122,6 @@ declare global { var require: RequireMethod; 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; - - var katex: { - renderToString(text: string, opts: { - throwOnError: boolean - }); - } - /* * Panzoom */ diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 1f3da0096..132632ceb 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -198,6 +198,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem ], selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command) }); + // Prevent automatic hiding of the context menu due to the button being clicked. + e.stopPropagation(); } // triggered from keyboard shortcut diff --git a/apps/client/src/widgets/buttons/global_menu.ts b/apps/client/src/widgets/buttons/global_menu.ts index d21e43b0d..47323e271 100644 --- a/apps/client/src/widgets/buttons/global_menu.ts +++ b/apps/client/src/widgets/buttons/global_menu.ts @@ -53,10 +53,6 @@ const TPL = /*html*/` pointer-events: none; } - .update-to-latest-version-button { - display: none; - } - .global-menu .zoom-container { display: flex; flex-direction: row; @@ -235,7 +231,7 @@ const TPL = /*html*/` ${t("global_menu.about")} -