diff --git a/.github/actions/build-server/action.yml b/.github/actions/build-server/action.yml index cf8057c70..7423b8c4a 100644 --- a/.github/actions/build-server/action.yml +++ b/.github/actions/build-server/action.yml @@ -1,4 +1,7 @@ inputs: + os: + description: "One of the supported platforms: windows" + required: true arch: description: "The architecture to build for: x64, arm64" required: true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b19145ca1..d7e80cfed 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: extension: [deb, rpm, zip, flatpak] - name: windows image: windows-latest - extension: exe + extension: [exe, zip] runs-on: ${{ matrix.os.image }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f7cee700a..c339fd37d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -26,7 +26,7 @@ jobs: extension: [deb, rpm, zip, flatpak] - name: windows image: windows-latest - extension: exe + extension: [exe, zip] runs-on: ${{ matrix.os.image }} steps: - uses: actions/checkout@v4 @@ -75,6 +75,7 @@ jobs: - name: Run the build uses: ./.github/actions/build-server with: + os: linux arch: ${{ matrix.arch }} - name: Publish release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b7625a706..e6c5c7c2e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,6 +65,7 @@ jobs: - name: Run the build uses: ./.github/actions/build-server with: + os: linux arch: ${{ matrix.arch }} - name: Publish release diff --git a/package-lock.json b/package-lock.json index 842410e2a..db09f9241 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.91.6", + "version": "0.92.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.91.6", + "version": "0.92.0-beta", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "7.1.1", diff --git a/package.json b/package.json index d48cdaf6e..2503ee043 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "TriliumNext Notes", "description": "Build your personal knowledge base with TriliumNext Notes", - "version": "0.91.6", + "version": "0.92.0-beta", "license": "AGPL-3.0-only", "main": "./dist/electron-main.js", "author": { @@ -39,15 +39,15 @@ "electron:switch": "electron-rebuild", "electron-forge:start": "npm run build:prepare-dist && electron-forge start", - "electron-forge:make": "npm run build:webpack && npm run build:prepare-dist && electron-forge make", - "electron-forge:package": "electron-forge package", + "electron-forge:make": "npm run build:prepare-dist && electron-forge make", + "electron-forge:package": "npm run build:prepare-dist && electron-forge package", "docs:build-backend": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts", "docs:build-frontend": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js", "docs:build": "npm run docs:build-backend && npm run docs:build-frontend", "build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts", - "build:prepare-dist": "rimraf ./dist && tsc && tsx ./bin/copy-dist.ts", + "build:prepare-dist": "npm run build:webpack && rimraf ./dist && tsc && tsx ./bin/copy-dist.ts", "test": "cross-env TRILIUM_DATA_DIR=./integration-tests/db vitest", "test:coverage": "cross-env TRILIUM_DATA_DIR=./integration-tests/db vitest --coverage", diff --git a/src/public/app/desktop.ts b/src/public/app/desktop.ts index b78074126..b546bf68c 100644 --- a/src/public/app/desktop.ts +++ b/src/public/app/desktop.ts @@ -50,6 +50,7 @@ function initOnElectron() { const currentWindow = electronRemote.getCurrentWindow(); const style = window.getComputedStyle(document.body); + initDarkOrLightMode(style); initTransparencyEffects(style, currentWindow); if (options.get("nativeTitleBarVisible") !== "true") { @@ -91,3 +92,21 @@ function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Elec } } } + +/** + * Informs Electron that we prefer a dark or light theme. Apart from changing prefers-color-scheme at CSS level which is a side effect, + * this fixes color issues with background effects or native title bars. + * + * @param style the root CSS element to read variables from. + */ +function initDarkOrLightMode(style: CSSStyleDeclaration) { + let themeSource: typeof nativeTheme.themeSource = "system"; + + const themeStyle = style.getPropertyValue("--theme-style"); + if (style.getPropertyValue("--theme-style-auto") !== "true" && (themeStyle === "light" || themeStyle === "dark")) { + themeSource = themeStyle; + } + + const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote; + nativeTheme.themeSource = themeSource; +} diff --git a/src/public/app/doc_notes/en/User Guide/!!!meta.json b/src/public/app/doc_notes/en/User Guide/!!!meta.json index b90fd4f50..4fb15df7f 100644 --- a/src/public/app/doc_notes/en/User Guide/!!!meta.json +++ b/src/public/app/doc_notes/en/User Guide/!!!meta.json @@ -1,6 +1,6 @@ { "formatVersion": 2, - "appVersion": "0.91.6-test-250214-024424", + "appVersion": "0.91.6-test-250217-024840", "files": [ { "isClone": false, @@ -11,7 +11,7 @@ "title": "User Guide", "notePosition": 20, "prefix": null, - "isExpanded": false, + "isExpanded": true, "type": "text", "mime": "text/html", "attributes": [ @@ -27,135 +27,6 @@ "attachments": [], "dirFileName": "User Guide", "children": [ - { - "isClone": false, - "noteId": "jrai60LsOhdk", - "notePath": [ - "OkOZllzB3fqN", - "jrai60LsOhdk" - ], - "title": "Installation", - "notePosition": 20, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "relation", - "name": "internalLink", - "value": "KPeRqBU7YSAY", - "isInheritable": false, - "position": 10 - }, - { - "type": "label", - "name": "hideChildrenOverview", - "value": "", - "isInheritable": false, - "position": 10 - } - ], - "format": "html", - "dataFileName": "Installation.html", - "attachments": [ - { - "attachmentId": "Mp9RaDeLtETz", - "title": "Fedora_logo.svg", - "role": "image", - "mime": "image/svg+xml", - "position": 10, - "dataFileName": "Installation_Fedora_logo.svg" - } - ], - "dirFileName": "Installation", - "children": [ - { - "isClone": false, - "noteId": "KPeRqBU7YSAY", - "notePath": [ - "OkOZllzB3fqN", - "jrai60LsOhdk", - "KPeRqBU7YSAY" - ], - "title": "On Fedora Linux", - "notePosition": 10, - "prefix": null, - "isExpanded": false, - "type": "text", - "mime": "text/html", - "attributes": [ - { - "type": "label", - "name": "iconClass", - "value": "bx bxl-tux", - "isInheritable": false, - "position": 10 - } - ], - "format": "html", - "dataFileName": "On Fedora Linux.html", - "attachments": [ - { - "attachmentId": "YHD8kyEhgkyZ", - "title": "Screenshot From 2025-02-05 19-30-50.png", - "role": "image", - "mime": "image/png", - "position": 10, - "dataFileName": "On Fedora Linux_Screenshot.png" - }, - { - "attachmentId": "0CpZ5v5xUMia", - "title": "Screenshot From 2025-02-05 19-35-45.png", - "role": "image", - "mime": "image/png", - "position": 10, - "dataFileName": "1_On Fedora Linux_Screenshot.png" - }, - { - "attachmentId": "9u7nBYvUbXJW", - "title": "image.png", - "role": "image", - "mime": "image/png", - "position": 10, - "dataFileName": "On Fedora Linux_image.png" - }, - { - "attachmentId": "ipGBq0moRvF3", - "title": "Screenshot From 2025-02-05 19-36-27.png", - "role": "image", - "mime": "image/png", - "position": 10, - "dataFileName": "2_On Fedora Linux_Screenshot.png" - }, - { - "attachmentId": "fa83WbDUIB4G", - "title": "image.png", - "role": "image", - "mime": "image/png", - "position": 10, - "dataFileName": "1_On Fedora Linux_image.png" - }, - { - "attachmentId": "kcCWr0YXytOU", - "title": "Screenshot From 2025-02-05 19-30-30.png", - "role": "image", - "mime": "image/png", - "position": 10, - "dataFileName": "3_On Fedora Linux_Screenshot.png" - }, - { - "attachmentId": "YF3JZy1qz7Fq", - "title": "image.png", - "role": "image", - "mime": "image/png", - "position": 10, - "dataFileName": "2_On Fedora Linux_image.png" - } - ] - } - ] - }, { "isClone": false, "noteId": "yoAe4jV2yzbd", @@ -166,7 +37,7 @@ "title": "Features", "notePosition": 40, "prefix": null, - "isExpanded": true, + "isExpanded": false, "type": "text", "mime": "text/html", "attributes": [], @@ -314,7 +185,7 @@ "title": "Note Types", "notePosition": 70, "prefix": null, - "isExpanded": true, + "isExpanded": false, "type": "text", "mime": "text/html", "attributes": [], @@ -548,7 +419,7 @@ "title": "Book", "notePosition": 30, "prefix": null, - "isExpanded": true, + "isExpanded": false, "type": "text", "mime": "text/html", "attributes": [ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Advanced usage/Theme development/Reference.html b/src/public/app/doc_notes/en/User Guide/User Guide/Advanced usage/Theme development/Reference.html index e858513c1..59c2320c4 100644 --- a/src/public/app/doc_notes/en/User Guide/User Guide/Advanced usage/Theme development/Reference.html +++ b/src/public/app/doc_notes/en/User Guide/User Guide/Advanced usage/Theme development/Reference.html @@ -13,6 +13,37 @@

Reference

+

Detecting mobile vs. desktop

+

The mobile layout is different than the one on the desktop. Use body.mobile and body.desktop to + differentiate between them.

body.mobile #root-widget {
+	/* Do something on mobile */
+}
+
+body.desktop #root-widget {
+	/* Do something on desktop */
+}
+

