Merge remote-tracking branch 'origin/develop' into bare2share2

# Conflicts:
#	src/public/translations/en/translation.json
This commit is contained in:
Matt Wilkie
2025-02-17 13:54:40 -07:00
41 changed files with 409 additions and 460 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -65,6 +65,7 @@ jobs:
- name: Run the build
uses: ./.github/actions/build-server
with:
os: linux
arch: ${{ matrix.arch }}
- name: Publish release

4
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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": [

View File

@@ -13,6 +13,37 @@
<h1 data-trilium-h1>Reference</h1>
<div class="ck-content">
<h2>Detecting mobile vs. desktop</h2>
<p>The mobile layout is different than the one on the desktop. Use <code>body.mobile</code> and <code>body.desktop</code> to
differentiate between them.</p><pre><code class="language-text-css">body.mobile #root-widget {
/* Do something on mobile */
}
body.desktop #root-widget {
/* Do something on desktop */
}</code></pre>
<p>Do note that there is also a “tablet mode” in the mobile layout. For that
particular case media queries are required:</p><pre><code class="language-text-css">@media (max-width: 991px) {
#launcher-pane {
/* Do something on mobile layout */
}
}
@media (min-width: 992px) {
#launcher-pane {
/* Do something on mobile tablet + desktop layout */
}
}</code></pre>
<h2>Detecting horizontal vs. vertical layout</h2>
<p>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) {
<h4>On macOS</h4>
<p>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:</p><pre><code class="language-text-x-trilium-auto">body {
using:</p><pre><code class="language-text-css">body {
--native-titlebar-darwin-x-offset: 12;
--native-titlebar-darwin-y-offset: 14 !important;
}</code></pre>
<h3>Background/transparency effects on Windows (Mica)</h3>
<p>Windows 11 offers a special background/transparency effect called Mica,
which can be enabled by themes by setting the <code>--background-material</code> variable
at <code>body</code> level:</p><pre><code class="language-text-x-trilium-auto">body.electron.platform-win32 {
at <code>body</code> level:</p><pre><code class="language-text-css">body.electron.platform-win32 {
--background-material: tabbed;
}</code></pre>
<p>The value can be either <code>tabbed</code> (especially useful for the horizontal
@@ -104,12 +135,12 @@ body.electron:not(.native-titlebar) {
<p>Theme capabilities are small adjustments done through CSS variables that
can affect the layout or the visual aspect of the application.</p>
<p>In the tab bar, to display the icons of notes instead of the icon of the
workspace:</p><pre><code class="language-text-x-trilium-auto">:root {
workspace:</p><pre><code class="language-text-css">:root {
--tab-note-icons: true;
}</code></pre>
<p>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:</p><pre><code class="language-text-x-trilium-auto">.note-tab .note-tab-wrapper {
on the tab instead of the whole background color:</p><pre><code class="language-text-css">.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</a>.
Basically import a font into Trilium and assign it <code>#customResourceProvider=fonts/myfont.ttf</code> and
then import the font in CSS via <code>/custom/fonts/myfont.ttf</code>.</p>
<h2>Dark and light themes</h2>
<p>A light theme needs to have the following CSS:</p><pre><code class="language-text-css">:root {
--theme-style: light;
}</code></pre>
<p>if the theme is dark, then <code>--theme-style</code> needs to be <code>dark</code>.</p>
<p>If the theme is auto (e.g. supports both light or dark based on <code>prefers-color-scheme</code>)
it must also declare (in addition to setting <code>--theme-style</code> to
either <code>light</code> or <code>dark</code>):</p><pre><code class="language-text-css">:root {
--theme-style-auto: true;
}</code></pre>
<p>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).</p>
</div>
</div>
</body>

View File

@@ -1,38 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../style.css">
<base target="_parent">
<title data-trilium-title>Installation</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Installation</h1>
<div class="ck-content">
<h2>Desktop application</h2>
<figure class="table">
<table style="border-color:transparent;border-style:solid;">
<tbody>
<tr>
<td style="border-color:transparent;text-align:center;">
<figure class="image image_resized" style="width:9.91%;">
<img style="aspect-ratio:267/267;" src="Installation_Fedora_logo.svg"
width="267" height="267">
</figure>
<p><a href="Installation/On%20Fedora%20Linux.html"><span class="text-big">Fedora</span></a>
</p>
</td>
</tr>
</tbody>
</table>
</figure>
<h2>Self-hosted server</h2>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,82 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>On Fedora Linux</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>On Fedora Linux</h1>
<div class="ck-content">
<p>First, download a release from GitHub.</p>
<figure class="table" style="width:100%;">
<table class="ck-table-resized">
<colgroup>
<col style="width:3.52%;">
<col style="width:58.5%;">
<col style="width:37.98%;">
</colgroup>
<tbody>
<tr>
<th>1</th>
<td>
<figure class="image">
<img style="aspect-ratio:816/581;" src="3_On Fedora Linux_Screenshot.png"
width="816" height="581">
</figure>
</td>
<td>In your file explorer, look for the <code>.rpm</code> file of TriliumNext.</td>
</tr>
<tr>
<th>2</th>
<td>
<figure class="image">
<img style="aspect-ratio:339/219;" src="On Fedora Linux_Screenshot.png"
width="339" height="219">
</figure>
</td>
<td>Right click the file and select <i>Open With Software Install</i>.</td>
</tr>
<tr>
<th>3</th>
<td>
<figure class="image">
<img style="aspect-ratio:996/953;" src="1_On Fedora Linux_Screenshot.png"
width="996" height="953">
</figure>
</td>
<td>
<p>GNOME Software will appear. Press the
<img src="2_On Fedora Linux_image.png"
width="106" height="34">.</p>
<p>You will be asked to confirm the action by entering your password.</p>
<p>After confirmation the software will start installing.</p>
<p>Once it's done the “Install” button will turn into
<img src="1_On Fedora Linux_image.png"
width="105" height="34">.</p>
</td>
</tr>
<tr>
<th>4</th>
<td>
<figure class="image">
<img style="aspect-ratio:389/224;" src="2_On Fedora Linux_Screenshot.png"
width="389" height="224">
</figure>
</td>
<td>After installation, the application will be available in the GNOME overview
section.</td>
</tr>
</tbody>
</table>
</figure>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0" width="267" height="267" id="svg2">
<defs id="defs5"/>
<path d="M 266.62575,133.50613 C 266.62575,59.98128 207.02222,0.37583 133.49792,0.37583 C 60.00668,0.37583 0.42639,59.93123 0.37425,133.41225 L 0.37425,236.4333 C 0.4138,253.11763 13.94545,266.62417 30.64027,266.62417 L 133.55192,266.62417 C 207.05167,266.59532 266.62575,207.01142 266.62575,133.50613" id="voice" style="fill:#294172"/>
<path d="M 77.126289,142.09756 C 77.126289,142.09756 124.97104,142.09756 124.97104,142.09756 C 124.97104,142.09756 124.97104,189.94234 124.97104,189.94234 C 124.97104,216.35263 103.53659,237.78707 77.126289,237.78707 C 50.715979,237.78707 29.28153,216.35263 29.28153,189.94234 C 29.28153,163.53203 50.715979,142.09756 77.126289,142.09756 z" id="in" style="fill:none;stroke:#3c6eb4;stroke-width:29.21"/>
<use transform="matrix(-1,0,0,-1,249.71151,284.2882)" id="finity" xlink:href="#in"/>
<path d="M 139.6074,127.52923 L 139.6074,189.87541 C 139.6074,224.37943 111.63203,252.35541 77.12679,252.35541 C 71.89185,252.35541 68.1703,251.7644 63.32444,250.49771 C 56.25849,248.64859 50.48398,242.85518 50.48158,236.1166 C 50.48158,227.97147 56.39394,222.0467 65.23187,222.0467 C 69.43824,222.0467 70.96454,222.85435 77.12679,222.85435 C 95.3184,222.85435 110.07443,208.11916 110.10634,189.92756 L 110.10634,161.27099 C 110.10634,158.70324 108.01971,156.62274 105.44767,156.62274 L 83.78246,156.61846 C 75.71034,156.61846 69.18845,150.18003 69.18845,142.0858 C 69.18414,133.94124 75.77725,127.52923 83.93653,127.52923" id="free" style="fill:#ffffff"/>
<use transform="matrix(-1,0,0,-1,249.71152,284.28821)" id="dom" xlink:href="#free"/>
<path d="M 243.65456,243.58425 C 243.65456,243.58425 243.6546,238.05286 243.6546,238.05286 L 241.12607,243.85062 C 241.12607,243.85062 238.66466,238.05286 238.66466,238.05286 L 238.66513,243.58425 L 237.24683,243.58425 L 237.24683,234.84933 L 238.73387,234.84933 C 238.73387,234.84933 241.16784,240.42984 241.16784,240.42984 L 243.56495,234.84933 L 245.07039,234.84933 L 245.07039,243.58425 L 243.65456,243.58425 z M 233.32154,236.31241 L 233.32154,243.58425 L 231.83941,243.58425 L 231.83941,236.31241 L 229.35453,236.31241 L 229.35453,234.84933 L 235.80399,234.84933 L 235.80399,236.31241" id="TM" style="fill:#3c6eb4"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -74,6 +74,8 @@
<br>
<img src="4_Calendar View_image.png" width="425" height="91">
</li>
<li>Creating new notes from the calendar will respect the <code>~child:template</code> relation
if set on the book note.</li>
</ul>
<h2>Interacting with events</h2>
<ul>

View File

@@ -12,7 +12,53 @@
<div class="content">
<h1 data-trilium-h1>Downloading responses from Google Forms</h1>
<div class="ck-content"></div>
<div class="ck-content">
<p>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.</p>
<p>Note that the link will be publicly accessible to everyone (however the
link is in a hard-to-guess format such as <code>https://docs.google.com/spreadsheets/d/e/2PACX-1vTA8NU2_eZFhc8TFadCZPreBfvP7un8IHd6J0SchrLLw3ueGmntNZjwRmsH2ZRcp1pJYDAzMz1FmFaj/pub?output=csv</code>).
Make sure you are not accidentally publishing sensitive information.</p>
<h2>Obtaining the CSV link</h2>
<ol>
<li>Open the Google Forms in a browser.</li>
<li>Select the “Responses” tab and click on “Link to Sheets”.</li>
<li>Select “Create a new spreadsheet” and press “Create”.</li>
<li>In Google Sheets, select File → Share → Publish to web.</li>
<li>In the “Publish to the web” screen, make sure the “Link” tab is selected
and instead of “Web page”, select “Comma-separated values (.csv)”.</li>
<li>Copy the given link which will be used for the upcoming script.</li>
</ol>
<h2>Creating the script</h2>
<p>Create a “JS Frontend” script:</p><pre><code class="language-text-x-trilium-auto">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.</code></pre>
<p>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:</p><pre><code class="language-text-x-trilium-auto">const content = data
.split("\n")
.slice(1)
.map((row) =&gt; row.split(","));</code></pre>
<p>This will return the data as an array of arrays.</p>
</div>
</div>
</body>

View File

@@ -6,6 +6,6 @@
</head>
<frameset cols="25%,75%">
<frame name="navigation" src="navigation.html">
<frame name="detail" src="User%20Guide/Installation.html">
<frame name="detail" src="User%20Guide/Features/Export%20as%20PDF.html">
</frameset>
</html>

View File

@@ -9,12 +9,6 @@
<ul>
<li>User Guide
<ul>
<li><a href="User%20Guide/Installation.html" target="detail">Installation</a>
<ul>
<li><a href="User%20Guide/Installation/On%20Fedora%20Linux.html" target="detail">On Fedora Linux</a>
</li>
</ul>
</li>
<li>Features
<ul>
<li><a href="User%20Guide/Features/Export%20as%20PDF.html" target="detail">Export as PDF</a>

View File

@@ -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<HTMLElement>;
private $noteTypeButton!: JQuery<HTMLElement>;
private $noteTypeDesc!: JQuery<HTMLElement>;
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<HTMLElement>;
const $title = $("<span>").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('<div class="dropdown-divider"></div>');
@@ -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();
}

View File

@@ -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();
}

View File

@@ -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 = `
<div class="options-section">
<h4>${t("attachment_erasure_timeout.attachment_erasure_timeout")}</h4>
<p>${t("attachment_erasure_timeout.attachment_auto_deletion_description")}</p>
<div class="form-group">
<label>${t("attachment_erasure_timeout.erase_attachments_after_x_seconds")}</label>
<input class="erase-unused-attachments-after-time-in-seconds form-control options-number-input" type="number" min="0">
</div>
<div id="time-selector-placeholder"></div>
<p>${t("attachment_erasure_timeout.manual_erasing_description")}</p>
<button class="erase-unused-attachments-now-button btn btn-secondary">${t("attachment_erasure_timeout.erase_unused_attachments_now")}</button>
</div>`;
export default class AttachmentErasureTimeoutOptions extends OptionsWidget {
private $eraseUnusedAttachmentsAfterTimeInSeconds!: JQuery<HTMLElement>;
export default class AttachmentErasureTimeoutOptions extends TimeSelector {
private $eraseUnusedAttachmentsNowButton!: JQuery<HTMLElement>;
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);
}
}

View File

@@ -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 = `
<div class="options-section">
<h4>${t("note_erasure_timeout.note_erasure_timeout_title")}</h4>
<p>${t("note_erasure_timeout.note_erasure_description")}</p>
<div class="form-group">
<label for="erase-entities-after-time">${t("note_erasure_timeout.erase_notes_after")}</label>
<div class="d-flex gap-2">
<input id="erase-entities-after-time" class="form-control options-number-input" type="number" min="0" steps="1" required>
<!-- TriliumNextTODO: i18n the strings when refactoring this to a standalone widget -->
<select id="erase-entities-after-time-scale" class="form-select duration-selector" required>
<option value="1">${t("duration.seconds")}</option>
<option value="60">${t("duration.minutes")}</option>
<option value="3600">${t("duration.hours")}</option>
<option value="86400">${t("duration.days")}</option>
</select>
</div>
</div>
<div id="time-selector-placeholder"></div>
<p>${t("note_erasure_timeout.manual_erasing_description")}</p>
<button id="erase-deleted-notes-now-button" class="btn btn-secondary">${t("note_erasure_timeout.erase_deleted_notes_now")}</button>
<style>
.duration-selector {
width: auto;
}
</style>
</div>`;
export default class NoteErasureTimeoutOptions extends OptionsWidget {
private $eraseEntitiesAfterTime!: JQuery<HTMLInputElement>;
private $eraseEntitiesAfterTimeScale!: JQuery<HTMLSelectElement>;
export default class NoteErasureTimeoutOptions extends TimeSelector {
private $eraseDeletedNotesButton!: JQuery<HTMLButtonElement>;
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;
}
}

View File

@@ -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<TimeSelectorScale>;
};
type TimeSelectorScale = "seconds" | "minutes" | "hours" | "days";
const TPL = (options: Omit<TimeSelectorConstructor, "optionValueId" | "optionTimeScaleId">) => `
<div class="form-group">
<label for="${options.widgetId}">${t(options.widgetLabelId)}</label>
<div class="d-flex gap-2">
<input id="${options.widgetId}" class="form-control options-number-input" type="number" min="0" steps="1" required>
<select id="${options.widgetId}-time-scale" class="form-select duration-selector" required>
${options.includedTimeScales?.has("seconds") ? `<option value="1">${t("duration.seconds")}</option>` : ""}
${options.includedTimeScales?.has("minutes") ? `<option value="60">${t("duration.minutes")}</option>` : ""}
${options.includedTimeScales?.has("hours") ? `<option value="3600">${t("duration.hours")}</option>` : ""}
${options.includedTimeScales?.has("days") ? `<option value="86400">${t("duration.days")}</option>` : ""}
</select>
</div>
</div>
</div>
<style>
.duration-selector {
width: auto;
}
</style>`;
export default class TimeSelector extends OptionsWidget {
private $timeValueInput!: JQuery<HTMLInputElement>;
private $timeScaleSelect!: JQuery<HTMLSelectElement>;
private internalTimeInSeconds!: string | number;
private widgetId: string;
private widgetLabelId: string;
private optionValueId: keyof OptionDefinitions;
private optionTimeScaleId: keyof OptionDefinitions;
private includedTimeScales: Set<TimeSelectorScale>;
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;
}
}

View File

@@ -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 = `
<div class="note-detail-web-view note-detail-printable" style="height: 100%">
<div class="note-detail-web-view-help alert alert-warning" style="margin: 50px; padding: 20px 20px 0px 20px;">
<h4>${t("web_view.web_view")}</h4>
<p>${t("web_view.embed_websites")}</p>
<p>${t("web_view.create_label")}</p>
<h4>${t("web_view.disclaimer")}</h4>
<p>${t("web_view.experimental_note")}</p>
</div>
<webview class="note-detail-web-view-content"></webview>
${buildElement()}
</div>`;
function buildElement() {
if (!utils.isElectron()) {
return `<iframe class="note-detail-web-view-content" sandbox="allow-same-origin allow-scripts"></iframe>`;
} else {
return `<webview class="note-detail-web-view-content"></webview>`;
}
}
export default class WebViewTypeWidget extends TypeWidget {
private $noteDetailWebViewHelp!: JQuery<HTMLElement>;
private $noteDetailWebViewContent!: JQuery<HTMLElement>;
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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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": "已删除的笔记已被清理。"

View File

@@ -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."

View File

@@ -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."
}
}

View File

@@ -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."

View File

@@ -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."

View File

@@ -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": {

View File

@@ -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": "已刪除的筆記已被清理。"

View File

@@ -61,6 +61,7 @@ const ALLOWED_OPTIONS = new Set([
"checkForUpdates",
"disableTray",
"eraseUnusedAttachmentsAfterSeconds",
"eraseUnusedAttachmentsAfterTimeScale",
"disableTray",
"customSearchEngineName",
"customSearchEngineUrl",

View File

@@ -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 },

View File

@@ -67,6 +67,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions<KeyboardActi
maxContentWidth: number;
minTocHeadings: number;
eraseUnusedAttachmentsAfterSeconds: number;
eraseUnusedAttachmentsAfterTimeScale: number;
firstDayOfWeek: number;
initialized: boolean;