Do note that there is also a “tablet mode” in the mobile layout. For that + particular case media queries are required:

@media (max-width: 991px) {
+
+    #launcher-pane {
+
+        /* Do something on mobile layout */
+
+    }
+
+}
+
+
+
+@media (min-width: 992px) {
+
+    #launcher-pane {
+
+        /* Do something on mobile tablet + desktop layout */
+
+    }
+
+}

Detecting horizontal vs. vertical layout

The user can select between vertical layout (the classical one, where the launcher bar is on the left) and a horizontal layout (where the launcher @@ -85,14 +116,14 @@ body.electron:not(.native-titlebar) {

On macOS

On macOS the semaphore window buttons are enabled by default when the native title bar is disabled. The offset of the buttons can be adjusted - using:

body {
+          using:

body {
     --native-titlebar-darwin-x-offset: 12;
     --native-titlebar-darwin-y-offset: 14 !important;
 }

Background/transparency effects on Windows (Mica)

Windows 11 offers a special background/transparency effect called Mica, which can be enabled by themes by setting the --background-material variable - at body level:

body.electron.platform-win32 {
+          at body level:

body.electron.platform-win32 {
 	--background-material: tabbed; 
 }

The value can be either tabbed (especially useful for the horizontal @@ -104,12 +135,12 @@ body.electron:not(.native-titlebar) {

Theme capabilities are small adjustments done through CSS variables that can affect the layout or the visual aspect of the application.

In the tab bar, to display the icons of notes instead of the icon of the - workspace:

:root {
+          workspace:

:root {
 	--tab-note-icons: true;
 }

When a workspace is hoisted for a given tab, it is possible to get the background color of that workspace, for example to apply a small strip - on the tab instead of the whole background color:

.note-tab .note-tab-wrapper {
+          on the tab instead of the whole background color:

.note-tab .note-tab-wrapper {
     --tab-background-color: initial !important;
 }
 
@@ -127,6 +158,21 @@ body.electron:not(.native-titlebar) {
           href="../Custom%20resource%20providers.html">Custom resource providers.
           Basically import a font into Trilium and assign it #customResourceProvider=fonts/myfont.ttf and
           then import the font in CSS via /custom/fonts/myfont.ttf.

+

Dark and light themes

+

A light theme needs to have the following CSS:

:root {
+	--theme-style: light;
+}
+

if the theme is dark, then --theme-style needs to be dark.

+

If the theme is auto (e.g. supports both light or dark based on prefers-color-scheme) + it must also declare (in addition to setting --theme-style to + either light or dark):

:root {
+
+    --theme-style-auto: true;
+
+}
+

This will affect the behavior of the Electron application by informing + the operating system of the color preference (e.g. background effects will + appear correct on Windows).

diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation.html b/src/public/app/doc_notes/en/User Guide/User Guide/Installation.html deleted file mode 100644 index 058f84feb..000000000 --- a/src/public/app/doc_notes/en/User Guide/User Guide/Installation.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - Installation - - - -
-

Installation

- -
-

Desktop application

-
- - - - - - -
-
- -
-

Fedora -

-
-
-

Self-hosted server

-
-
- - - \ No newline at end of file diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/1_On Fedora Linux_Screenshot.png b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/1_On Fedora Linux_Screenshot.png deleted file mode 100644 index d519f9b75..000000000 Binary files a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/1_On Fedora Linux_Screenshot.png and /dev/null differ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/1_On Fedora Linux_image.png b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/1_On Fedora Linux_image.png deleted file mode 100644 index f3fcbfcb5..000000000 Binary files a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/1_On Fedora Linux_image.png and /dev/null differ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/2_On Fedora Linux_Screenshot.png b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/2_On Fedora Linux_Screenshot.png deleted file mode 100644 index d7370a422..000000000 Binary files a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/2_On Fedora Linux_Screenshot.png and /dev/null differ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/2_On Fedora Linux_image.png b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/2_On Fedora Linux_image.png deleted file mode 100644 index b69b71d3c..000000000 Binary files a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/2_On Fedora Linux_image.png and /dev/null differ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/3_On Fedora Linux_Screenshot.png b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/3_On Fedora Linux_Screenshot.png deleted file mode 100644 index 6c3589ca4..000000000 Binary files a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/3_On Fedora Linux_Screenshot.png and /dev/null differ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux.html b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux.html deleted file mode 100644 index 88ad36272..000000000 --- a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - On Fedora Linux - - - -
-

On Fedora Linux

- -
-

First, download a release from GitHub.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 -
- -
-
In your file explorer, look for the .rpm file of TriliumNext.
2 -
- -
-
Right click the file and select Open With Software Install.
3 -
- -
-
-

GNOME Software will appear. Press the - .

-

You will be asked to confirm the action by entering your password.

-

After confirmation the software will start installing.

-

Once it's done the “Install” button will turn into - .

-
4 -
- -
-
After installation, the application will be available in the GNOME overview - section.
-
-
-
- - - \ No newline at end of file diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux_Screenshot.png b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux_Screenshot.png deleted file mode 100644 index fb0ce66ef..000000000 Binary files a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux_Screenshot.png and /dev/null differ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux_image.png b/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux_image.png deleted file mode 100644 index 5791f83ef..000000000 Binary files a/src/public/app/doc_notes/en/User Guide/User Guide/Installation/On Fedora Linux_image.png and /dev/null differ diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Installation_Fedora_logo.svg b/src/public/app/doc_notes/en/User Guide/User Guide/Installation_Fedora_logo.svg deleted file mode 100644 index 1ad9c15c8..000000000 --- a/src/public/app/doc_notes/en/User Guide/User Guide/Installation_Fedora_logo.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Note Types/Book/Calendar View.html b/src/public/app/doc_notes/en/User Guide/User Guide/Note Types/Book/Calendar View.html index d5364a1e9..c01fe823f 100644 --- a/src/public/app/doc_notes/en/User Guide/User Guide/Note Types/Book/Calendar View.html +++ b/src/public/app/doc_notes/en/User Guide/User Guide/Note Types/Book/Calendar View.html @@ -74,6 +74,8 @@
+
  • Creating new notes from the calendar will respect the ~child:template relation + if set on the book note.
  • Interacting with events

      diff --git a/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Examples/Downloading responses from Goo.html b/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Examples/Downloading responses from Goo.html index 7e4955655..fdffa270d 100644 --- a/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Examples/Downloading responses from Goo.html +++ b/src/public/app/doc_notes/en/User Guide/User Guide/Scripting/Examples/Downloading responses from Goo.html @@ -12,7 +12,53 @@

      Downloading responses from Google Forms

      -
      +
      +

      This tutorials showcases a basic integration with Google Forms, where + we are able to download the responses of a form using the “Link to Sheets" + functionality.

      +

      Note that the link will be publicly accessible to everyone (however the + link is in a hard-to-guess format such as https://docs.google.com/spreadsheets/d/e/2PACX-1vTA8NU2_eZFhc8TFadCZPreBfvP7un8IHd6J0SchrLLw3ueGmntNZjwRmsH2ZRcp1pJYDAzMz1FmFaj/pub?output=csv). + Make sure you are not accidentally publishing sensitive information.

      +

      Obtaining the CSV link

      +
        +
      1. Open the Google Forms in a browser.
      2. +
      3. Select the “Responses” tab and click on “Link to Sheets”.
      4. +
      5. Select “Create a new spreadsheet” and press “Create”.
      6. +
      7. In Google Sheets, select File → Share → Publish to web.
      8. +
      9. In the “Publish to the web” screen, make sure the “Link” tab is selected + and instead of “Web page”, select “Comma-separated values (.csv)”.
      10. +
      11. Copy the given link which will be used for the upcoming script.
      12. +
      +

      Creating the script

      +

      Create a “JS Frontend” script:

      const CSV_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vTiwooLV2whjCSVa49dJ99p_G3_qhqHHRqttMjYCJVfLXVdTgUSNJu5K0rpqmaHYF2k7Vofi3o7gW82/pub?output=csv";
      +
      +
      +async function fetchData() {
      +
      +    try {
      +
      +        const response = await fetch(CSV_URL);
      +
      +        return await response.text();
      +
      +    } catch (e) {
      +
      +        api.showError(e.message);
      +
      +    }
      +
      +}
      +
      +const data = await fetchData();
      +console.log(data);
      +// Do something with the data.
      +

      Note that the data will be received as a string and there is no library + to do the CSV parsing for us. To do a very simple parsing of CSV:

      const content = data
      +	.split("\n")
      +	.slice(1)
      +	.map((row) => row.split(","));
      +

      This will return the data as an array of arrays.

      +
      diff --git a/src/public/app/doc_notes/en/User Guide/index.html b/src/public/app/doc_notes/en/User Guide/index.html index fc49dc742..c5c17da96 100644 --- a/src/public/app/doc_notes/en/User Guide/index.html +++ b/src/public/app/doc_notes/en/User Guide/index.html @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/src/public/app/doc_notes/en/User Guide/navigation.html b/src/public/app/doc_notes/en/User Guide/navigation.html index 85143e944..732ee6d61 100644 --- a/src/public/app/doc_notes/en/User Guide/navigation.html +++ b/src/public/app/doc_notes/en/User Guide/navigation.html @@ -9,12 +9,6 @@
      • User Guide
          -
        • Installation - -
        • Features
          • Export as PDF diff --git a/src/public/app/widgets/note_type.js b/src/public/app/widgets/note_type.ts similarity index 84% rename from src/public/app/widgets/note_type.js rename to src/public/app/widgets/note_type.ts index 26a42bdb7..9b3b90665 100644 --- a/src/public/app/widgets/note_type.js +++ b/src/public/app/widgets/note_type.ts @@ -3,8 +3,19 @@ import mimeTypesService from "../services/mime_types.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; import dialogService from "../services/dialog.js"; import { t } from "../services/i18n.js"; +import type FNote from "../entities/fnote.js"; +import type { NoteType } from "../entities/fnote.js"; +import type { EventData } from "../components/app_context.js"; -const NOTE_TYPES = [ +interface NoteTypeMapping { + type: NoteType; + mime?: string; + title: string; + isBeta?: boolean; + selectable: boolean; +} + +const NOTE_TYPES: NoteTypeMapping[] = [ // The suggested note type ordering method: insert the item into the corresponding group, // then ensure the items within the group are ordered alphabetically. @@ -67,9 +78,16 @@ const TPL = ` `; export default class NoteTypeWidget extends NoteContextAwareWidget { + + private dropdown!: bootstrap.Dropdown; + private $noteTypeDropdown!: JQuery; + private $noteTypeButton!: JQuery; + private $noteTypeDesc!: JQuery; + doRender() { this.$widget = $(TPL); + //@ts-ignore this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); this.$widget.on("show.bs.dropdown", () => this.renderDropdown()); @@ -81,7 +99,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { this.$widget.on("click", ".dropdown-item", () => this.dropdown.toggle()); } - async refreshWithNote(note) { + async refreshWithNote(note: FNote) { this.$noteTypeButton.prop("disabled", () => NOT_SELECTABLE_NOTE_TYPES.includes(note.type)); this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime)); @@ -93,8 +111,12 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { async renderDropdown() { this.$noteTypeDropdown.empty(); + if (!this.note) { + return; + } + for (const noteType of NOTE_TYPES.filter((nt) => nt.selectable)) { - let $typeLink; + let $typeLink: JQuery; const $title = $("").text(noteType.title); if (noteType.isBeta) { @@ -110,7 +132,9 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { const type = $typeLink.attr("data-note-type"); const noteType = NOTE_TYPES.find((nt) => nt.type === type); - this.save(noteType.type, noteType.mime); + if (noteType) { + this.save(noteType.type, noteType.mime); + } }); } else { this.$noteTypeDropdown.append(''); @@ -136,7 +160,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { .on("click", (e) => { const $link = $(e.target).closest(".dropdown-item"); - this.save("code", $link.attr("data-mime-type")); + this.save("code", $link.attr("data-mime-type") ?? ""); }); if (this.note.type === "code" && this.note.mime === mimeType.mime) { @@ -149,7 +173,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { } } - async findTypeTitle(type, mime) { + async findTypeTitle(type: NoteType, mime: string) { if (type === "code") { const mimeTypes = mimeTypesService.getMimeTypes(); const found = mimeTypes.find((mt) => mt.mime === mime); @@ -162,12 +186,12 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { } } - async save(type, mime) { - if (type === this.note.type && mime === this.note.mime) { + async save(type: NoteType, mime?: string) { + if (type === this.note?.type && mime === this.note?.mime) { return; } - if (type !== this.note.type && !(await this.confirmChangeIfContent())) { + if (type !== this.note?.type && !(await this.confirmChangeIfContent())) { return; } @@ -175,16 +199,20 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { } async confirmChangeIfContent() { + if (!this.note) { + return; + } + const blob = await this.note.getBlob(); - if (!blob.content || !blob.content.trim().length) { + if (!blob?.content || !blob.content.trim().length) { return true; } return await dialogService.confirm(t("note_types.confirm-change")); } - async entitiesReloadedEvent({ loadResults }) { + async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { if (loadResults.isNoteReloaded(this.noteId)) { this.refresh(); } diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 11de0b2b3..1c1a72e35 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -135,10 +135,22 @@ export default class GeoMapTypeWidget extends TypeWidget { throw new Error(t("geo-map.unable-to-load-map")); } - if (!this.note) { + this.#restoreViewportAndZoom(); + + // Restore markers. + await this.#reloadMarkers(); + + const updateFn = () => this.spacedUpdate.scheduleUpdate(); + map.on("moveend", updateFn); + map.on("zoomend", updateFn); + map.on("click", (e) => this.#onMapClicked(e)); + } + + async #restoreViewportAndZoom() { + const map = this.geoMapWidget.map; + if (!map || !this.note) { return; } - const blob = await this.note.getBlob(); let parsedContent: MapData = {}; @@ -150,14 +162,6 @@ export default class GeoMapTypeWidget extends TypeWidget { const center = parsedContent.view?.center ?? DEFAULT_COORDINATES; const zoom = parsedContent.view?.zoom ?? DEFAULT_ZOOM; map.setView(center, zoom); - - // Restore markers. - await this.#reloadMarkers(); - - const updateFn = () => this.spacedUpdate.scheduleUpdate(); - map.on("moveend", updateFn); - map.on("zoomend", updateFn); - map.on("click", (e) => this.#onMapClicked(e)); } async #reloadMarkers() { @@ -343,6 +347,7 @@ export default class GeoMapTypeWidget extends TypeWidget { async doRefresh(note: FNote) { await this.geoMapWidget.refresh(); + this.#restoreViewportAndZoom(); await this.#reloadMarkers(); } diff --git a/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.ts b/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.ts index 48ce0cb0d..7af216cd0 100644 --- a/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.ts +++ b/src/public/app/widgets/type_widgets/options/other/attachment_erasure_timeout.ts @@ -2,33 +2,37 @@ import OptionsWidget from "../options_widget.js"; import server from "../../../../services/server.js"; import toastService from "../../../../services/toast.js"; import { t } from "../../../../services/i18n.js"; -import type { OptionMap } from "../../../../../../services/options_interface.js"; +import TimeSelector from "../time_selector.js"; const TPL = `

            ${t("attachment_erasure_timeout.attachment_erasure_timeout")}

            ${t("attachment_erasure_timeout.attachment_auto_deletion_description")}

            - -
            - - -
            - +

            ${t("attachment_erasure_timeout.manual_erasing_description")}

            `; -export default class AttachmentErasureTimeoutOptions extends OptionsWidget { - - private $eraseUnusedAttachmentsAfterTimeInSeconds!: JQuery; +export default class AttachmentErasureTimeoutOptions extends TimeSelector { private $eraseUnusedAttachmentsNowButton!: JQuery; + constructor() { + super({ + widgetId: "erase-unused-attachments-after", + widgetLabelId: "attachment_erasure_timeout.erase_attachments_after", + optionValueId: "eraseUnusedAttachmentsAfterSeconds", + optionTimeScaleId: "eraseUnusedAttachmentsAfterTimeScale" + }); + super.doRender(); + } + doRender() { + const $timeSelector = this.$widget; this.$widget = $(TPL); - this.$eraseUnusedAttachmentsAfterTimeInSeconds = this.$widget.find(".erase-unused-attachments-after-time-in-seconds"); - this.$eraseUnusedAttachmentsAfterTimeInSeconds.on("change", () => this.updateOption("eraseUnusedAttachmentsAfterSeconds", this.$eraseUnusedAttachmentsAfterTimeInSeconds.val())); + // inject TimeSelector widget template + this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector); this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button"); this.$eraseUnusedAttachmentsNowButton.on("click", () => { @@ -37,8 +41,4 @@ export default class AttachmentErasureTimeoutOptions extends OptionsWidget { }); }); } - - async optionsLoaded(options: OptionMap) { - this.$eraseUnusedAttachmentsAfterTimeInSeconds.val(options.eraseUnusedAttachmentsAfterSeconds); - } } diff --git a/src/public/app/widgets/type_widgets/options/other/note_erasure_timeout.ts b/src/public/app/widgets/type_widgets/options/other/note_erasure_timeout.ts index 99161f9a9..b15e56074 100644 --- a/src/public/app/widgets/type_widgets/options/other/note_erasure_timeout.ts +++ b/src/public/app/widgets/type_widgets/options/other/note_erasure_timeout.ts @@ -3,118 +3,42 @@ import server from "../../../../services/server.js"; import toastService from "../../../../services/toast.js"; import { t } from "../../../../services/i18n.js"; import type { OptionMap } from "../../../../../../services/options_interface.js"; +import TimeSelector from "../time_selector.js"; const TPL = `

            ${t("note_erasure_timeout.note_erasure_timeout_title")}

            -

            ${t("note_erasure_timeout.note_erasure_description")}

            - -
            - -
            - - - -
            -
            - +

            ${t("note_erasure_timeout.manual_erasing_description")}

            - - -
            `; -export default class NoteErasureTimeoutOptions extends OptionsWidget { - - private $eraseEntitiesAfterTime!: JQuery; - private $eraseEntitiesAfterTimeScale!: JQuery; +export default class NoteErasureTimeoutOptions extends TimeSelector { private $eraseDeletedNotesButton!: JQuery; - private eraseEntitiesAfterTimeInSeconds!: string | number; + + constructor() { + super({ + widgetId: "erase-entities-after", + widgetLabelId: "note_erasure_timeout.erase_notes_after", + optionValueId: "eraseEntitiesAfterTimeInSeconds", + optionTimeScaleId: "eraseEntitiesAfterTimeScale" + }); + super.doRender(); + } doRender() { + const $timeSelector = this.$widget; + // inject TimeSelector widget template this.$widget = $(TPL); - this.$eraseEntitiesAfterTime = this.$widget.find("#erase-entities-after-time"); - this.$eraseEntitiesAfterTimeScale = this.$widget.find("#erase-entities-after-time-scale"); - - this.$eraseEntitiesAfterTime.on("change", () => { - const time = this.$eraseEntitiesAfterTime.val(); - const timeScale = this.$eraseEntitiesAfterTimeScale.val(); - - if (!this.handleTimeValidation() || typeof timeScale !== "string" || !time) return; - - this.eraseEntitiesAfterTimeInSeconds = this.convertTime(time, timeScale).toOption(); - this.updateOption("eraseEntitiesAfterTimeInSeconds", this.eraseEntitiesAfterTimeInSeconds); - - }); - - this.$eraseEntitiesAfterTimeScale.on("change", () => { - - const timeScale = this.$eraseEntitiesAfterTimeScale.val(); - - if (!this.handleTimeValidation() || typeof timeScale !== "string") return; - - //calculate the new displayed value - const displayedTime = this.convertTime(this.eraseEntitiesAfterTimeInSeconds, timeScale).toDisplay(); - - this.updateOption("eraseEntitiesAfterTimeScale", timeScale); - this.$eraseEntitiesAfterTime.val(displayedTime).trigger("change"); - - }); + this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector) this.$eraseDeletedNotesButton = this.$widget.find("#erase-deleted-notes-now-button"); + this.$eraseDeletedNotesButton.on("click", () => { server.post("notes/erase-deleted-notes-now").then(() => { toastService.showMessage(t("note_erasure_timeout.deleted_notes_erased")); }); }); } - - async optionsLoaded(options: OptionMap) { - this.eraseEntitiesAfterTimeInSeconds = options.eraseEntitiesAfterTimeInSeconds; - - const displayedTime = this.convertTime(options.eraseEntitiesAfterTimeInSeconds, options.eraseEntitiesAfterTimeScale).toDisplay(); - this.$eraseEntitiesAfterTime.val(displayedTime); - this.$eraseEntitiesAfterTimeScale.val(options.eraseEntitiesAfterTimeScale); - } - - - convertTime(time: string | number, timeScale: string | number) { - - const value = typeof time === "number" ? time : parseInt(time); - if (Number.isNaN(value)) { - throw new Error(`Time needs to be a valid integer, but received: ${time}`); - } - - const operand = typeof timeScale === "number" ? timeScale : parseInt(timeScale); - if (Number.isNaN(operand) || operand < 1) { - throw new Error(`TimeScale needs to be a valid integer >= 1, but received: ${timeScale}`); - } - - return { - toOption: () => Math.ceil(value * operand), - toDisplay: () => Math.ceil(value / operand), - } - - } - - handleTimeValidation() { - if (this.$eraseEntitiesAfterTime.is(":invalid")) { - // TriliumNextTODO: i18n - toastService.showMessage("The entered time value is not a valid number."); - return false - } - return true; - } - } diff --git a/src/public/app/widgets/type_widgets/options/time_selector.ts b/src/public/app/widgets/type_widgets/options/time_selector.ts new file mode 100644 index 000000000..7e12f5fbd --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/time_selector.ts @@ -0,0 +1,122 @@ +import OptionsWidget from "./options_widget.js"; +import toastService from "../../../services/toast.js"; +import { t } from "../../../services/i18n.js"; +import type { OptionDefinitions, OptionMap } from "../../../../../services/options_interface.js"; + +type TimeSelectorConstructor = { + widgetId: string; + widgetLabelId: string; + optionValueId: keyof OptionDefinitions; + optionTimeScaleId: keyof OptionDefinitions; + includedTimeScales?: Set; +}; + +type TimeSelectorScale = "seconds" | "minutes" | "hours" | "days"; + +const TPL = (options: Omit) => ` +
            + +
            + + +
            +
            + + +`; + +export default class TimeSelector extends OptionsWidget { + private $timeValueInput!: JQuery; + private $timeScaleSelect!: JQuery; + private internalTimeInSeconds!: string | number; + private widgetId: string; + private widgetLabelId: string; + private optionValueId: keyof OptionDefinitions; + private optionTimeScaleId: keyof OptionDefinitions; + private includedTimeScales: Set; + + constructor(options: TimeSelectorConstructor) { + super(); + this.widgetId = options.widgetId; + this.widgetLabelId = options.widgetLabelId; + this.optionValueId = options.optionValueId; + this.optionTimeScaleId = options.optionTimeScaleId; + this.includedTimeScales = !options.includedTimeScales ? new Set(["seconds", "minutes", "hours", "days"]) : options.includedTimeScales; + } + + doRender() { + this.$widget = $( + TPL({ + widgetId: this.widgetId, + widgetLabelId: this.widgetLabelId, + includedTimeScales: this.includedTimeScales + }) + ); + + this.$timeValueInput = this.$widget.find(`#${this.widgetId}`); + this.$timeScaleSelect = this.$widget.find(`#${this.widgetId}-time-scale`); + + this.$timeValueInput.on("change", () => { + const time = this.$timeValueInput.val(); + const timeScale = this.$timeScaleSelect.val(); + + if (!this.handleTimeValidation() || typeof timeScale !== "string" || !time) return; + + this.internalTimeInSeconds = this.convertTime(time, timeScale).toOption(); + this.updateOption(this.optionValueId, this.internalTimeInSeconds); + }); + + this.$timeScaleSelect.on("change", () => { + const timeScale = this.$timeScaleSelect.val(); + + if (!this.handleTimeValidation() || typeof timeScale !== "string") return; + + //calculate the new displayed value + const displayedTime = this.convertTime(this.internalTimeInSeconds, timeScale).toDisplay(); + + this.updateOption(this.optionTimeScaleId, timeScale); + this.$timeValueInput.val(displayedTime).trigger("change"); + }); + } + + async optionsLoaded(options: OptionMap) { + this.internalTimeInSeconds = options[this.optionValueId]; + const displayedTime = this.convertTime(options[this.optionValueId], options[this.optionTimeScaleId]).toDisplay(); + this.$timeValueInput.val(displayedTime); + this.$timeScaleSelect.val(options[this.optionTimeScaleId]); + } + + convertTime(time: string | number, timeScale: string | number) { + const value = typeof time === "number" ? time : parseInt(time); + if (Number.isNaN(value)) { + throw new Error(`Time needs to be a valid integer, but received: ${time}`); + } + + const operand = typeof timeScale === "number" ? timeScale : parseInt(timeScale); + if (Number.isNaN(operand) || operand < 1) { + throw new Error(`TimeScale needs to be a valid integer >= 1, but received: ${timeScale}`); + } + + return { + toOption: () => Math.ceil(value * operand), + toDisplay: () => Math.ceil(value / operand) + }; + } + + handleTimeValidation() { + if (this.$timeValueInput.is(":invalid")) { + toastService.showError(t("time_selector.invalid_input")); + return false; + } + return true; + } +} diff --git a/src/public/app/widgets/type_widgets/web_view.js b/src/public/app/widgets/type_widgets/web_view.ts similarity index 64% rename from src/public/app/widgets/type_widgets/web_view.js rename to src/public/app/widgets/type_widgets/web_view.ts index 5bfa74f05..a5aeef534 100644 --- a/src/public/app/widgets/type_widgets/web_view.js +++ b/src/public/app/widgets/type_widgets/web_view.ts @@ -1,25 +1,36 @@ import { t } from "../../services/i18n.js"; import TypeWidget from "./type_widget.js"; import attributeService from "../../services/attributes.js"; +import type FNote from "../../entities/fnote.js"; +import type { EventData } from "../../components/app_context.js"; +import utils from "../../services/utils.js"; const TPL = `

            ${t("web_view.web_view")}

            - +

            ${t("web_view.embed_websites")}

            ${t("web_view.create_label")}

            - -

            ${t("web_view.disclaimer")}

            - -

            ${t("web_view.experimental_note")}

            - + ${buildElement()}
            `; +function buildElement() { + if (!utils.isElectron()) { + return ``; + } else { + return ``; + } +} + export default class WebViewTypeWidget extends TypeWidget { + + private $noteDetailWebViewHelp!: JQuery; + private $noteDetailWebViewContent!: JQuery; + static getType() { return "webView"; } @@ -34,11 +45,15 @@ export default class WebViewTypeWidget extends TypeWidget { super.doRender(); } - async doRefresh(note) { + async doRefresh(note: FNote) { this.$widget.show(); this.$noteDetailWebViewHelp.hide(); this.$noteDetailWebViewContent.hide(); + if (!this.note) { + return; + } + const webViewSrc = this.note.getLabelValue("webViewSrc"); if (webViewSrc) { @@ -54,17 +69,19 @@ export default class WebViewTypeWidget extends TypeWidget { } cleanup() { - this.$noteDetailWebViewContent.removeAttribute("src"); + this.$noteDetailWebViewContent.removeAttr("src"); } setDimensions() { const $parent = this.$widget; - this.$noteDetailWebViewContent.height($parent.height()).width($parent.width()); + this.$noteDetailWebViewContent + .height($parent.height() ?? 0) + .width($parent.width() ?? 0); } - entitiesReloadedEvent({ loadResults }) { - if (loadResults.getAttributeRows().find((attr) => attr.name === "webViewSrc" && attributeService.isAffecting(attr, this.noteContext.note))) { + entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { + if (loadResults.getAttributeRows().find((attr) => attr.name === "webViewSrc" && attributeService.isAffecting(attr, this.noteContext?.note))) { this.refresh(); } } diff --git a/src/public/stylesheets/theme-next.css b/src/public/stylesheets/theme-next.css index c740a3006..1c8a7d810 100644 --- a/src/public/stylesheets/theme-next.css +++ b/src/public/stylesheets/theme-next.css @@ -5,3 +5,7 @@ /* Import the dark color scheme when the system preference is set to dark mode */ @import url(./theme-next-dark.css) (prefers-color-scheme: dark); + +:root { + --theme-style-auto: true; +} diff --git a/src/public/stylesheets/theme.css b/src/public/stylesheets/theme.css index 35a22e516..6b8fee32d 100644 --- a/src/public/stylesheets/theme.css +++ b/src/public/stylesheets/theme.css @@ -5,3 +5,7 @@ /* Import the dark color scheme when the system preference is set to dark mode */ @import url(./theme-dark.css) (prefers-color-scheme: dark); + +:root { + --theme-style-auto: true; +} \ No newline at end of file diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 3525f7a27..7deb0d59c 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -989,8 +989,6 @@ "web_view": "网页视图", "embed_websites": "网页视图类型的笔记允许您将网站嵌入到 Trilium 中。", "create_label": "首先,请创建一个带有您要嵌入的 URL 地址的标签,例如 #webViewSrc=\"https://www.bing.com\"", - "disclaimer": "实验性功能免责声明", - "experimental_note": "网页视图是一种实验性的笔记类型,将来可能会被移除或大幅更改。网页视图只在桌面端有效。" }, "backend_log": { "refresh": "刷新" @@ -1122,7 +1120,7 @@ "attachment_erasure_timeout": { "attachment_erasure_timeout": "附件清理超时", "attachment_auto_deletion_description": "如果附件在一段时间后不再被笔记引用,它们将自动被删除(并被清理)。", - "erase_attachments_after_x_seconds": "在附件在笔记中未被使用 X 秒后清理", + "erase_attachments_after": "Erase unused attachments after:", "manual_erasing_description": "您还可以手动触发清理(而不考虑上述定义的超时时间):", "erase_unused_attachments_now": "立即清理未使用的附件笔记", "unused_attachments_erased": "未使用的附件已被删除。" @@ -1134,7 +1132,7 @@ "note_erasure_timeout": { "note_erasure_timeout_title": "笔记清理超时", "note_erasure_description": "被删除的笔记(以及属性、历史版本等)最初仅被标记为“删除”,可以从“最近修改”对话框中恢复它们。经过一段时间后,已删除的笔记会被“清理”,这意味着它们的内容将无法恢复。此设置允许您配置从删除到清除笔记之间的时间长度。", - "erase_notes_after": "Erase notes after", + "erase_notes_after": "Erase notes after:", "manual_erasing_description": "您还可以手动触发清理(不考虑上述定义的超时):", "erase_deleted_notes_now": "立即清理已删除的笔记", "deleted_notes_erased": "已删除的笔记已被清理。" diff --git a/src/public/translations/de/translation.json b/src/public/translations/de/translation.json index 9d318b7c2..c450085e3 100644 --- a/src/public/translations/de/translation.json +++ b/src/public/translations/de/translation.json @@ -965,9 +965,7 @@ "web_view": { "web_view": "Webansicht", "embed_websites": "Hinweis vom Typ Web View ermöglicht das Einbetten von Websites in Trilium.", - "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"", - "disclaimer": "Haftungsausschluss zum experimentellen Status", - "experimental_note": "Web View ist ein experimenteller Notiztyp und könnte in Zukunft entfernt oder grundlegend geändert werden. Web View funktioniert auch nur im Desktop-Build." + "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"" }, "backend_log": { "refresh": "Aktualisieren" @@ -1090,7 +1088,7 @@ "attachment_erasure_timeout": { "attachment_erasure_timeout": "Zeitüberschreitung beim Löschen von Anhängen", "attachment_auto_deletion_description": "Anhänge werden automatisch gelöscht (und gelöscht), wenn sie nach einer definierten Zeitspanne nicht mehr in ihrer Notiz referenziert werden.", - "erase_attachments_after_x_seconds": "Anhänge nach X Sekunden löschen, nachdem sie nicht in der Notiz verwendet wurden", + "erase_attachments_after": "Erase unused attachments after:", "manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):", "erase_unused_attachments_now": "Lösche jetzt nicht verwendete Anhangnotizen", "unused_attachments_erased": "Nicht verwendete Anhänge wurden gelöscht." @@ -1102,7 +1100,7 @@ "note_erasure_timeout": { "note_erasure_timeout_title": "Beachte das Zeitlimit für die Löschung", "note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.", - "erase_notes_after": "Notizen löschen nach", + "erase_notes_after": "Notizen löschen nach:", "manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):", "erase_deleted_notes_now": "Jetzt gelöschte Notizen löschen", "deleted_notes_erased": "Gelöschte Notizen wurden gelöscht." diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 7fed97206..027c2f269 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1005,9 +1005,7 @@ "web_view": { "web_view": "Web View", "embed_websites": "Note of type Web View allows you to embed websites into Trilium.", - "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"", - "disclaimer": "Disclaimer on the experimental status", - "experimental_note": "Web View is an experimental note type, and it might be removed or substantially changed in the future. Web View works also only in the desktop build." + "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"" }, "backend_log": { "refresh": "Refresh" @@ -1148,7 +1146,7 @@ "attachment_erasure_timeout": { "attachment_erasure_timeout": "Attachment Erasure Timeout", "attachment_auto_deletion_description": "Attachments get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.", - "erase_attachments_after_x_seconds": "Erase attachments after X seconds of not being used in its note", + "erase_attachments_after": "Erase unused attachments after:", "manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):", "erase_unused_attachments_now": "Erase unused attachment notes now", "unused_attachments_erased": "Unused attachments have been erased." @@ -1160,7 +1158,7 @@ "note_erasure_timeout": { "note_erasure_timeout_title": "Note Erasure Timeout", "note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.", - "erase_notes_after": "Erase notes after", + "erase_notes_after": "Erase notes after:", "manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):", "erase_deleted_notes_now": "Erase deleted notes now", "deleted_notes_erased": "Deleted notes have been erased." @@ -1678,5 +1676,8 @@ "test_description": "This will test the connection and handshake to the sync server. If the sync server isn't initialized, this will set it up to sync with the local document.", "test_button": "Test sync", "handshake_failed": "Sync server handshake failed, error: {{message}}" - } + }, + "time_selector": { + "invalid_input": "The entered time value is not a valid number." + } } diff --git a/src/public/translations/es/translation.json b/src/public/translations/es/translation.json index f23b90972..3810997c3 100644 --- a/src/public/translations/es/translation.json +++ b/src/public/translations/es/translation.json @@ -1003,9 +1003,7 @@ "web_view": { "web_view": "Vista web", "embed_websites": "La nota de tipo Web View le permite insertar sitios web en Trilium.", - "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"", - "disclaimer": "Descargo de responsabilidad sobre el estado experimental", - "experimental_note": "Web View es un tipo de nota experimental y es posible que se elimine o cambie sustancialmente en el futuro. Web View también funciona solo en la versión de escritorio." + "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"" }, "backend_log": { "refresh": "Refrescar" @@ -1146,7 +1144,7 @@ "attachment_erasure_timeout": { "attachment_erasure_timeout": "Tiempo de espera para borrar archivos adjuntos", "attachment_auto_deletion_description": "Los archivos adjuntos se eliminan (y borran) automáticamente si ya no se hace referencia a ellos en su nota después de un tiempo de espera definido.", - "erase_attachments_after_x_seconds": "Borrar archivos adjuntos después de X segundos de no usarse en su nota", + "erase_attachments_after": "Erase unused attachments after:", "manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):", "erase_unused_attachments_now": "Borrar ahora los archivos adjuntos no utilizados en la nota", "unused_attachments_erased": "Los archivos adjuntos no utilizados se han eliminado." @@ -1158,7 +1156,7 @@ "note_erasure_timeout": { "note_erasure_timeout_title": "Tiempo de espera de borrado de notas", "note_erasure_description": "Las notas eliminadas (y los atributos, las revisiones ...) en principio solo están marcadas como eliminadas y es posible recuperarlas del diálogo de Notas recientes. Después de un período de tiempo, las notas eliminadas son \" borradas\", lo que significa que su contenido ya no es recuperable. Esta configuración le permite configurar la longitud del período entre eliminar y borrar la nota.", - "erase_notes_after": "Borrar notas después de", + "erase_notes_after": "Borrar notas después de:", "manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):", "erase_deleted_notes_now": "Borrar notas eliminadas ahora", "deleted_notes_erased": "Las notas eliminadas han sido borradas." diff --git a/src/public/translations/fr/translation.json b/src/public/translations/fr/translation.json index 9e13ba005..891760da8 100644 --- a/src/public/translations/fr/translation.json +++ b/src/public/translations/fr/translation.json @@ -966,9 +966,7 @@ "web_view": { "web_view": "Affichage Web", "embed_websites": "Les notes de type Affichage Web vous permet d'intégrer des sites Web dans Trilium.", - "create_label": "Pour commencer, veuillez créer un label avec l'adresse URL que vous souhaitez intégrer, par ex. #webViewSrc=\"https://www.google.com\"", - "disclaimer": "Avertissement sur le statut expérimental", - "experimental_note": "Affichage Web est un type de note expérimental et il pourrait être supprimé ou considérablement modifié à l'avenir. Affichage Web ne fonctionne que dans la version de bureau." + "create_label": "Pour commencer, veuillez créer un label avec l'adresse URL que vous souhaitez intégrer, par ex. #webViewSrc=\"https://www.google.com\"" }, "backend_log": { "refresh": "Rafraîchir" @@ -1091,7 +1089,7 @@ "attachment_erasure_timeout": { "attachment_erasure_timeout": "Délai d'effacement des pièces jointes", "attachment_auto_deletion_description": "Les pièces jointes sont automatiquement supprimées (et effacées) si elles ne sont plus référencées par leur note après un certain délai.", - "erase_attachments_after_x_seconds": "Effacer les pièces jointes après X secondes sans utilisation dans sa note", + "erase_attachments_after": "Erase unused attachments after:", "manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte du délai défini ci-dessus) :", "erase_unused_attachments_now": "Effacez maintenant les pièces jointes inutilisées", "unused_attachments_erased": "Les pièces jointes inutilisées ont été effacées." @@ -1103,7 +1101,7 @@ "note_erasure_timeout": { "note_erasure_timeout_title": "Délai d'effacement des notes", "note_erasure_description": "Les notes supprimées (et les attributs, versions...) sont seulement marquées comme supprimées et il est possible de les récupérer à partir de la boîte de dialogue Notes récentes. Après un certain temps, les notes supprimées sont « effacées », ce qui signifie que leur contenu n'est plus récupérable. Ce paramètre vous permet de configurer la durée entre la suppression et l'effacement de la note.", - "erase_notes_after": "Effacer les notes après", + "erase_notes_after": "Effacer les notes après:", "manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte de la durée définie ci-dessus) :", "erase_deleted_notes_now": "Effacer les notes supprimées maintenant", "deleted_notes_erased": "Les notes supprimées ont été effacées." diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index 1f768eea6..2d22c2c3c 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -78,7 +78,7 @@ "attachment_erasure_timeout": { "attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.", "attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor", - "erase_attachments_after_x_seconds": "Șterge atașamentele după X secunde după ce acestea n-au mai fost folosite într-o notiță", + "erase_attachments_after": "Erase unused attachments after:", "erase_unused_attachments_now": "Elimină atașamentele șterse acum", "manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe", "unused_attachments_erased": "Atașamentele nefolosite au fost șterse." @@ -836,7 +836,7 @@ "note_erasure_timeout": { "deleted_notes_erased": "Notițele șterse au fost eliminate permanent.", "erase_deleted_notes_now": "Elimină notițele șterse acum", - "erase_notes_after": "Elimină notițele șterse după", + "erase_notes_after": "Elimină notițele șterse după:", "manual_erasing_description": "Se poate rula o eliminare manuală (fără a lua în considerare timpul definit mai sus):", "note_erasure_description": "Notițele șterse (precum și atributele, reviziile) sunt prima oară doar marcate drept șterse și este posibil să fie recuperate din ecranul Notițe recente. După o perioadă de timp, notițele șterse vor fi „eliminate”, caz în care conținutul lor nu se poate recupera. Această setare permite configurarea duratei de timp dintre ștergerea și eliminarea notițelor.", "note_erasure_timeout_title": "Timpul de eliminare automată a notițelor șterse" @@ -1307,9 +1307,7 @@ }, "web_view": { "create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"", - "disclaimer": "Avertisment despre statutul experimental", "embed_websites": "Notițele de tip „Vizualizare web” permit încorporarea site-urilor web în Trilium.", - "experimental_note": "„Vizualizare web” este un tip experimental de notiță, și poate fi înlăturat sau schimbat substanțial în viitor. De asemenea, Web View funcționeză doar în aplicația desktop.", "web_view": "Vizualizare web" }, "wrap_lines": { diff --git a/src/public/translations/tw/translation.json b/src/public/translations/tw/translation.json index 463ec858f..654033580 100644 --- a/src/public/translations/tw/translation.json +++ b/src/public/translations/tw/translation.json @@ -970,9 +970,7 @@ "web_view": { "web_view": "網頁視圖", "embed_websites": "網頁視圖類型的筆記允許您將網站嵌入到 Trilium 中。", - "create_label": "首先,請新增一個帶有您要嵌入的 URL 地址的標籤,例如 #webViewSrc=\"https://www.bing.com\"", - "disclaimer": "實驗性功能免責聲明", - "experimental_note": "網頁視圖是一種實驗性的筆記類型,將來可能會被移除或大幅更改。網頁視圖只在桌面端有效。" + "create_label": "首先,請新增一個帶有您要嵌入的 URL 地址的標籤,例如 #webViewSrc=\"https://www.bing.com\"" }, "backend_log": { "refresh": "刷新" @@ -1100,7 +1098,7 @@ "attachment_erasure_timeout": { "attachment_erasure_timeout": "附件清理超時", "attachment_auto_deletion_description": "如果附件在一段時間後不再被筆記引用,它們將自動被刪除(並被清理)。", - "erase_attachments_after_x_seconds": "在附件在筆記中未被使用 X 秒後清理", + "erase_attachments_after": "Erase unused attachments after:", "manual_erasing_description": "您還可以手動觸發清理(而不考慮上述定義的超時時間):", "erase_unused_attachments_now": "立即清理未使用的附件筆記", "unused_attachments_erased": "未使用的附件已被刪除。" @@ -1112,7 +1110,7 @@ "note_erasure_timeout": { "note_erasure_timeout_title": "筆記清理超時", "note_erasure_description": "被刪除的筆記(以及屬性、歷史版本等)最初僅被標記為「刪除」,可以從「最近修改」對話框中恢復它們。經過一段時間後,已刪除的筆記會被「清理」,這意味著它們的內容將無法恢復。此設定允許您設定從刪除到清除筆記之間的時間長度。", - "erase_notes_after": "Erase notes after", + "erase_notes_after": "Erase notes after:", "manual_erasing_description": "您還可以手動觸發清理(不考慮上述定義的超時):", "erase_deleted_notes_now": "立即清理已刪除的筆記", "deleted_notes_erased": "已刪除的筆記已被清理。" diff --git a/src/routes/api/options.ts b/src/routes/api/options.ts index 85272ea62..7273fbde2 100644 --- a/src/routes/api/options.ts +++ b/src/routes/api/options.ts @@ -61,6 +61,7 @@ const ALLOWED_OPTIONS = new Set([ "checkForUpdates", "disableTray", "eraseUnusedAttachmentsAfterSeconds", + "eraseUnusedAttachmentsAfterTimeScale", "disableTray", "customSearchEngineName", "customSearchEngineUrl", diff --git a/src/services/options_init.ts b/src/services/options_init.ts index da81abc90..ac2cbad24 100644 --- a/src/services/options_init.ts +++ b/src/services/options_init.ts @@ -122,7 +122,8 @@ const defaultOptions: DefaultOption[] = [ { name: "highlightsList", value: '["bold","italic","underline","color","bgColor"]', isSynced: true }, { name: "checkForUpdates", value: "true", isSynced: true }, { name: "disableTray", value: "false", isSynced: false }, - { name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, + { name: "eraseUnusedAttachmentsAfterSeconds", value: "2592000", isSynced: true }, // default 30 days + { name: "eraseUnusedAttachmentsAfterTimeScale", value: "86400", isSynced: true }, // default 86400 seconds = Day { name: "customSearchEngineName", value: "DuckDuckGo", isSynced: true }, { name: "customSearchEngineUrl", value: "https://duckduckgo.com/?q={keyword}", isSynced: true }, { name: "promotedAttributesOpenInRibbon", value: "true", isSynced: true }, diff --git a/src/services/options_interface.ts b/src/services/options_interface.ts index 9a89c9ac7..b3b75faca 100644 --- a/src/services/options_interface.ts +++ b/src/services/options_interface.ts @@ -67,6 +67,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions