Compare commits

..

137 Commits

Author SHA1 Message Date
Elian Doran
e3595a43c2 docs(user): missing language tags for JSX code blocks 2025-12-24 00:42:20 +02:00
Elian Doran
963fcd615a docs(user): missing language tags for code blocks 2025-12-24 00:30:16 +02:00
Elian Doran
fc8605a14f docs(user): broken code blocks due to table 2025-12-24 00:26:29 +02:00
Elian Doran
04fffb7ee0 Merge remote-tracking branch 'origin/main' into feature/minor_tweaks 2025-12-24 00:20:48 +02:00
Elian Doran
86307b482f docs(user): change URL for demo notes 2025-12-24 00:01:13 +02:00
Elian Doran
3e50262665 fix(status_bar): attribute pane not shown when adding new attribute def 2025-12-23 23:40:16 +02:00
Elian Doran
4e5c97d548 fix(toast): unreadable buttons on light theme 2025-12-23 23:06:29 +02:00
Elian Doran
1185d4b10b chore(layout): reduce padding for promoted attributes 2025-12-23 23:02:26 +02:00
Elian Doran
19cd7a0cad feat(script): improve script error message 2025-12-23 23:02:15 +02:00
Elian Doran
8fda283977 fix(title_actions): dark background in code affecting readability 2025-12-23 22:14:07 +02:00
Elian Doran
6e3a020d0f chore(badges): increase threshold for hiding text 2025-12-23 22:02:06 +02:00
Elian Doran
2fef25e57b chore(badges): allow overflow with clipping 2025-12-23 22:01:49 +02:00
Elian Doran
89ef38ba97 docs(user): mention history navigation buttons in the tab bar 2025-12-23 21:16:24 +02:00
Elian Doran
e96ee87472 chore: prevent error in .envrc for non nix systems (#8144) 2025-12-23 21:10:56 +02:00
Elian Doran
ae83126903 chore(tab_navigation): enable on server as well 2025-12-23 21:09:03 +02:00
Elian Doran
a6c7610fcc fix(dropdown): clicking in the outer area of a menu dismisses it 2025-12-23 21:06:47 +02:00
contributor
d8ce0e5f16 chore: use direnv built-in has command
https://direnv.net/man/direnv-stdlib.1.html#stdlib
2025-12-23 20:52:07 +02:00
Elian Doran
1eebc8ff77 fix(note_badges): avoid "shared locally" on server build 2025-12-23 20:44:12 +02:00
Elian Doran
00592025c0 fix(breadcrumb): overflow hides more items than threshold 2025-12-23 20:40:56 +02:00
Elian Doran
1ac7db41d3 fix(note_title_actions): edited notes link looking strange 2025-12-23 20:33:46 +02:00
contributor
ce84e7a861 chore: prevent error in .envrc for non nix systems 2025-12-23 20:25:15 +02:00
Elian Doran
cf039916d3 chore(note_title_actions): rephrase edited notes 2025-12-23 20:08:21 +02:00
Elian Doran
bfb3ed3ddf chore(layout): relocate note type switcher right above content 2025-12-23 20:03:27 +02:00
Elian Doran
a4f34ce6c5 refactor(client): remove items array 2025-12-23 19:55:50 +02:00
Elian Doran
2ac3d3aaed style(layout): adjust paddings slightly 2025-12-23 19:49:21 +02:00
Elian Doran
494b99d073 chore(layout): integrate edited notes into note title actions 2025-12-23 19:34:19 +02:00
Elian Doran
8434549a9b feat(breadcrumbs): display separator even if no child notes 2025-12-23 19:15:48 +02:00
Elian Doran
c1e01467a5 fix(breadcrumbs): not showing on first render 2025-12-23 19:12:18 +02:00
Elian Doran
4bd8eeb52a Translations update from Hosted Weblate (#8143) 2025-12-23 15:41:11 +02:00
Marcelo Nolasco
f4a6edbc9f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/pt_BR/
2025-12-23 13:38:17 +00:00
noobhjy
0d0a1866e4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-23 13:38:16 +00:00
green
cd47e79a1b Translated using Weblate (Japanese)
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-23 13:38:16 +00:00
Kuzma Simonov
d0a83f7c05 Translated using Weblate (Russian)
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-23 13:38:15 +00:00
Marcelo Nolasco
26160b44ea Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt_BR/
2025-12-23 13:38:14 +00:00
Giovi
9df7b04d7d Translated using Weblate (Italian)
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-12-23 13:38:13 +00:00
Elian Doran
b8d933d308 Icons for code notes by mime type (#8142) 2025-12-23 15:37:51 +02:00
Elian Doran
9021b119b2 fix(client): some tooltips don't render correctly due to extra whitespace 2025-12-23 15:34:53 +02:00
Elian Doran
35034fe9df chore: address requested changes 2025-12-23 15:30:57 +02:00
Elian Doran
2eef655ec2 feat(client): display mapped icon as default for notes 2025-12-23 15:15:22 +02:00
Elian Doran
57ff2f4023 feat(status_bar): display icon for code note switcher 2025-12-23 15:00:31 +02:00
Elian Doran
df6331e3a0 chore(commons): add icon mappings to some common mime types 2025-12-23 15:00:19 +02:00
Elian Doran
b84da65a81 fix(code): not reacting to mime type changes 2025-12-23 14:38:17 +02:00
Elian Doran
58e04a6f72 Support for scripting with Preact and JSX (#8126) 2025-12-23 13:50:35 +02:00
Elian Doran
450bdeb39e fix(deps): update dependency @codemirror/view to v6.39.5 (#8137) 2025-12-23 13:39:58 +02:00
Elian Doran
79494e8cfe chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.2.3 (#8135) 2025-12-23 13:39:40 +02:00
Elian Doran
2afba34055 chore(deps): update typescript-eslint monorepo to v8.50.1 (#8136) 2025-12-23 13:39:26 +02:00
Elian Doran
c391234eeb fix(deps): update fullcalendar monorepo to v6.1.20 (#8138) 2025-12-23 13:39:13 +02:00
Elian Doran
a3fca323c7 test(server): fix test depending on note content 2025-12-23 13:38:38 +02:00
Elian Doran
9332b9ca8f docs(demo): add JSX widget showcase 2025-12-23 13:34:26 +02:00
Elian Doran
8740bf84cf chore(mime_types): set JSX as enabled by default 2025-12-23 13:12:29 +02:00
Elian Doran
1554085d7a chore(scripts/preact): address review 2025-12-23 13:06:33 +02:00
renovate[bot]
7dd4c09057 fix(deps): update fullcalendar monorepo to v6.1.20 2025-12-23 11:05:39 +00:00
renovate[bot]
eafd5140ea fix(deps): update dependency @codemirror/view to v6.39.5 2025-12-23 11:04:52 +00:00
renovate[bot]
cddde353cd chore(deps): update typescript-eslint monorepo to v8.50.1 2025-12-23 11:04:01 +00:00
renovate[bot]
e4ef8f2352 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.2.3 2025-12-23 11:02:27 +00:00
Elian Doran
2561c7ca0d Merge remote-tracking branch 'origin/main' into feature/preact_scripts 2025-12-23 13:00:55 +02:00
Elian Doran
b4e4950d20 chore(client): fix typecheck 2025-12-23 12:59:22 +02:00
Adorian Doran
a4be86dbd8 style/text: prevent reference links inherit color from the current note 2025-12-23 05:26:54 +02:00
Adorian Doran
b6ca6476de demo notes: change the icon and color for the "Trilium Demo" branch 2025-12-23 05:00:23 +02:00
Adorian Doran
db1f632859 style/zen mode: make the read-only note badge visible 2025-12-23 04:24:39 +02:00
Adorian Doran
7af8acec0f style/zen mode: fix the title widget layout, make the icon and title editable 2025-12-23 04:20:17 +02:00
Adorian Doran
3f1b0fa71e style/breadcrumb: tweak 2025-12-23 04:02:53 +02:00
Adorian Doran
519323292c style/breadcrumb: tweak 2025-12-23 03:48:09 +02:00
Adorian Doran
2d6f17aeaa style/breadcrumb: tweak dropdown lists 2025-12-23 03:07:30 +02:00
Adorian Doran
7507d6b385 style/bottom panel: tweak colors 2025-12-23 02:52:03 +02:00
Adorian Doran
d4fa21e7c1 style/bottom panel: tweak colors 2025-12-22 23:57:40 +02:00
Adorian Doran
608f156b82 client: rename "status bar pane" to "bottom panel" 2025-12-22 23:40:11 +02:00
Adorian Doran
0c965bfdf4 client/status bar panes: extract colors as CSS variables 2025-12-22 23:36:35 +02:00
Adorian Doran
d407c72fae Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-22 23:27:55 +02:00
Adorian Doran
bdc0b062d5 client/status bar panes: improve 2025-12-22 23:27:45 +02:00
Elian Doran
0b912b9c7d Translations update from Hosted Weblate (#8134) 2025-12-22 22:35:26 +02:00
Anton Antonov
99ac6b4df1 Translated using Weblate (Bulgarian)
Currently translated at 16.4% (25 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/bg/
2025-12-22 20:23:36 +00:00
Hosted Weblate
b0a97208a2 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/
2025-12-22 20:23:36 +00:00
Anton Antonov
6e044b19c8 Translated using Weblate (Bulgarian)
Currently translated at 12.5% (19 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/bg/
2025-12-22 20:23:35 +00:00
Anton Antonov
198dd15fb4 Added translation using Weblate (Bulgarian) 2025-12-22 20:23:34 +00:00
Anton Antonov
18f3b83827 Added translation using Weblate (Bulgarian) 2025-12-22 20:23:33 +00:00
Anton Antonov
8142b7489a Added translation using Weblate (Bulgarian) 2025-12-22 20:23:33 +00:00
Anton Antonov
7bf0a4134e Added translation using Weblate (Bulgarian) 2025-12-22 20:23:32 +00:00
Kuzma Simonov
29ed08d062 Translated using Weblate (Russian)
Currently translated at 100.0% (1718 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-22 20:23:31 +00:00
Francis C.
68dc7eedec Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.5% (1711 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-22 20:23:31 +00:00
Yunho Park
3fc195998c Translated using Weblate (Korean)
Currently translated at 67.7% (103 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2025-12-22 20:23:30 +00:00
Kuzma Simonov
b6d550f682 Translated using Weblate (Russian)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/
2025-12-22 20:23:29 +00:00
Adorian Doran
1f55ff536e client/status bar panes: tweak 2025-12-22 22:23:16 +02:00
Adorian Doran
67fb8d0354 client/status bar panes: tweak 2025-12-22 21:25:39 +02:00
Adorian Doran
1408b159d7 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-22 19:40:01 +02:00
Adorian Doran
74b00e60e3 client/status bar panes: refactor into own component, add title bar and close button 2025-12-22 19:39:52 +02:00
Elian Doran
1b18a964b9 chore(deps): update dependency @types/tabulator-tables to v6.3.1 (#8131) 2025-12-22 19:35:09 +02:00
renovate[bot]
931f0a694e chore(deps): update dependency @types/tabulator-tables to v6.3.1 2025-12-22 01:50:47 +00:00
Adorian Doran
0d32e1f0d8 style/classic toolbar: fix broken border radius 2025-12-22 02:43:39 +02:00
Adorian Doran
d0f91e7709 style/status bar: hide the focus outline for dropdown buttons 2025-12-22 02:37:00 +02:00
Adorian Doran
353d626d45 style/breadcrumb: tweak arrows 2025-12-22 02:29:05 +02:00
Adorian Doran
af67a3ba11 style/breadcrumb: use scrollable dropdowns for note listings 2025-12-22 02:24:34 +02:00
Adorian Doran
a867c646e4 style: refactor 2025-12-22 02:23:43 +02:00
Adorian Doran
150e2504b1 style: add (limited) support for scrollable menus 2025-12-22 02:20:56 +02:00
Adorian Doran
aa7ae150dc style/text editor/links: tweak 2025-12-22 02:04:35 +02:00
Adorian Doran
d99e08bfdd style/text editor: fix links 2025-12-22 01:39:33 +02:00
Elian Doran
29d038c76b Translations update from Hosted Weblate (#8130) 2025-12-22 00:25:39 +02:00
Barszczun
f1615bb4f6 Translated using Weblate (Polish)
Currently translated at 99.6% (1712 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-21 23:11:12 +01:00
Elian Doran
0688ea7de3 chore(client): address requested changes 2025-12-21 22:59:21 +02:00
Elian Doran
af37c175a3 chore(client): fix typecheck 2025-12-21 22:55:20 +02:00
Elian Doran
7567903da3 docs(user): improve documentation on custom widgets & Preact 2025-12-21 22:51:23 +02:00
Elian Doran
531698cafb fix(server/script): ignoring sub-component JSX 2025-12-21 20:01:59 +02:00
Elian Doran
f68f99806b Merge remote-tracking branch 'origin/main' into feature/preact_scripts 2025-12-21 18:25:24 +02:00
Elian Doran
c4f55395a9 feat(client/jsx): disable debug info 2025-12-21 13:31:44 +02:00
Elian Doran
444c0c6107 chore(client/jsx): fix errors in API 2025-12-21 13:19:42 +02:00
Elian Doran
4da5cb43fc fet(client/jsx): expose basic React widgets 2025-12-21 13:16:05 +02:00
Elian Doran
e6b79e83c4 fet(client/jsx): basic support for JSX render notes 2025-12-21 11:18:42 +02:00
Elian Doran
6e67da7b1f chore(deps): revert sucrase from client 2025-12-21 10:32:54 +02:00
Elian Doran
9071e54bfe chore(client/jsx): use different method for launcher widget defs 2025-12-21 10:26:20 +02:00
Elian Doran
783b5ac8e3 feat(client/jsx): support launcher widgets 2025-12-21 10:23:34 +02:00
Elian Doran
f3f491d141 feat(client/bundle): respect position for TSX widgets 2025-12-21 10:02:13 +02:00
Elian Doran
f8bf301d12 feat(client/bundle): use new toast for script errors with known note ID 2025-12-20 23:34:36 +02:00
Elian Doran
2c25786fa2 feat(client/bundle): expose Trilium hooks 2025-12-20 23:26:10 +02:00
Elian Doran
1093acfe45 feat(client/bundle): make Preact custom widgets content-sized by default 2025-12-20 23:17:30 +02:00
Elian Doran
76f054bbd5 feat(client/bundle): support rendering in other places 2025-12-20 23:16:19 +02:00
Elian Doran
c558255450 feat(client/bundle): add button to open script note 2025-12-20 22:51:04 +02:00
Elian Doran
1e94125133 feat(client/bundle): display toast when parent is missing 2025-12-20 22:45:58 +02:00
Elian Doran
64a770175f refactor(client/bundle): use type for parent name 2025-12-20 22:40:03 +02:00
Elian Doran
e0416097e1 feat(script/jsx): support import syntax for api 2025-12-20 22:23:25 +02:00
Elian Doran
6c1b327f5f feat(script/jsx): support import syntax for preact 2025-12-20 22:14:45 +02:00
Elian Doran
284b66acd2 feat(script/jsx): support export default syntax 2025-12-20 21:59:03 +02:00
Elian Doran
dcd73ff9f9 test(script/jsx): JSX fragment 2025-12-20 21:37:41 +02:00
Elian Doran
645557b505 test(script/jsx): basic JSX processing 2025-12-20 21:35:52 +02:00
Elian Doran
22a83d9f82 refactor(script/jsx): "react-widget" -> "preact-widget" 2025-12-20 21:26:01 +02:00
Elian Doran
f64de3acca chore(script/jsx): move defineWidget into Preact API 2025-12-20 21:25:36 +02:00
Elian Doran
34d5793888 chore(script/jsx): expose RightPanelWidget 2025-12-20 21:19:53 +02:00
Elian Doran
44ca9f457c feat(script/jsx): add support for React hooks 2025-12-20 20:29:03 +02:00
Elian Doran
4d7e5bc8f6 chore(script/jsx): move Preact API in dedicated object 2025-12-20 20:10:19 +02:00
Elian Doran
644ff07a50 feat(script/jsx): get right panel widgets to actually render 2025-12-20 19:49:24 +02:00
Elian Doran
41220a9d1d fix(script/jsx): cannot find preact hydration function 2025-12-20 19:45:44 +02:00
Elian Doran
88945788d6 fix(script/jsx): critical crash if widget fails to render 2025-12-20 19:41:48 +02:00
Elian Doran
fe8f033409 chore(script/jsx): get widgets to be interpreted 2025-12-20 19:36:02 +02:00
Elian Doran
eee7c49f6e fix(script/jsx): module not defined 2025-12-20 19:28:26 +02:00
Elian Doran
d036bf0870 fix(client): full crash if server fails to obtain list of widgets 2025-12-20 19:18:50 +02:00
Elian Doran
fa8ff4bfbf chore(script/jsx): basic client-side logic to render bundles 2025-12-20 19:01:29 +02:00
Elian Doran
3619c0c3e4 feat(script/jsx): compile JSX on server side 2025-12-20 18:46:15 +02:00
Elian Doran
883e32f5c9 chore(script): install sucrase 2025-12-20 18:03:45 +02:00
167 changed files with 4963 additions and 1672 deletions

4
.envrc
View File

@@ -1 +1,3 @@
use flake
if has nix; then
use flake
fi

View File

@@ -17,12 +17,12 @@
},
"dependencies": {
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
"@fullcalendar/interaction": "6.1.19",
"@fullcalendar/list": "6.1.19",
"@fullcalendar/multimonth": "6.1.19",
"@fullcalendar/timegrid": "6.1.19",
"@fullcalendar/core": "6.1.20",
"@fullcalendar/daygrid": "6.1.20",
"@fullcalendar/interaction": "6.1.20",
"@fullcalendar/list": "6.1.20",
"@fullcalendar/multimonth": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1",
@@ -75,7 +75,7 @@
"@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12",
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.0",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.0.11",
"script-loader": "0.7.2",

View File

@@ -1,3 +1,5 @@
import { MIME_TYPES_DICT } from "@triliumnext/commons";
import cssClassManager from "../services/css_class_manager.js";
import type { Froca } from "../services/froca-interface.js";
import noteAttributeCache from "../services/note_attribute_cache.js";
@@ -597,8 +599,9 @@ export default class FNote {
return "bx bx-folder";
}
return "bx bx-note";
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
return "bx bx-data";
} else if (this.type === "code") {
const correspondingMimeType = MIME_TYPES_DICT.find(m => m.mime === this.mime);
return correspondingMimeType?.icon ?? NOTE_TYPE_ICONS.code;
}
return NOTE_TYPE_ICONS[this.type];
}
@@ -989,6 +992,10 @@ export default class FNote {
);
}
isJsx() {
return (this.type === "code" && this.mime === "text/jsx");
}
/** @returns true if this note is HTML */
isHtml() {
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
@@ -996,7 +1003,7 @@ export default class FNote {
/** @returns JS script environment - either "frontend" or "backend" */
getScriptEnv() {
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend"))) {
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend")) || this.isJsx()) {
return "frontend";
}
@@ -1018,7 +1025,7 @@ export default class FNote {
* @returns a promise that resolves when the script has been run. Additionally, for front-end notes, the promise will contain the value that is returned by the script.
*/
async executeScript() {
if (!this.isJavaScript()) {
if (!(this.isJavaScript() || this.isJsx())) {
throw new Error(`Note ${this.noteId} is of type ${this.type} and mime ${this.mime} and thus cannot be executed`);
}

View File

@@ -184,7 +184,7 @@ export default class DesktopLayout {
.child(new HighlightsListWidget())
.child(...this.customWidgets.get("right-pane"))
)
.optChild(isNewLayout, <RightPanelContainer customWidgets={this.customWidgets.get("right-pane")} />)
.optChild(isNewLayout, <RightPanelContainer widgetsByParent={this.customWidgets} />)
)
.optChild(!launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
)

View File

@@ -1,10 +1,15 @@
import { h, VNode } from "preact";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import froca from "./froca.js";
import type { Entity } from "./frontend_script_api.js";
import { WidgetDefinitionWithType } from "./frontend_script_api_preact.js";
import { t } from "./i18n.js";
import ScriptContext from "./script_context.js";
import server from "./server.js";
import toastService, { showError } from "./toast.js";
import froca from "./froca.js";
import utils from "./utils.js";
import { t } from "./i18n.js";
import type { Entity } from "./frontend_script_api.js";
import toastService, { showErrorForScriptNote } from "./toast.js";
import utils, { getErrorMessage } from "./utils.js";
// TODO: Deduplicate with server.
export interface Bundle {
@@ -14,9 +19,13 @@ export interface Bundle {
allNoteIds: string[];
}
interface Widget {
type LegacyWidget = (BasicWidget | RightPanelWidget) & {
parentWidget?: string;
}
};
type WithNoteId<T> = T & {
_noteId: string;
};
export type Widget = WithNoteId<(LegacyWidget | WidgetDefinitionWithType)>;
async function getAndExecuteBundle(noteId: string, originEntity = null, script = null, params = null) {
const bundle = await server.post<Bundle>(`script/bundle/${noteId}`, {
@@ -27,6 +36,8 @@ async function getAndExecuteBundle(noteId: string, originEntity = null, script =
return await executeBundle(bundle, originEntity);
}
export type ParentName = "left-pane" | "center-pane" | "note-detail-pane" | "right-pane";
export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container);
@@ -35,24 +46,14 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
}.call(apiContext);
} catch (e: any) {
const note = await froca.getNote(bundle.noteId);
toastService.showPersistent({
id: `custom-script-failure-${note?.noteId}`,
title: t("toast.bundle-error.title"),
icon: "bx bx-error-circle",
message: t("toast.bundle-error.message", {
id: note?.noteId,
title: note?.title,
message: e.message
})
});
showErrorForScriptNote(bundle.noteId, t("toast.bundle-error.message", { message: e.message }));
logError("Widget initialization failed: ", e);
}
}
async function executeStartupBundles() {
const isMobile = utils.isMobile();
const scriptBundles = await server.get<Bundle[]>("script/startup" + (isMobile ? "?mobile=true" : ""));
const scriptBundles = await server.get<Bundle[]>(`script/startup${ isMobile ? "?mobile=true" : ""}`);
for (const bundle of scriptBundles) {
await executeBundle(bundle);
@@ -60,68 +61,99 @@ async function executeStartupBundles() {
}
export class WidgetsByParent {
private byParent: Record<string, Widget[]>;
private legacyWidgets: Record<string, WithNoteId<LegacyWidget>[]>;
private preactWidgets: Record<string, WithNoteId<WidgetDefinitionWithType>[]>;
constructor() {
this.byParent = {};
this.legacyWidgets = {};
this.preactWidgets = {};
}
add(widget: Widget) {
if (!widget.parentWidget) {
console.log(`Custom widget does not have mandatory 'parentWidget' property defined`);
return;
let hasParentWidget = false;
let isPreact = false;
if ("type" in widget && widget.type === "preact-widget") {
// React-based script.
const reactWidget = widget as WithNoteId<WidgetDefinitionWithType>;
this.preactWidgets[reactWidget.parent] = this.preactWidgets[reactWidget.parent] || [];
this.preactWidgets[reactWidget.parent].push(reactWidget);
isPreact = true;
hasParentWidget = !!reactWidget.parent;
} else if ("parentWidget" in widget && widget.parentWidget) {
this.legacyWidgets[widget.parentWidget] = this.legacyWidgets[widget.parentWidget] || [];
this.legacyWidgets[widget.parentWidget].push(widget);
hasParentWidget = !!widget.parentWidget;
}
this.byParent[widget.parentWidget] = this.byParent[widget.parentWidget] || [];
this.byParent[widget.parentWidget].push(widget);
if (!hasParentWidget) {
showErrorForScriptNote(widget._noteId, t("toast.widget-missing-parent", {
property: isPreact ? "parent" : "parentWidget"
}));
}
}
get(parentName: string) {
if (!this.byParent[parentName]) {
return [];
get(parentName: ParentName) {
const widgets: (BasicWidget | VNode)[] = this.getLegacyWidgets(parentName);
for (const preactWidget of this.getPreactWidgets(parentName)) {
const el = h(preactWidget.render, {});
const widget = new ReactWrappedWidget(el);
widget.contentSized();
if (preactWidget.position) {
widget.position = preactWidget.position;
}
widgets.push(widget);
}
return widgets;
}
getLegacyWidgets(parentName: ParentName): (BasicWidget | RightPanelWidget)[] {
if (!this.legacyWidgets[parentName]) return [];
return (
this.byParent[parentName]
this.legacyWidgets[parentName]
// previously, custom widgets were provided as a single instance, but that has the disadvantage
// for splits where we actually need multiple instaces and thus having a class to instantiate is better
// https://github.com/zadam/trilium/issues/4274
.map((w: any) => (w.prototype ? new w() : w))
);
}
getPreactWidgets(parentName: ParentName) {
return this.preactWidgets[parentName] ?? [];
}
}
async function getWidgetBundlesByParent() {
const scriptBundles = await server.get<Bundle[]>("script/widgets");
const widgetsByParent = new WidgetsByParent();
for (const bundle of scriptBundles) {
let widget;
try {
const scriptBundles = await server.get<Bundle[]>("script/widgets");
try {
widget = await executeBundle(bundle);
if (widget) {
widget._noteId = bundle.noteId;
widgetsByParent.add(widget);
for (const bundle of scriptBundles) {
let widget;
try {
widget = await executeBundle(bundle);
if (widget) {
widget._noteId = bundle.noteId;
widgetsByParent.add(widget);
}
} catch (e: any) {
const noteId = bundle.noteId;
showErrorForScriptNote(noteId, t("toast.bundle-error.message", { message: e.message }));
logError("Widget initialization failed: ", e);
continue;
}
} catch (e: any) {
const noteId = bundle.noteId;
const note = await froca.getNote(noteId);
toastService.showPersistent({
id: `custom-script-failure-${noteId}`,
title: t("toast.bundle-error.title"),
icon: "bx bx-error-circle",
message: t("toast.bundle-error.message", {
id: noteId,
title: note?.title,
message: e.message
})
});
logError("Widget initialization failed: ", e);
continue;
}
} catch (e) {
toastService.showPersistent({
id: `custom-widget-list-failure`,
title: t("toast.widget-list-error.title"),
message: getErrorMessage(e),
icon: "bx bx-error-circle"
});
}
return widgetsByParent;

View File

@@ -1,26 +1,27 @@
import server from "./server.js";
import utils from "./utils.js";
import toastService from "./toast.js";
import linkService from "./link.js";
import { dayjs, formatLogMessage } from "@triliumnext/commons";
import appContext from "../components/app_context.js";
import type Component from "../components/component.js";
import type NoteContext from "../components/note_context.js";
import type FNote from "../entities/fnote.js";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import dateNotesService from "./date_notes.js";
import dialogService from "./dialog.js";
import froca from "./froca.js";
import { preactAPI } from "./frontend_script_api_preact.js";
import { t } from "./i18n.js";
import linkService from "./link.js";
import noteTooltipService from "./note_tooltip.js";
import protectedSessionService from "./protected_session.js";
import dateNotesService from "./date_notes.js";
import searchService from "./search.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js";
import server from "./server.js";
import shortcutService from "./shortcuts.js";
import dialogService from "./dialog.js";
import type FNote from "../entities/fnote.js";
import { t } from "./i18n.js";
import { dayjs } from "@triliumnext/commons";
import type NoteContext from "../components/note_context.js";
import type Component from "../components/component.js";
import { formatLogMessage } from "@triliumnext/commons";
import SpacedUpdate from "./spaced_update.js";
import toastService from "./toast.js";
import utils from "./utils.js";
import ws from "./ws.js";
/**
* A whole number
@@ -464,6 +465,8 @@ export interface Api {
* Log given message to the log pane in UI
*/
log(message: string | object): void;
preact: typeof preactAPI;
}
/**
@@ -533,9 +536,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
return params.map((p) => {
if (typeof p === "function") {
return `!@#Function: ${p.toString()}`;
} else {
return p;
}
return p;
});
}
@@ -562,9 +564,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
await ws.waitForMaxKnownEntityChangeId();
return ret.executionResult;
} else {
throw new Error(`server error: ${ret.error}`);
}
throw new Error(`server error: ${ret.error}`);
};
this.runOnBackend = async (func, params = []) => {
@@ -721,6 +722,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
this.logMessages[noteId].push(message);
this.logSpacedUpdates[noteId].scheduleUpdate();
};
this.preact = preactAPI;
}
export default FrontendScriptApi as any as {

View File

@@ -0,0 +1,101 @@
import { Fragment, h, VNode } from "preact";
import * as hooks from "preact/hooks";
import ActionButton from "../widgets/react/ActionButton";
import Admonition from "../widgets/react/Admonition";
import Button from "../widgets/react/Button";
import CKEditor from "../widgets/react/CKEditor";
import Collapsible from "../widgets/react/Collapsible";
import Dropdown from "../widgets/react/Dropdown";
import FormCheckbox from "../widgets/react/FormCheckbox";
import FormDropdownList from "../widgets/react/FormDropdownList";
import { FormFileUploadActionButton, FormFileUploadButton } from "../widgets/react/FormFileUpload";
import FormGroup from "../widgets/react/FormGroup";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../widgets/react/FormList";
import FormRadioGroup from "../widgets/react/FormRadioGroup";
import FormText from "../widgets/react/FormText";
import FormTextArea from "../widgets/react/FormTextArea";
import FormTextBox from "../widgets/react/FormTextBox";
import FormToggle from "../widgets/react/FormToggle";
import * as triliumHooks from "../widgets/react/hooks";
import Icon from "../widgets/react/Icon";
import LinkButton from "../widgets/react/LinkButton";
import LoadingSpinner from "../widgets/react/LoadingSpinner";
import Modal from "../widgets/react/Modal";
import NoteAutocomplete from "../widgets/react/NoteAutocomplete";
import NoteLink from "../widgets/react/NoteLink";
import RawHtml from "../widgets/react/RawHtml";
import Slider from "../widgets/react/Slider";
import RightPanelWidget from "../widgets/sidebar/RightPanelWidget";
export interface WidgetDefinition {
parent: "right-pane",
render: () => VNode,
position?: number,
}
export interface WidgetDefinitionWithType extends WidgetDefinition {
type: "preact-widget"
}
export interface LauncherWidgetDefinitionWithType {
type: "preact-launcher-widget"
render: () => VNode
}
export const preactAPI = Object.freeze({
// Core
h,
Fragment,
/**
* Method that must be run for widget scripts that run on Preact, using JSX. The method just returns the same definition, reserved for future typechecking and perhaps validation purposes.
*
* @param definition the widget definition.
*/
defineWidget(definition: WidgetDefinition) {
return {
type: "preact-widget",
...definition
};
},
defineLauncherWidget(definition: Omit<LauncherWidgetDefinitionWithType, "type">) {
return {
type: "preact-launcher-widget",
...definition
};
},
// Basic widgets
ActionButton,
Admonition,
Button,
CKEditor,
Collapsible,
Dropdown,
FormCheckbox,
FormDropdownList,
FormFileUploadButton, FormFileUploadActionButton,
FormGroup,
FormListItem, FormDropdownDivider, FormDropdownSubmenu,
FormRadioGroup,
FormText,
FormTextArea,
FormTextBox,
FormToggle,
Icon,
LinkButton,
LoadingSpinner,
Modal,
NoteAutocomplete,
NoteLink,
RawHtml,
Slider,
// Specialized widgets
RightPanelWidget,
...hooks,
...triliumHooks
});

View File

@@ -1,6 +1,10 @@
import server from "./server.js";
import bundleService, { type Bundle } from "./bundle.js";
import { h, VNode } from "preact";
import type FNote from "../entities/fnote.js";
import { renderReactWidgetAtElement } from "../widgets/react/react_utils.jsx";
import bundleService, { type Bundle } from "./bundle.js";
import froca from "./froca.js";
import server from "./server.js";
async function render(note: FNote, $el: JQuery<HTMLElement>) {
const relations = note.getRelations("renderNote");
@@ -17,12 +21,34 @@ async function render(note: FNote, $el: JQuery<HTMLElement>) {
$scriptContainer.append(bundle.html);
// async so that scripts cannot block trilium execution
bundleService.executeBundle(bundle, note, $scriptContainer);
bundleService.executeBundle(bundle, note, $scriptContainer).then(result => {
// Render JSX
if (bundle.html === "") {
renderIfJsx(bundle, result, $el);
}
});
}
return renderNoteIds.length > 0;
}
async function renderIfJsx(bundle: Bundle, result: unknown, $el: JQuery<HTMLElement>) {
// Ensure the root script note is actually a JSX.
const rootScriptNoteId = await froca.getNote(bundle.noteId);
if (rootScriptNoteId?.mime !== "text/jsx") return;
// Ensure the output is a valid el.
if (typeof result !== "function") return;
// Obtain the parent component.
const closestComponent = glob.getComponentByEl($el.closest(".component")[0]);
if (!closestComponent) return;
// Render the element.
const el = h(result as () => VNode, {});
renderReactWidgetAtElement(closestComponent, el, $el[0]);
}
export default {
render
};

View File

@@ -133,11 +133,11 @@ async function call<T>(method: string, url: string, componentId?: string, option
};
ipc.send("server-request", {
requestId: requestId,
headers: headers,
method: method,
requestId,
headers,
method,
url: `/${window.glob.baseApiUrl}${url}`,
data: data
data
});
})) as any;
} else {
@@ -161,7 +161,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
const options: JQueryAjaxSettings = {
url: window.glob.baseApiUrl + url,
type: method,
headers: headers,
headers,
timeout: 60000,
success: (body, textStatus, jqXhr) => {
const respHeaders: Headers = {};
@@ -288,8 +288,8 @@ async function reportError(method: string, url: string, statusCode: number, resp
t("server.unknown_http_error_content", { statusCode, method, url, message: messageStr }),
15_000);
}
const { throwError } = await import("./ws.js");
throwError(`${statusCode} ${method} ${url} - ${message}`);
const { logError } = await import("./ws.js");
logError(`${statusCode} ${method} ${url} - ${message}`);
}
}

View File

@@ -1,5 +1,8 @@
import { signal } from "@preact/signals";
import appContext from "../components/app_context.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
import utils from "./utils.js";
export interface ToastOptions {
@@ -61,6 +64,24 @@ function showErrorTitleAndMessage(title: string, message: string, timeout = 1000
});
}
export async function showErrorForScriptNote(noteId: string, message: string) {
const note = await froca.getNote(noteId, true);
showPersistent({
id: `custom-widget-failure-${noteId}`,
title: t("toast.scripting-error", { title: note?.title ?? "" }),
icon: note?.getIcon() ?? "bx bx-error-circle",
message,
timeout: 15_000,
buttons: [
{
text: t("toast.open-script-note"),
onClick: () => appContext.tabManager.openInNewTab(noteId, null, true)
}
]
});
}
//#region Toast store
export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
@@ -74,7 +95,7 @@ function addToast(opts: ToastOptions) {
function updateToast(id: string, partial: Partial<ToastOptions>) {
toasts.value = toasts.value.map(toast => {
if (toast.id === id) {
return { ...toast, ...partial }
return { ...toast, ...partial };
}
return toast;
});

View File

@@ -717,7 +717,6 @@ table.promoted-attributes-in-tooltip th {
.tooltip {
font-size: var(--main-font-size) !important;
z-index: calc(var(--ck-z-panel) - 1) !important;
white-space: pre-wrap;
}
.tooltip.tooltip-top {
@@ -2012,8 +2011,10 @@ body.zen .shared-info-widget,
body.zen .ribbon-container:not(:has(.classic-toolbar-widget)),
body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row,
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
body.zen .note-icon-widget,
body.zen .title-row .icon-action,
body.zen .note-badges > *:not(.read-only-badge),
body.zen .ribbon-button-container,
body.zen .inline-title,
body.zen .promoted-attributes-widget,
body.zen .floating-buttons-children > *:not(.bx-edit-alt),
body.zen .action-button,
@@ -2036,11 +2037,11 @@ body.zen #launcher-pane {
}
body.zen .title-row {
display: block !important;
height: unset !important;
-webkit-app-region: drag;
padding-inline-start: env(titlebar-area-x);
padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw) + 2.5em);
border-bottom: none !important;
}
body.zen .floating-buttons {
@@ -2060,8 +2061,6 @@ body.zen .floating-buttons-children .button-widget {
body.zen .note-title-widget,
body.zen .note-title-widget input {
font-size: 1rem !important;
background: transparent !important;
pointer-events: none;
}
body.zen #detail-container {

View File

@@ -234,6 +234,9 @@
--right-pane-item-hover-background: #ffffff26;
--right-pane-item-hover-color: white;
--bottom-panel-background-color: #11111180;
--bottom-panel-title-bar-background-color: #3F3F3F80;
--scrollbar-thumb-color: #fdfdfd5c;
--scrollbar-thumb-hover-color: #ffffff7d;
--scrollbar-background-color: transparent;

View File

@@ -232,6 +232,9 @@
--right-pane-item-hover-background: #00000013;
--right-pane-item-hover-color: inherit;
--bottom-panel-background-color: #0000000a;
--bottom-panel-title-bar-background-color: #00000017;
--scrollbar-thumb-color: #0000005c;
--scrollbar-thumb-hover-color: #00000066;
--scrollbar-background-color: transparent;

View File

@@ -128,6 +128,12 @@ body.backdrop-effects-disabled {
font-size: 0.9rem !important;
}
.dropdown-menu.tn-dropdown-menu-scrollable {
/* Note: scrollable dropdowns does not support submenus */
max-height: 90vh;
overflow: scroll;
}
body.desktop .dropdown-menu::before,
:root .ck.ck-dropdown__panel::before,
:root .excalidraw .popover::before,

View File

@@ -653,7 +653,8 @@ body a.tn-link:focus-visible,
}
body a.tn-link:hover,
.use-tn-links a:hover {
.use-tn-links a:hover,
.use-tn-links a.ck-widget_selected {
box-shadow: 0 0 0 4px var(--link-hover-background);
--background: var(--link-hover-background);
color: var(--link-hover-color);

View File

@@ -670,16 +670,33 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
color: var(--main-text-color);
}
/* Links */
.ck-content a.ck-widget {
outline: none;
}
.ck-content a.ck-widget.ck-widget_selected,
.ck-content a.ck-link_selected {
outline: 2px solid var(--input-focus-outline-color);
outline-offset: 2px;
background: var(--link-hover-background);
}
/* Reference link */
.ck-content a.reference-link,
.ck-content a.reference-link:hover {
/* Apply underline only to the span inside the link so it can follow the
* target note's user defined color */
text-decoration: none;
text-decoration: none;
}
.ck-content a.reference-link > span {
.ck-content a.reference-link.use-note-color > span {
color: var(--custom-color, inherit);
}
.ck-content a.reference-link:hover > span {
text-decoration: underline;
}

View File

@@ -1230,7 +1230,7 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
margin-bottom: 2px;
}
body.vertical-layout #rest-pane > .classic-toolbar-widget {
body.layout-vertical #rest-pane > .classic-toolbar-widget {
border-start-start-radius: var(--center-pane-border-radius);
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -817,7 +817,8 @@
},
"inherited_attribute_list": {
"title": "继承的属性",
"no_inherited_attributes": "没有继承的属性。"
"no_inherited_attributes": "没有继承的属性。",
"none": "无"
},
"note_info_widget": {
"note_id": "笔记 ID",
@@ -2200,5 +2201,8 @@
"toggle": "切换右侧面板",
"custom_widget_go_to_source": "跳转到源码",
"empty_message": "这篇笔记没有展示内容"
},
"attributes_panel": {
"title": "笔记属性"
}
}

View File

@@ -21,8 +21,17 @@
},
"bundle-error": {
"title": "Failed to load a custom script",
"message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}"
}
"message": "Script could not be executed due to:\n\n{{message}}"
},
"widget-list-error": {
"title": "Failed to obtain the list of widgets from the server"
},
"widget-render-error": {
"title": "Failed to render a custom React widget"
},
"widget-missing-parent": "Custom widget does not have mandatory '{{property}}' property defined.",
"open-script-note": "Open script note",
"scripting-error": "Custom script error: {{title}}"
},
"add_link": {
"add_link": "Add link",
@@ -818,7 +827,8 @@
},
"inherited_attribute_list": {
"title": "Inherited Attributes",
"no_inherited_attributes": "No inherited attributes."
"no_inherited_attributes": "No inherited attributes.",
"none": "none"
},
"note_info_widget": {
"note_id": "Note ID",
@@ -1770,7 +1780,8 @@
"note_type_switcher_others": "Other note type",
"note_type_switcher_templates": "Template",
"note_type_switcher_collection": "Collection",
"edited_notes": "Edited notes"
"edited_notes": "Notes edited on this day",
"promoted_attributes": "Promoted attributes"
},
"search_result": {
"no_notes_found": "No notes have been found for given search parameters.",
@@ -2203,6 +2214,9 @@
"note_paths_title": "Note paths",
"code_note_switcher": "Change language mode"
},
"attributes_panel": {
"title": "Note Attributes"
},
"right_pane": {
"empty_message": "Nothing to show for this note",
"empty_button": "Hide the panel",

View File

@@ -523,7 +523,8 @@
},
"toc": {
"table_of_contents": "Sommario",
"options": "Opzioni"
"options": "Opzioni",
"no_headings": "Nessun titolo."
},
"table_of_contents": {
"title": "Sommario",
@@ -556,7 +557,13 @@
},
"highlights_list_2": {
"title": "Punti salienti",
"options": "Opzioni"
"options": "Opzioni",
"title_with_count_one": "{{count}} evidenza",
"title_with_count_many": "{{count}} evidenze",
"title_with_count_other": "{{count}} evidenze",
"modal_title": "Configura elenco dei punti salienti",
"menu_configure": "Configura elenco dei punti salienti...",
"no_highlights": "Nessun punto saliente trovato."
},
"quick-search": {
"placeholder": "Ricerca rapida",
@@ -1388,7 +1395,8 @@
},
"inherited_attribute_list": {
"title": "Attributi ereditati",
"no_inherited_attributes": "Nessun attributo ereditato."
"no_inherited_attributes": "Nessun attributo ereditato.",
"none": "nessuno"
},
"note_info_widget": {
"note_id": "ID nota",
@@ -1400,7 +1408,8 @@
"calculate": "calcolare",
"subtree_size": "(dimensione del sottoalbero: {{size}} in {{count}} note)",
"title": "Nota informativa",
"show_similar_notes": "Mostra note simili"
"show_similar_notes": "Mostra note simili",
"mime": "Tipo MIME"
},
"note_map": {
"open_full": "Espandi completamente",
@@ -2095,14 +2104,20 @@
"background_effects_title": "Gli effetti di sfondo sono ora stabili",
"background_effects_message": "Sui dispositivi Windows, gli effetti di sfondo sono ora completamente stabili. Gli effetti di sfondo aggiungono un tocco di colore all'interfaccia utente sfocando lo sfondo retrostante. Questa tecnica è utilizzata anche in altre applicazioni come Esplora risorse di Windows.",
"background_effects_button": "Abilita gli effetti di sfondo",
"dismiss": "Congedare"
"dismiss": "Congedare",
"new_layout_title": "Nuovo layout",
"new_layout_message": "Abbiamo introdotto un layout modernizzato per Trilium. La barra multifunzione è stata rimossa e integrata perfettamente nell'interfaccia principale, con una nuova barra di stato e sezioni espandibili (come gli attributi promossi) che assumono le funzioni chiave.\n\nIl nuovo layout è abilitato di default e può essere temporaneamente disabilitato tramite Opzioni → Aspetto.",
"new_layout_button": "Maggiori informazioni"
},
"settings": {
"related_settings": "Impostazioni correlate"
},
"settings_appearance": {
"related_code_blocks": "Schema di colori per i blocchi di codice nelle note di testo",
"related_code_notes": "Schema di colori per le note del codice"
"related_code_notes": "Schema di colori per le note del codice",
"ui": "Interfaccia utente",
"ui_old_layout": "Vecchio layout",
"ui_new_layout": "Nuovo layout"
},
"units": {
"percentage": "%"
@@ -2159,13 +2174,18 @@
"execute_script": "Esegui script",
"execute_script_description": "Questa nota è una nota di script. Clicca per eseguire lo script.",
"execute_sql": "Esegui SQL",
"execute_sql_description": "Questa nota è una nota SQL. Clicca per eseguire la query SQL."
"execute_sql_description": "Questa nota è una nota SQL. Clicca per eseguire la query SQL.",
"shared_copy_to_clipboard": "Copia link negli appunti",
"shared_open_in_browser": "Apri il link nel browser",
"shared_unshare": "Rimuovi condivisione"
},
"breadcrumb": {
"workspace_badge": "Area di lavoro",
"scroll_to_top_title": "Vai all'inizio della nota",
"hoisted_badge": "Sollevato",
"hoisted_badge_title": "Abbassato"
"hoisted_badge_title": "Abbassato",
"create_new_note": "Crea nuova nota secondaria",
"empty_hide_archived_notes": "Nascondi note archiviate"
},
"status_bar": {
"language_title": "Cambia lingua dei contenuti",
@@ -2191,5 +2211,14 @@
"note_paths_other": "{{count}} percorsi",
"note_paths_title": "Nota percorsi",
"code_note_switcher": "Cambia modalità lingua"
},
"attributes_panel": {
"title": "Attributi delle note"
},
"right_pane": {
"empty_message": "Nulla da segnalare per questa nota",
"empty_button": "Nascondi il pannello",
"toggle": "Attiva/disattiva pannello destro",
"custom_widget_go_to_source": "Vai al codice sorgente"
}
}

View File

@@ -1621,7 +1621,8 @@
},
"inherited_attribute_list": {
"title": "継承属性",
"no_inherited_attributes": "継承属性はありません。"
"no_inherited_attributes": "継承属性はありません。",
"none": "なし"
},
"note_map": {
"open_full": "拡大表示",
@@ -2200,5 +2201,8 @@
"empty_button": "パネルを非表示",
"toggle": "右パネルを切り替え",
"custom_widget_go_to_source": "ソースコードへ移動"
},
"attributes_panel": {
"title": "ノート属性"
}
}

View File

@@ -1934,7 +1934,10 @@
},
"highlights_list_2": {
"title": "Lista wyróżnień",
"options": "Opcje"
"options": "Opcje",
"modal_title": "Konfiguracja listy wyróżnień",
"menu_configure": "Konfiguracja listy wyróżnień...",
"no_highlights": "Nie znaleziono wyróżnień."
},
"quick-search": {
"placeholder": "Szybkie wyszukiwanie",

View File

@@ -46,7 +46,10 @@
"save": "Salvar",
"edit_branch_prefix": "Editar Prefixo do Branch",
"help_on_tree_prefix": "Ajuda sobre o prefixo da árvore de notas",
"branch_prefix_saved": "O prefixo de ramificação foi salvo."
"branch_prefix_saved": "O prefixo de ramificação foi salvo.",
"edit_branch_prefix_multiple": "Editar prefixo do ramo para {{count}} ramos",
"branch_prefix_saved_multiple": "O prefixo do ramo foi salvo para {{count}} ramos.",
"affected_branches": "Ramos afetados ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "Ações em massa",
@@ -254,7 +257,8 @@
"export_status": "Status da exportação",
"export_in_progress": "Exportação em andamento: {{progressCount}}",
"export_finished_successfully": "Exportação concluída com sucesso.",
"format_pdf": "PDF para impressão ou compartilhamento."
"format_pdf": "PDF para impressão ou compartilhamento.",
"share-format": "HTML para publicação na web — usa o mesmo tema das notas compartilhadas, mas pode ser publicado como um site estático."
},
"help": {
"noteNavigation": "Navegação de notas",
@@ -308,7 +312,8 @@
"other": "Outros",
"quickSearch": "focar no campo de pesquisa rápida",
"inPageSearch": "pesquisa na página",
"title": "Folha de Dicas"
"title": "Folha de Dicas",
"editShortcuts": "Editar atalhos de teclado"
},
"import": {
"importIntoNote": "Importar para a nota",
@@ -334,7 +339,8 @@
},
"import-status": "Status da importação",
"in-progress": "Importação em andamento: {{progress}}",
"successful": "Importação concluída com sucesso."
"successful": "Importação concluída com sucesso.",
"importZipRecommendation": "Ao importar um arquivo ZIP, a hierarquia de notas refletirá a estrutura de subdiretórios dentro do arquivo."
},
"include_note": {
"dialog_title": "Incluir nota",
@@ -349,7 +355,8 @@
"info": {
"modalTitle": "Mensagem informativa",
"closeButton": "Fechar",
"okButton": "OK"
"okButton": "OK",
"copy_to_clipboard": "Copiar para a área de transferência"
},
"jump_to_note": {
"search_placeholder": "Pesquise uma nota pelo nome ou digite > para comandos...",
@@ -771,7 +778,7 @@
"import-into-note": "Importar na nota",
"apply-bulk-actions": "Aplicar ações em massa",
"converted-to-attachments": "{{count}} notas foram convertidas em anexos.",
"convert-to-attachment-confirm": "Tem certeza de que deseja converter as notas selecionadas em anexos de suas notas-pai?",
"convert-to-attachment-confirm": "Tem certeza de que deseja converter as notas selecionadas em anexos de suas notas pai? Esta operação se aplica apenas a notas de imagem; outras notas serão ignoradas.",
"open-in-popup": "Edição rápida",
"archive": "Ficheiro",
"unarchive": "Desarquivar"
@@ -789,7 +796,7 @@
"show_attachments_description": "Exibir anexos da nota",
"search_notes_title": "Buscar Notas",
"search_notes_description": "Abrir busca avançada",
"configure_launch_bar_description": "Abrir a configuração da barra de lançamento, para adicionar ou remover itens."
"configure_launch_bar_description": "Abrir a configuração da barra de atalho, para adicionar ou remover itens."
},
"delete_note": {
"delete_note": "Excluir nota",
@@ -882,7 +889,7 @@
"zoom_out": "Reduzir",
"reset_zoom_level": "Redefinir Zoom",
"zoom_in": "Aumentar",
"configure_launchbar": "Configurar Barra de Lançamento",
"configure_launchbar": "Configurar Barra de Atalhos",
"show_shared_notes_subtree": "Exibir Subárvore de Notas Compartilhadas",
"advanced": "Avançado",
"open_dev_tools": "Abrir Ferramentas de Desenvolvedor",
@@ -897,7 +904,9 @@
"logout": "Sair",
"show-cheatsheet": "Exibir Cheatsheet",
"toggle-zen-mode": "Modo Zen",
"reload_hint": "Recarregar pode ajudar com alguns problemas visuais sem reiniciar toda a aplicação."
"reload_hint": "Recarregar pode ajudar com alguns problemas visuais sem reiniciar toda a aplicação.",
"new-version-available": "Nova atualização disponível",
"download-update": "Obter a versão {{latestVersion}}"
},
"zen_mode": {
"button_exit": "Sair do Modo Zen"
@@ -935,7 +944,14 @@
"convert_into_attachment_successful": "A nota '{{title}}' foi convertida para anexo.",
"print_pdf": "Exportar como PDF…",
"open_note_externally_title": "O arquivo será aberto em uma aplicação externa e monitorado por alterações. Você então poderá enviar a versão modificada de volta para o Trilium.",
"convert_into_attachment_prompt": "Você tem certeza que quer converter a nota '{{title}}' em um anexo da nota pai?"
"convert_into_attachment_prompt": "Você tem certeza que quer converter a nota '{{title}}' em um anexo da nota pai?",
"open_note_on_server": "Abrir nota no servidor",
"view_revisions": "Revisões da nota…",
"advanced": "Avançado",
"export_as_image": "Exportar como imagem",
"export_as_image_png": "PNG (raster)",
"export_as_image_svg": "SVG (vetorial)",
"note_map": "Mapa de notas"
},
"protected_session_status": {
"inactive": "Clique para entrar na sessão protegida",
@@ -979,7 +995,8 @@
"insert_child_note": "Inserir nota filha",
"delete_this_note": "Excluir essa nota",
"error_unrecognized_command": "Comando não reconhecido {{command}}",
"error_cannot_get_branch_id": "Não foi possível obter o branchId para o notePath '{{notePath}} '"
"error_cannot_get_branch_id": "Não foi possível obter o branchId para o notePath '{{notePath}} '",
"note_revisions": "Revisões de notas"
},
"note_icon": {
"change_note_icon": "Alterar ícone da nota",
@@ -1007,7 +1024,12 @@
"table": "Tabela",
"geo-map": "Mapa geográfico",
"board": "Quadro",
"include_archived_notes": "Exibir notas arquivadas"
"include_archived_notes": "Exibir notas arquivadas",
"expand_tooltip": "Expande os filhos diretos desta coleção (um nível). Para mais opções, pressione a seta à direita.",
"expand_first_level": "Expandir filhos diretos",
"expand_nth_level": "Expandir {{depth}} níveis",
"expand_all_levels": "Expandir todos os níveis",
"presentation": "Apresentação"
},
"edited_notes": {
"no_edited_notes_found": "Ainda não há nenhuma nota editada neste dia…",
@@ -1020,7 +1042,7 @@
"file_type": "Tipo do arquivo",
"file_size": "Tamanho do arquivo",
"download": "Baixar",
"open": "Abrir",
"open": "Abrir externamente",
"upload_new_revision": "Enviar nova revisão",
"upload_success": "Uma nova revisão de arquivo foi enviada.",
"upload_failed": "O envio de uma nova revisão de arquivo falhou.",
@@ -1040,7 +1062,8 @@
},
"inherited_attribute_list": {
"title": "Atributos Herdados",
"no_inherited_attributes": "Nenhum atributo herdado."
"no_inherited_attributes": "Nenhum atributo herdado.",
"none": "nenhum"
},
"note_info_widget": {
"note_id": "ID da Nota",
@@ -1051,7 +1074,9 @@
"calculate": "calcular",
"title": "Informações da nota",
"subtree_size": "(tamanho da subárvore: {{size}} em {{count}} notas)",
"note_size_info": "O tamanho da nota fornece uma estimativa aproximada dos requisitos de armazenamento para esta nota. Leva em conta o conteúdo e o conteúdo de suas revisões de nota."
"note_size_info": "O tamanho da nota fornece uma estimativa aproximada dos requisitos de armazenamento para esta nota. Leva em conta o conteúdo e o conteúdo de suas revisões de nota.",
"mime": "Tipo MIME",
"show_similar_notes": "Mostrar notas semelhantes"
},
"note_map": {
"open_full": "Expandir completamente",
@@ -1111,7 +1136,8 @@
"search_note_saved": "Nota de pesquisa foi salva em {{- notePathTitle}}",
"fast_search_description": "A opção de pesquisa rápida desabilita a pesquisa de texto completo do conteúdo de nota, o que pode acelerar a pesquisa em grandes bancos de dados.",
"include_archived_notes_description": "As notas arquivadas são por padrão excluídas dos resultados da pesquisa, com esta opção elas serão incluídas.",
"debug_description": "A depuração irá imprimir informações adicionais no console para ajudar na depuração de consultas complexas"
"debug_description": "A depuração irá imprimir informações adicionais no console para ajudar na depuração de consultas complexas",
"view_options": "Ver opções:"
},
"similar_notes": {
"title": "Notas Similares",
@@ -1192,7 +1218,13 @@
},
"editable_text": {
"placeholder": "Digite o conteúdo da sua nota aqui…",
"auto-detect-language": "Detectado automaticamente"
"auto-detect-language": "Detectado automaticamente",
"editor_crashed_title": "O editor de texto travou",
"editor_crashed_content": "Seu conteúdo foi recuperado com sucesso, mas algumas das suas alterações mais recentes podem não ter sido salvas.",
"editor_crashed_details_button": "Veja mais detalhes...",
"editor_crashed_details_intro": "Se você encontrar este erro várias vezes, considere relatá-lo no GitHub colando as informações abaixo.",
"editor_crashed_details_title": "Informação técnica",
"keeps-crashing": "O componente de edição continua travando. Tente reiniciar o Trilium. Se o problema persistir, considere criar um relatório de bug."
},
"empty": {
"search_placeholder": "buscar uma nota pelo nome",
@@ -1299,7 +1331,8 @@
"title": "Largura do Conteúdo",
"max_width_label": "Largura máxima do conteúdo",
"max_width_unit": "pixels",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em telas wide."
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em telas wide.",
"centerContent": "Manter conteúdo centralizado"
},
"native_title_bar": {
"title": "Barra de Título Nativa (requer recarregar o app)",
@@ -1319,11 +1352,11 @@
"layout": "Layout",
"layout-vertical-title": "Vertical",
"layout-horizontal-title": "Horizontal",
"layout-vertical-description": "barra de lançamento está a esquerda (padrão)",
"layout-horizontal-description": "barra de lançamento está abaixo da barra de abas, a barra de abas agora tem a largura total."
"layout-vertical-description": "barra de atalho está a esquerda (padrão)",
"layout-horizontal-description": "barra de atalho está abaixo da barra de abas, a barra de abas agora tem a largura total."
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Este lançador não define uma nota destino."
"this_launcher_doesnt_define_target_note": "Este atalho não define uma nota destino."
},
"copy_image_reference_button": {
"button_title": "Copiar referência da imagem para a área de transferência, pode ser colado em uma nota de texto."
@@ -1378,7 +1411,10 @@
"title": "Editor"
},
"code_mime_types": {
"title": "Tipos MIME disponíveis no dropdown"
"title": "Tipos MIME disponíveis no dropdown",
"tooltip_syntax_highlighting": "Realce de sintaxe",
"tooltip_code_block_syntax": "Blocos de código em notas de texto",
"tooltip_code_note_syntax": "Notas de código"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "Atribuições de teclas do Vim",
@@ -1498,7 +1534,13 @@
"min-days-in-first-week": "Mínimo de dias da primeira semana",
"first-week-info": "Primeira semana que contenha a primeira Quinta-feira do ano é baseado na <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a>.",
"first-week-warning": "Alterar as opções de primeira semana pode causar duplicidade nas Notas Semanais existentes e estas Notas não serão atualizadas de acordo.",
"formatting-locale": "Formato de data e número"
"formatting-locale": "Formato de data e número",
"tuesday": "Terça-feira",
"wednesday": "Quarta-feira",
"thursday": "Quinta-feira",
"friday": "Sexta-feira",
"saturday": "Sábado",
"formatting-locale-auto": "Com base no idioma do aplicativo"
},
"backup": {
"automatic_backup": "Backup automático",
@@ -1526,7 +1568,7 @@
"mind-map": "Mapa Mental",
"file": "Arquivo",
"image": "Imagem",
"launcher": "Lançador",
"launcher": "Atalho",
"doc": "Documento",
"widget": "Widget",
"confirm-change": "Não é recomentado alterar o tipo da nota quando o conteúdo da nota não está vazio. Quer continuar assim mesmo?",
@@ -1569,7 +1611,13 @@
},
"highlights_list_2": {
"title": "Lista de Destaques",
"options": "Opções"
"options": "Opções",
"title_with_count_one": "{{count}} destaque",
"title_with_count_many": "{{count}} destaques",
"title_with_count_other": "{{count}} destaques",
"modal_title": "Configurar lista de destaques",
"menu_configure": "Configurar lista de destaques…",
"no_highlights": "Nenhum destaque encontrado."
},
"quick-search": {
"placeholder": "Busca rápida",
@@ -1592,23 +1640,33 @@
"refresh-saved-search-results": "Atualizar resultados de pesquisa salvos",
"create-child-note": "Criar nota filha",
"unhoist": "Desafixar",
"toggle-sidebar": "Alternar barra lateral"
"toggle-sidebar": "Alternar barra lateral",
"dropping-not-allowed": "Não é permitido soltar notas neste local."
},
"title_bar_buttons": {
"window-on-top": "Manter Janela no Topo"
},
"note_detail": {
"could_not_find_typewidget": "Não foi possível encontrar typeWidget para o tipo '{{type}}'"
"could_not_find_typewidget": "Não foi possível encontrar typeWidget para o tipo '{{type}}'",
"printing": "Impressão em andamento…",
"printing_pdf": "Exportação para PDF em andamento…"
},
"note_title": {
"placeholder": "digite o título da nota aqui..."
"placeholder": "digite o título da nota aqui...",
"created_on": "Criado em <Value />",
"last_modified": "Modificado em <Value />",
"note_type_switcher_label": "Alternar de {{type}} para:",
"note_type_switcher_others": "Outro tipo de nota",
"note_type_switcher_templates": "Modelo",
"note_type_switcher_collection": "Coleção",
"edited_notes": "Notas editadas"
},
"search_result": {
"no_notes_found": "Nenhuma nota encontrada para os parâmetros de busca digitados.",
"search_not_executed": "A busca ainda não foi executada. Clique no botão \"Buscar\" acima para ver os resultados."
},
"spacer": {
"configure_launchbar": "Configurar Barra de Lançamento"
"configure_launchbar": "Configurar Barra de Atalhos"
},
"sql_result": {
"no_rows": "Nenhum linha foi retornada para esta consulta"
@@ -1630,7 +1688,8 @@
},
"toc": {
"table_of_contents": "Tabela de Conteúdos",
"options": "Opções"
"options": "Opções",
"no_headings": "Nenhum título."
},
"watched_file_update_status": {
"file_last_modified": "O arquivo <code class=\"file-path\"></code> foi modificado pela última vez em <span class=\"file-last-modified\"></span>.",
@@ -1673,22 +1732,24 @@
"ws": {
"sync-check-failed": "A verificação de sincronização falhou!",
"consistency-checks-failed": "A verificação de consistência falhou! Veja os logs para detalhes.",
"encountered-error": "Encontrado o erro \"{{message}}\", verifique o console."
"encountered-error": "Encontrado o erro \"{{message}}\", verifique o console.",
"lost-websocket-connection-title": "Conexão com o servidor perdida",
"lost-websocket-connection-message": "Verifique a configuração do seu proxy reverso (por exemplo, nginx ou Apache) para garantir que as conexões WebSocket estejam devidamente permitidas e não estejam sendo bloqueadas."
},
"hoisted_note": {
"confirm_unhoisting": "A nota solicitada '{{requestedNote}}' está fora da árvore da nota fixada '{{hoistedNote}}' e você precisa desafixar para acessar a nota. Quer prosseguir e desafixar?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "Você deseja realmente reiniciar \"{{title}}\"? Todos os dados / configurações desta nota (e suas filhas) serão perdidos o lançador irá retornar para sua localização original.",
"add-note-launcher": "Adicionar um lançador de nota",
"add-script-launcher": "Adicionar um lançador de script",
"reset_launcher_confirm": "Você deseja realmente reiniciar \"{{title}}\"? Todos os dados / configurações desta nota (e suas filhas) serão perdidos o atalho irá retornar para sua localização original.",
"add-note-launcher": "Adicionar um atalho de nota",
"add-script-launcher": "Adicionar um atalho de script",
"add-custom-widget": "Adicionar um componente personalizado",
"add-spacer": "Adicionar um espaçador",
"delete": "Excluir <kbd data-command=\"deleteNotes\"></kbd>",
"reset": "Reiniciar",
"move-to-visible-launchers": "Mover para lançadores visíveis",
"move-to-available-launchers": "Mover para lançadores disponíveis",
"duplicate-launcher": "Duplicar o lançador <kbd data-command=\"duplicateSubtree\">"
"move-to-visible-launchers": "Mover para atalhos visíveis",
"move-to-available-launchers": "Mover para atalhos disponíveis",
"duplicate-launcher": "Duplicar o atalho <kbd data-command=\"duplicateSubtree\">"
},
"highlighting": {
"title": "Blocos de Código",
@@ -1722,7 +1783,8 @@
"copy-link": "Copiar link",
"paste": "Colar",
"paste-as-plain-text": "Colar como texto sem formatação",
"search_online": "Buscar por \"{{term}}\" usando {{searchEngine}}"
"search_online": "Buscar por \"{{term}}\" usando {{searchEngine}}",
"search_in_trilium": "Pesquisar por \"{{term}}\" no Trilium"
},
"image_context_menu": {
"copy_reference_to_clipboard": "Copiar referência para a área de transferência",
@@ -1732,7 +1794,8 @@
"open_note_in_new_tab": "Abrir nota em nova aba",
"open_note_in_new_split": "Abrir nota em nova divisão",
"open_note_in_new_window": "Abrir nota em nova janela",
"open_note_in_popup": "Edição rápida"
"open_note_in_popup": "Edição rápida",
"open_note_in_other_split": "Abrir nota no outro painel dividido"
},
"electron_integration": {
"desktop-application": "Aplicação Desktop",
@@ -1800,8 +1863,9 @@
"unknown_widget": "Componente desconhecido para \"{{id}}\"."
},
"note_language": {
"not_set": "Não atribuído",
"configure-languages": "Configurar idiomas..."
"not_set": "Nenhum idioma definido",
"configure-languages": "Configurar idiomas...",
"help-on-languages": "Ajuda sobre idiomas de conteúdo…"
},
"content_language": {
"title": "Idiomas do conteúdo",
@@ -1819,7 +1883,8 @@
"button_title": "Exportar diagrama como PNG"
},
"svg": {
"export_to_png": "O diagrama não pôde ser exportado como PNG."
"export_to_png": "O diagrama não pôde ser exportado como PNG.",
"export_to_svg": "O diagrama não pôde ser exportado para SVG."
},
"code_theme": {
"title": "Aparência",
@@ -1838,7 +1903,11 @@
"editorfeatures": {
"title": "Recursos",
"emoji_completion_enabled": "Habilitar auto-completar de Emoji",
"note_completion_enabled": "Habilitar auto-completar de notas"
"note_completion_enabled": "Habilitar auto-completar de notas",
"emoji_completion_description": "Se ativado, emojis podem ser inseridos facilmente no texto digitando`:`, seguido do nome do emoji.",
"note_completion_description": "Se ativado, links para notas podem ser criados digitando `@` seguido do título de uma nota.",
"slash_commands_enabled": "Ativar comandos de barra",
"slash_commands_description": "Se ativado, comandos de edição como inserir quebras de linha ou títulos podem ser acionados digitando`/`."
},
"table_view": {
"new-row": "Nova linha",
@@ -1863,7 +1932,7 @@
"book_properties_config": {
"hide-weekends": "Ocultar fins de semana",
"display-week-numbers": "Exibir números de semana",
"map-style": "Estilo do mapa:",
"map-style": "Estilo do mapa",
"max-nesting-depth": "Profundidade máxima de aninhamento:",
"vector_light": "Vetor (Claro)",
"vector_dark": "Vetor (Escuro)",
@@ -1888,7 +1957,8 @@
"new-item-placeholder": "Escreva o título da nota...",
"add-column-placeholder": "Escreva o nome da coluna...",
"edit-note-title": "Clique para editar o título da nota",
"edit-column-title": "Clique para editar o título da coluna"
"edit-column-title": "Clique para editar o título da coluna",
"column-already-exists": "Esta coluna já existe no quadro."
},
"call_to_action": {
"next_theme_title": "Testar no novo tema do Trilium",
@@ -1897,14 +1967,20 @@
"background_effects_title": "Efeitos de fundo estão estáveis agora",
"background_effects_message": "Em dispositivos Windows, efeitos de fundo estão estáveis agora. Os efeitos de fundo adicionam um toque de cor à interface do usuário borrando o plano de fundo atrás dela. Esta técnica também é usada em outras aplicações como o Windows Explorer.",
"background_effects_button": "Habilitar os efeitos de fundo",
"dismiss": "Dispensar"
"dismiss": "Dispensar",
"new_layout_title": "Novo layout",
"new_layout_message": "Introduzimos um layout modernizado para o Trilium. A faixa de opções foi removida e integrada de forma contínua à interface principal, com uma nova barra de status e seções expansíveis (como atributos promovidos) assumindo funções importantes.\n\nO novo layout vem ativado por padrão e pode ser desativado temporariamente em Opções → Aparência.",
"new_layout_button": "Mais informações"
},
"settings": {
"related_settings": "Configurações relacionadas"
},
"settings_appearance": {
"related_code_blocks": "Esquema de cores para blocos de código em notas de texto",
"related_code_notes": "Esquema de cores para notas de código"
"related_code_notes": "Esquema de cores para notas de código",
"ui": "Interface do usuário",
"ui_old_layout": "Layout antigo",
"ui_new_layout": "Novo Layout"
},
"units": {
"percentage": "%"
@@ -2047,5 +2123,102 @@
},
"collections": {
"rendering_error": "Não foi possível exibir o conteúdo devido a um erro."
},
"experimental_features": {
"title": "Opções experimentais",
"disclaimer": "Essas opções são experimentais e podem causar instabilidade. Use com cautela.",
"new_layout_name": "Novo Layout",
"new_layout_description": "Experimente o novo layout para um visual mais moderno e melhor usabilidade. Pode sofrer alterações significativas nas próximas versões."
},
"read-only-info": {
"read-only-note": "Você está visualizando uma nota somente leitura.",
"auto-read-only-note": "Esta nota é exibida em modo somente leitura para carregamento mais rápido.",
"edit-note": "Editar nota"
},
"presentation_view": {
"edit-slide": "Editar este slide",
"start-presentation": "Iniciar apresentação",
"slide-overview": "Alternar a visualização geral dos slides"
},
"calendar_view": {
"delete_note": "Excluir nota…"
},
"note-color": {
"clear-color": "Limpar cor da nota",
"set-color": "Definir cor da nota",
"set-custom-color": "Definir cor personalizada da nota"
},
"popup-editor": {
"maximize": "Alternar para editor completo"
},
"server": {
"unknown_http_error_title": "Erro de comunicação com o servidor",
"unknown_http_error_content": "Código de status: {{statusCode}}\nURL: {{method}} {{url}}\nMensagem: {{message}}",
"traefik_blocks_requests": "Se você estiver usando o proxy reverso Traefik, ele introduziu uma alteração que afeta a comunicação com o servidor."
},
"tab_history_navigation_buttons": {
"go-back": "Voltar para a nota anterior",
"go-forward": "Avançar para a próxima nota"
},
"breadcrumb": {
"hoisted_badge": "Destacado",
"hoisted_badge_title": "Remover destaque",
"workspace_badge": "Espaço de trabalho",
"scroll_to_top_title": "Ir para o início da nota",
"create_new_note": "Criar nova nota filha",
"empty_hide_archived_notes": "Ocultar notas arquivadas"
},
"breadcrumb_badges": {
"read_only_explicit": "Somente leitura",
"read_only_explicit_description": "Esta nota foi definida manualmente como somente leitura.\nClique para editá-la temporariamente.",
"read_only_auto": "Auto Somente leitura",
"read_only_auto_description": "Esta nota foi definida automaticamente como somente leitura por motivos de desempenho. Esse limite automático pode ser ajustado nas configurações.\n\nClique para editá-la temporariamente.",
"read_only_temporarily_disabled": "Editável temporariamente",
"read_only_temporarily_disabled_description": "Esta nota está atualmente editável, mas normalmente é somente leitura. A nota voltará a ser somente leitura assim que você navegar para outra nota.\n\nClique para reativar o modo somente leitura.",
"shared_publicly": "Compartilhado publicamente",
"shared_locally": "Compartilhado localmente",
"shared_copy_to_clipboard": "Copiar link para a área de transferência",
"shared_open_in_browser": "Abrir link no navegador",
"shared_unshare": "Remover compartilhamento",
"clipped_note": "Recorte da web",
"clipped_note_description": "Esta nota foi originalmente obtida de {{url}}.\n\nClique para navegar até a página de origem.",
"execute_script": "Executar script",
"execute_script_description": "Esta nota é uma nota de script. Clique para executar o script.",
"execute_sql": "Executar SQL",
"execute_sql_description": "Esta nota é uma nota SQL. Clique para executar a consulta SQL."
},
"status_bar": {
"language_title": "Alterar idioma do conteúdo",
"note_info_title": "Ver informações da nota (por exemplo, datas, tamanho da nota)",
"backlinks_one": "{{count}} referência inversa",
"backlinks_many": "{{count}} referências inversas",
"backlinks_other": "{{count}} referências inversas",
"backlinks_title_one": "Ver referência inversa",
"backlinks_title_many": "Ver referências inversas",
"backlinks_title_other": "Ver referências inversas",
"attachments_one": "{{count}} anexo",
"attachments_many": "{{count}} anexos",
"attachments_other": "{{count}} anexos",
"attachments_title_one": "Visualizar anexo em uma nova aba",
"attachments_title_many": "Visualizar anexos em uma nova aba",
"attachments_title_other": "Visualizar anexos em uma nova aba",
"attributes_one": "{{count}} atributo",
"attributes_many": "{{count}} atributos",
"attributes_other": "{{count}} atributos",
"attributes_title": "Atributos próprios e atributos herdados",
"note_paths_one": "{{count}} caminho",
"note_paths_many": "{{count}} caminhos",
"note_paths_other": "{{count}} caminhos",
"note_paths_title": "Caminhos da nota",
"code_note_switcher": "Alterar modo de idioma"
},
"attributes_panel": {
"title": "Atributos da nota"
},
"right_pane": {
"empty_message": "Nada para exibir nesta nota",
"empty_button": "Ocultar o painel",
"toggle": "Alternar painel direito",
"custom_widget_go_to_source": "Ir para o código-fonte"
}
}

View File

@@ -483,7 +483,7 @@
"workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если будет установлен фокус на рабочую область с этим шаблоном",
"workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки, когда установлен фокус на какую-либо родительскую заметку этого рабочего пространство",
"workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства",
"hide_highlight_widget": "Скрыть виджет «Выделенное»",
"hide_highlight_widget": "Скрыть виджет «Акценты»",
"is_owned_by_note": "принадлежит заметке",
"and_more": "... и ещё {{count}}.",
"app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium.",
@@ -750,7 +750,8 @@
},
"toc": {
"table_of_contents": "Оглавление",
"options": "Параметры"
"options": "Параметры",
"no_headings": "Заголовки не найдены."
},
"note_tree": {
"hide-archived-notes": "Скрыть архивные заметки",
@@ -1562,7 +1563,13 @@
},
"highlights_list_2": {
"options": "Параметры",
"title": "Список выделенного"
"title": "Акценты",
"modal_title": "Настроить акценты",
"menu_configure": "Настроить акценты...",
"no_highlights": "Акценты в тексте не найдены.",
"title_with_count_one": "{{count}} акцент",
"title_with_count_few": "{{count}} акцента",
"title_with_count_many": "{{count}} акцентов"
},
"include_note": {
"dialog_title": "Вставить заметку",
@@ -1679,7 +1686,8 @@
},
"inherited_attribute_list": {
"title": "Унаследованные атрибуты",
"no_inherited_attributes": "Нет унаследованных атрибутов."
"no_inherited_attributes": "Нет унаследованных атрибутов.",
"none": "нет"
},
"note_map": {
"title": "Карта заметок",
@@ -1755,15 +1763,15 @@
"enable_tray": "Включить отображение иконки в системном трее (чтобы изменения вступили в силу, необходимо перезапустить Trilium)"
},
"highlights_list": {
"title": "Список выделенного",
"title": "Акценты",
"bold": "Жирный текст",
"italic": "Наклонный текст",
"underline": "Подчеркнутый текст",
"color": "Цветной текст",
"description": "Вы можете настроить список выделенного, отображаемый на правой панели:",
"description": "Вы можете настроить список акцентов, отображаемый на правой панели:",
"bg_color": "Текст с заливкой фона",
"visibility_title": "Видимость списка выделений",
"visibility_description": "Вы можете скрыть виджет списка выделенного, добавив атрибут #hideHighlightWidget к заметке.",
"visibility_title": "Видимость списка акцентов",
"visibility_description": "Вы можете скрыть виджет списка акцентов, добавив атрибут #hideHighlightWidget к заметке.",
"shortcut_info": "Вы можете настроить сочетание клавиш для быстрого переключения правой панели (включая список выделенного) в меню Параметры -> Сочетания клавиш (название \"toggleRightPane\")."
},
"custom_date_time_format": {
@@ -1808,7 +1816,7 @@
"edit_this_note": "Редактировать заметку"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Показать список выделенного"
"show_highlights_list": "Показать список акцентов"
},
"zen_mode": {
"button_exit": "Покинуть режим \"дзен\""
@@ -2203,5 +2211,14 @@
},
"popup-editor": {
"maximize": "Переключить на полный редактор"
},
"right_pane": {
"custom_widget_go_to_source": "Исходный код",
"toggle": "Переключить панель справа",
"empty_button": "Скрыть панель",
"empty_message": "Нечего отобразить для текущей заметки"
},
"attributes_panel": {
"title": "Атрибуты заметки"
}
}

View File

@@ -691,7 +691,12 @@
"convert_into_attachment_prompt": "確定要將筆記 '{{title}}' 轉換為父級筆記的附件嗎?",
"print_pdf": "匯出為 PDF…",
"open_note_on_server": "在伺服器上開啟筆記",
"view_revisions": "筆記歷史版本..."
"view_revisions": "筆記歷史版本...",
"advanced": "進階",
"export_as_image": "匯出為圖片",
"export_as_image_png": "PNG (點陣)",
"export_as_image_svg": "SVG (向量)",
"note_map": "筆記地圖"
},
"onclick_button": {
"no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式"
@@ -790,7 +795,7 @@
"file_type": "檔案類型",
"file_size": "檔案大小",
"download": "下載",
"open": "打開",
"open": "以外部程式打開",
"upload_new_revision": "上傳新版本",
"upload_success": "已上傳新檔案版本。",
"upload_failed": "新檔案版本上傳失敗。",
@@ -821,7 +826,9 @@
"note_size_info": "筆記大小提供了該筆記儲存需求的粗略估計。它考慮了筆記及其歷史的內容。",
"calculate": "計算",
"subtree_size": "(子階層大小: {{size}}, 共計 {{count}} 個筆記)",
"title": "筆記資訊"
"title": "筆記資訊",
"mime": "MIME 類型",
"show_similar_notes": "顯示相似筆記"
},
"note_map": {
"open_full": "展開顯示",
@@ -884,7 +891,8 @@
"search_parameters": "搜尋參數",
"unknown_search_option": "未知的搜尋選項 {{searchOptionName}}",
"search_note_saved": "搜尋筆記已儲存至 {{- notePathTitle}}",
"actions_executed": "已執行操作。"
"actions_executed": "已執行操作。",
"view_options": "查看選項:"
},
"similar_notes": {
"title": "相似筆記",
@@ -1503,7 +1511,11 @@
},
"highlights_list_2": {
"title": "高亮列表",
"options": "選項"
"options": "選項",
"title_with_count_one": "{{count}} 處高亮",
"modal_title": "設定高亮列表",
"menu_configure": "設定高亮列表…",
"no_highlights": "未找到高亮內容。"
},
"quick-search": {
"placeholder": "快速搜尋",
@@ -1539,8 +1551,13 @@
},
"note_title": {
"placeholder": "請輸入筆記標題...",
"created_on": "建立於 {{date}}",
"last_modified": "最後修改於 {{date}}"
"created_on": "建立於 <Value />",
"last_modified": "修改於 <Value />",
"note_type_switcher_label": "從 {{type}} 切換至:",
"note_type_switcher_others": "其他筆記類型",
"note_type_switcher_templates": "模板",
"note_type_switcher_collection": "集合",
"edited_notes": "編輯過的筆記"
},
"search_result": {
"no_notes_found": "沒有找到符合搜尋條件的筆記。",
@@ -1569,7 +1586,8 @@
},
"toc": {
"table_of_contents": "目錄",
"options": "選項"
"options": "選項",
"no_headings": "無標題。"
},
"watched_file_update_status": {
"file_last_modified": "檔案 <code class=\"file-path\"></code> 最後修改時間為 <span class=\"file-last-modified\"></span>。",
@@ -1943,8 +1961,9 @@
"unknown_widget": "未知元件:\"{{id}}\"。"
},
"note_language": {
"not_set": "設定",
"configure-languages": "設定語言…"
"not_set": "設定語言",
"configure-languages": "設定語言…",
"help-on-languages": "設定內容語言說明…"
},
"content_language": {
"title": "內文語言",
@@ -2011,7 +2030,7 @@
"book_properties_config": {
"hide-weekends": "隱藏週末",
"display-week-numbers": "顯示週數",
"map-style": "地圖樣式",
"map-style": "地圖樣式",
"max-nesting-depth": "最大嵌套深度:",
"raster": "柵格",
"vector_light": "向量(淺色)",
@@ -2068,14 +2087,19 @@
"next_theme_title": "試用新 Trilium 主題",
"next_theme_message": "您正在使用舊版主題,要試用新主題嗎?",
"next_theme_button": "試用新主題",
"dismiss": "關閉"
"dismiss": "關閉",
"new_layout_title": "新版面配置",
"new_layout_button": "更多資訊"
},
"settings": {
"related_settings": "相關設定"
},
"settings_appearance": {
"related_code_blocks": "文字筆記中程式碼區塊的配色方案",
"related_code_notes": "程式碼筆記的配色方案"
"related_code_notes": "程式碼筆記的配色方案",
"ui": "使用者介面",
"ui_old_layout": "舊版面配置",
"ui_new_layout": "新版面配置"
},
"units": {
"percentage": "%"
@@ -2135,6 +2159,40 @@
"read_only_explicit": "唯讀",
"read_only_auto": "自動唯讀",
"shared_publicly": "公開分享",
"shared_locally": "本地分享"
"shared_locally": "本地分享",
"read_only_explicit_description": "此筆記已被手動設定為唯讀。\n點擊以臨時編輯。",
"read_only_temporarily_disabled": "臨時編輯",
"shared_copy_to_clipboard": "複製連結至剪貼簿",
"shared_open_in_browser": "在瀏覽器中打開連結",
"shared_unshare": "取消分享",
"clipped_note": "網頁擷取",
"execute_script": "運行腳本",
"execute_sql": "運行 SQL"
},
"breadcrumb": {
"hoisted_badge": "聚焦",
"hoisted_badge_title": "取消聚焦",
"workspace_badge": "工作空間",
"scroll_to_top_title": "跳轉至筆記開頭",
"create_new_note": "新增子筆記",
"empty_hide_archived_notes": "隱藏已歸檔的筆記"
},
"status_bar": {
"language_title": "更改內容語言",
"note_info_title": "查看筆記資訊(如日期、筆記大小)",
"backlinks_one": "{{count}} 個反連結",
"backlinks_title_one": "查看反連結",
"attachments_one": "{{count}} 個附件",
"attachments_title_one": "在新分頁中查看附件",
"attributes_one": "{{count}} 個屬性",
"attributes_title": "自有屬性及繼承屬性",
"note_paths_one": "{{count}} 條路徑",
"note_paths_title": "筆記路徑",
"code_note_switcher": "更改語言模式"
},
"right_pane": {
"empty_button": "隱藏面板",
"toggle": "切換右側面板",
"custom_widget_go_to_source": "跳轉至原始碼"
}
}

View File

@@ -15,7 +15,7 @@ export default function TabHistoryNavigationButtons() {
const legacyBackVisible = useLauncherVisibility("_lbBackInHistory");
const legacyForwardVisible = useLauncherVisibility("_lbForwardInHistory");
return (isElectron() &&
return (
<div className="tab-history-navigation-buttons">
{!legacyBackVisible && <ActionButton
icon="bx bx-left-arrow-alt"

View File

@@ -54,6 +54,16 @@
display: flex;
gap: 1em;
justify-content: space-between;
.btn {
color: var(--bs-toast-color);
background: var(--modal-control-button-background);
&:hover {
background: var(--modal-control-button-hover-background);
color: var(--bs-toast-color);
}
}
}
.toast-progress {

View File

@@ -1,8 +1,9 @@
import { isValidElement, VNode } from "preact";
import Component, { TypedComponent } from "../components/component.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import toastService from "../services/toast.js";
import toastService, { showErrorForScriptNote } from "../services/toast.js";
import { renderReactWidget } from "./react/react_utils.jsx";
export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedComponent<T> {
@@ -56,9 +57,8 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
optChild(condition: boolean, ...components: (T | VNode)[]) {
if (condition) {
return this.child(...components);
} else {
return this;
}
return this;
}
id(id: string) {
@@ -172,16 +172,11 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
const noteId = this._noteId;
if (this._noteId) {
froca.getNote(noteId, true).then((note) => {
toastService.showPersistent({
id: `custom-widget-failure-${noteId}`,
title: t("toast.widget-error.title"),
icon: "bx bx-error-circle",
message: t("toast.widget-error.message-custom", {
id: noteId,
title: note?.title,
message: e.message || e.toString()
})
});
showErrorForScriptNote(noteId, t("toast.widget-error.message-custom", {
id: noteId,
title: note?.title,
message: e.message || e.toString()
}));
});
} else {
toastService.showPersistent({
@@ -213,7 +208,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
toggleInt(show: boolean | null | undefined) {
this.$widget.toggleClass("hidden-int", !show)
.toggleClass("visible", !!show);
.toggleClass("visible", !!show);
}
isHiddenInt() {
@@ -222,7 +217,7 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
toggleExt(show: boolean | null | "" | undefined) {
this.$widget.toggleClass("hidden-ext", !show)
.toggleClass("visible", !!show);
.toggleClass("visible", !!show);
}
isHiddenExt() {
@@ -250,9 +245,8 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
getClosestNtxId() {
if (this.$widget) {
return this.$widget.closest("[data-ntx-id]").attr("data-ntx-id");
} else {
return null;
}
return null;
}
cleanup() {}

View File

@@ -1,9 +1,9 @@
import FlexContainer from "./flex_container.js";
import splitService from "../../services/resizer.js";
import type RightPanelWidget from "../right_panel_widget.js";
import type { EventData, EventNames } from "../../components/app_context.js";
import splitService from "../../services/resizer.js";
import type BasicWidget from "../basic_widget.js";
import FlexContainer from "./flex_container.js";
export default class RightPaneContainer extends FlexContainer<RightPanelWidget> {
export default class RightPaneContainer extends FlexContainer<BasicWidget> {
private rightPaneHidden: boolean;
private firstRender: boolean;

View File

@@ -4,7 +4,14 @@
position: relative;
}
.note-split.type-code:not(.mime-text-x-sqlite) > .scrolling-container {
background-color: var(--code-background-color);
--scrollbar-background-color: var(--main-background-color);
.note-split.type-code:not(.mime-text-x-sqlite) {
&> .scrolling-container {
background-color: var(--code-background-color);
--scrollbar-background-color: var(--main-background-color);
}
.inline-title,
.title-actions {
background-color: var(--main-background-color);
}
}

View File

@@ -1,17 +1,20 @@
import { useCallback, useContext, useEffect, useMemo, useState } from "preact/hooks";
import appContext, { CommandNames } from "../../components/app_context";
import FNote from "../../entities/fnote";
import date_notes from "../../services/date_notes";
import dialog from "../../services/dialog";
import { LauncherWidgetDefinitionWithType } from "../../services/frontend_script_api_preact";
import { t } from "../../services/i18n";
import toast from "../../services/toast";
import { getErrorMessage, isMobile } from "../../services/utils";
import BasicWidget from "../basic_widget";
import NoteContextAwareWidget from "../note_context_aware_widget";
import QuickSearchWidget from "../quick_search";
import { useGlobalShortcut, useLegacyWidget, useNoteLabel, useNoteRelationTarget, useTriliumOptionBool } from "../react/hooks";
import { ParentComponent } from "../react/react_utils";
import BasicWidget from "../basic_widget";
import FNote from "../../entities/fnote";
import QuickSearchWidget from "../quick_search";
import { getErrorMessage, isMobile } from "../../services/utils";
import date_notes from "../../services/date_notes";
import { CustomNoteLauncher } from "./GenericButtons";
import { LaunchBarActionButton, LaunchBarContext, LauncherNoteProps, useLauncherIconAndTitle } from "./launch_bar_widgets";
import dialog from "../../services/dialog";
import { t } from "../../services/i18n";
import appContext, { CommandNames } from "../../components/app_context";
import toast from "../../services/toast";
export function CommandButton({ launcherNote }: LauncherNoteProps) {
const { icon, title } = useLauncherIconAndTitle(launcherNote);
@@ -23,7 +26,7 @@ export function CommandButton({ launcherNote }: LauncherNoteProps) {
text={title}
triggerCommand={command as CommandNames}
/>
)
);
}
// we're intentionally displaying the launcher title and icon instead of the target,
@@ -75,7 +78,7 @@ export function ScriptLauncher({ launcherNote }: LauncherNoteProps) {
text={title}
onClick={launch}
/>
)
);
}
export function AiChatButton({ launcherNote }: LauncherNoteProps) {
@@ -88,7 +91,7 @@ export function AiChatButton({ launcherNote }: LauncherNoteProps) {
text={title}
triggerCommand="createAiChat"
/>
)
);
}
export function TodayLauncher({ launcherNote }: LauncherNoteProps) {
@@ -114,12 +117,13 @@ export function QuickSearchLauncherWidget() {
<div>
{isEnabled && <LegacyWidgetRenderer widget={widget} />}
</div>
)
);
}
export function CustomWidget({ launcherNote }: LauncherNoteProps) {
const [ widgetNote ] = useNoteRelationTarget(launcherNote, "widget");
const [ widget, setWidget ] = useState<BasicWidget>();
const [ widget, setWidget ] = useState<BasicWidget | NoteContextAwareWidget | LauncherWidgetDefinitionWithType>();
const parentComponent = useContext(ParentComponent) as BasicWidget | null;
parentComponent?.contentSized();
@@ -146,9 +150,13 @@ export function CustomWidget({ launcherNote }: LauncherNoteProps) {
return (
<div>
{widget && <LegacyWidgetRenderer widget={widget} />}
{widget && (
("type" in widget && widget.type === "preact-launcher-widget")
? <ReactWidgetRenderer widget={widget as LauncherWidgetDefinitionWithType} />
: <LegacyWidgetRenderer widget={widget as BasicWidget} />
)}
</div>
)
);
}
export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
@@ -158,3 +166,8 @@ export function LegacyWidgetRenderer({ widget }: { widget: BasicWidget }) {
return widgetEl;
}
function ReactWidgetRenderer({ widget }: { widget: LauncherWidgetDefinitionWithType }) {
const El = widget.render;
return <El />;
}

View File

@@ -16,6 +16,8 @@
}
a.tn-link {
--link-hover-background: var(--icon-button-hover-background);
color: var(--custom-color, inherit);
&:hover {
@@ -46,6 +48,19 @@
overflow: hidden;
display: block;
flex-shrink: 2;
font-weight: normal;
}
}
.icon-action {
font-size: .9rem !important;
.bxs-chevron-right {
transform: translateY(8%);
&::before {
opacity: .75;
}
}
}
@@ -72,6 +87,15 @@
color: var(--custom-color, inherit) !important;
}
.dropdown .breadcrumb-child-list {
/* Icon */
li > span:first-child {
opacity: .75;
padding-inline-end: 4px;
translate: none;
};
}
a.breadcrumb-last-item,
a.breadcrumb-last-item:visited {
text-decoration: none;

View File

@@ -32,7 +32,7 @@ import { ParentComponent } from "../react/react_utils";
const COLLAPSE_THRESHOLD = 5;
const INITIAL_ITEMS = 2;
const FINAL_ITEMS = 2;
const FINAL_ITEMS = 3;
export default function Breadcrumb() {
const { note, notePath, notePaths, noteContext } = useNotePaths();
@@ -65,8 +65,7 @@ export default function Breadcrumb() {
? <BreadcrumbRoot noteContext={noteContext} />
: <BreadcrumbItem index={index} notePath={item} notePathLength={notePaths.length} noteContext={noteContext} parentComponent={parentComponent} />
}
{(index < notePaths.length - 1 || note?.hasChildren()) &&
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />}
<BreadcrumbSeparator notePath={item} activeNotePath={notePaths[index + 1]} {...separatorProps} />
</Fragment>
))
)}
@@ -189,10 +188,11 @@ interface BreadcrumbSeparatorProps {
function BreadcrumbSeparator(props: BreadcrumbSeparatorProps) {
return (
<Dropdown
text={<Icon icon="bx bx-chevron-right" />}
text={<Icon icon="bx bxs-chevron-right" />}
noSelectButtonStyle
buttonClassName="icon-action"
hideToggleArrow
dropdownContainerClassName="tn-dropdown-menu-scrollable"
dropdownOptions={{ popperConfig: { strategy: "fixed", placement: "top" } }}
>
<BreadcrumbSeparatorDropdownContent {...props} />
@@ -225,7 +225,7 @@ function BreadcrumbSeparatorDropdownContent({ notePath, noteContext, activeNoteP
</li>;
})}
<FormDropdownDivider />
{childNotes.length > 0 && <FormDropdownDivider />}
<FormListItem
icon="bx bx-plus"
onClick={() => note_create.createNote(notePath, { activate: true })}

View File

@@ -75,10 +75,6 @@
}
}
.note-split.type-code:not(.mime-text-x-sqlite) .inline-title {
background-color: var(--main-background-color);
}
body.prefers-centered-content .inline-title {
margin-inline: auto;
}
@@ -99,58 +95,3 @@ body.prefers-centered-content .inline-title {
font-weight: 500;
}
}
@keyframes note-type-switcher-intro {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.note-type-switcher {
--badge-radius: 12px;
position: relative;
top: 5px;
padding: .25em 0;
display: flex;
align-items: center;
overflow-x: auto;
min-width: 0;
gap: 5px;
min-height: 35px;
>* {
flex-shrink: 0;
animation: note-type-switcher-intro 200ms ease-in;
}
.ext-badge {
--color: var(--input-background-color);
color: var(--main-text-color);
font-size: 0.9rem;
flex-shrink: 0;
}
}
.edited-notes {
padding: 1.5em 0;
.collapsible-inner-body {
display: flex;
flex-wrap: wrap;
gap: 0.3em;
.badge {
margin: 0;
a.tn-link {
color: inherit;
text-transform: none;
text-decoration: none;
display: inline-block;
}
}
}
}

View File

@@ -3,28 +3,16 @@ import "./InlineTitle.css";
import { NoteType } from "@triliumnext/commons";
import clsx from "clsx";
import { ComponentChild } from "preact";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { useLayoutEffect, useRef, useState } from "preact/hooks";
import { Trans } from "react-i18next";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { ViewScope } from "../../services/link";
import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types";
import server from "../../services/server";
import { formatDateTime } from "../../utils/formatters";
import NoteIcon from "../note_icon";
import NoteTitleWidget from "../note_title";
import SimpleBadge, { Badge, BadgeWithDropdown } from "../react/Badge";
import Collapsible from "../react/Collapsible";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useNoteBlob, useNoteContext, useNoteLabel, useNoteProperty, useStaticTooltip, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
import NoteLink from "../react/NoteLink";
import { useNoteContext, useNoteProperty, useStaticTooltip } from "../react/hooks";
import { joinElements } from "../react/react_utils";
import { useEditedNotes } from "../ribbon/EditedNotesTab";
import { useNoteMetadata } from "../ribbon/NoteInfoTab";
import { onWheelHorizontalScroll } from "../widget_utils";
const supportedNoteTypes = new Set<NoteType>([
"text", "code"
@@ -76,9 +64,6 @@ export default function InlineTitle() {
<NoteTitleDetails />
</div>
</div>
<EditedNotes />
<NoteTypeSwitcher />
</div>
);
}
@@ -142,205 +127,4 @@ function TextWithValue({ i18nKey, value, valueTooltip }: {
}
//#endregion
//#region Note type switcher
const SWITCHER_PINNED_NOTE_TYPES = new Set<NoteType>([ "text", "code", "book", "canvas" ]);
function NoteTypeSwitcher() {
const { note } = useNoteContext();
const blob = useNoteBlob(note);
const currentNoteType = useNoteProperty(note, "type");
const { pinnedNoteTypes, restNoteTypes } = useMemo(() => {
const pinnedNoteTypes: NoteTypeMapping[] = [];
const restNoteTypes: NoteTypeMapping[] = [];
for (const noteType of NOTE_TYPES) {
if (noteType.reserved || noteType.static || noteType.type === "book") continue;
if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) {
pinnedNoteTypes.push(noteType);
} else {
restNoteTypes.push(noteType);
}
}
return { pinnedNoteTypes, restNoteTypes };
}, []);
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
<div
className="note-type-switcher"
onWheel={onWheelHorizontalScroll}
>
{note && blob?.contentLength === 0 && (
<>
<div className="intro">{t("note_title.note_type_switcher_label", { type: currentNoteTypeData?.title.toLocaleLowerCase() })}</div>
{pinnedNoteTypes.map(noteType => noteType.type !== currentNoteType && (
<Badge
key={noteType.type}
text={noteType.title}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(note.noteId, noteType)}
/>
))}
{collectionTemplates.length > 0 && <CollectionNoteTypes noteId={note.noteId} collectionTemplates={collectionTemplates} />}
{builtinTemplates.length > 0 && <TemplateNoteTypes noteId={note.noteId} builtinTemplates={builtinTemplates} />}
{restNoteTypes.length > 0 && <MoreNoteTypes noteId={note.noteId} restNoteTypes={restNoteTypes} />}
</>
)}
</div>
);
}
function MoreNoteTypes({ noteId, restNoteTypes }: { noteId: string, restNoteTypes: NoteTypeMapping[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_others")}
icon="bx bx-dots-vertical-rounded"
>
{restNoteTypes.map(noteType => (
<FormListItem
key={noteType.type}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(noteId, noteType)}
>{noteType.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function CollectionNoteTypes({ noteId, collectionTemplates }: { noteId: string, collectionTemplates: FNote[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_collection")}
icon="bx bx-book"
>
{collectionTemplates.map(collectionTemplate => (
<FormListItem
key={collectionTemplate.noteId}
icon={collectionTemplate.getIcon()}
onClick={() => setTemplate(noteId, collectionTemplate.noteId)}
>{collectionTemplate.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function TemplateNoteTypes({ noteId, builtinTemplates }: { noteId: string, builtinTemplates: FNote[] }) {
const [ userTemplates, setUserTemplates ] = useState<FNote[]>([]);
async function refreshTemplates() {
const templateNoteIds = await server.get<string[]>("search-templates");
const templateNotes = await froca.getNotes(templateNoteIds);
setUserTemplates(templateNotes);
}
// First load.
useEffect(() => {
refreshTemplates();
}, []);
// React to external changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(attr => attr.type === "label" && attr.name === "template")) {
refreshTemplates();
}
});
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_templates")}
icon="bx bx-copy-alt"
>
{userTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
{userTemplates.length > 0 && <FormDropdownDivider />}
{builtinTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
</BadgeWithDropdown>
);
}
function TemplateItem({ noteId, template }: { noteId: string, template: FNote }) {
return (
<FormListItem
icon={template.getIcon()}
onClick={() => setTemplate(noteId, template.noteId)}
>{template.title}</FormListItem>
);
}
function switchNoteType(noteId: string, { type, mime }: NoteTypeMapping) {
return server.put(`notes/${noteId}/type`, { type, mime });
}
function setTemplate(noteId: string, templateId: string) {
return attributes.setRelation(noteId, "template", templateId);
}
function useBuiltinTemplates() {
const [ templates, setTemplates ] = useState<{
builtinTemplates: FNote[];
collectionTemplates: FNote[];
}>({
builtinTemplates: [],
collectionTemplates: []
});
async function loadBuiltinTemplates() {
const templatesRoot = await froca.getNote("_templates");
if (!templatesRoot) return;
const childNotes = await templatesRoot.getChildNotes();
const builtinTemplates: FNote[] = [];
const collectionTemplates: FNote[] = [];
for (const childNote of childNotes) {
if (!childNote.hasLabel("template")) continue;
if (childNote.hasLabel("collection")) {
collectionTemplates.push(childNote);
} else {
builtinTemplates.push(childNote);
}
}
setTemplates({ builtinTemplates, collectionTemplates });
}
useEffect(() => {
loadBuiltinTemplates();
}, []);
return templates;
}
//#endregion
//#region Edited Notes
function EditedNotes() {
const { note } = useNoteContext();
const [ dateNote ] = useNoteLabel(note, "dateNote");
const [ editedNotesOpenInRibbon ] = useTriliumOptionBool("editedNotesOpenInRibbon");
return (note && dateNote &&
<Collapsible
className="edited-notes"
title={t("note_title.edited_notes")}
initiallyExpanded={editedNotesOpenInRibbon}
>
<EditedNotesContent note={note} />
</Collapsible>
);
}
function EditedNotesContent({ note }: { note: FNote }) {
const editedNotes = useEditedNotes(note);
return (editedNotes !== undefined &&
(editedNotes.length > 0 ? editedNotes?.map(editedNote => (
<SimpleBadge
key={editedNote.noteId}
title={(
<NoteLink
notePath={editedNote.noteId}
showNoteIcon
/>
)}
/>
)) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
)));
}
//#endregion

View File

@@ -16,6 +16,13 @@
&.share-badge {--color: var(--badge-share-background-color);}
&.clipped-note-badge {--color: var(--badge-clipped-note-background-color);}
&.execute-badge {--color: var(--badge-execute-background-color);}
min-width: 0;
.text {
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
}
.dropdown-badge {

View File

@@ -4,8 +4,41 @@ body.experimental-feature-new-layout {
}
.title-actions {
&.visible:not(:empty) {
display: flex;
flex-direction: column;
gap: 0.5em;
&:not(:empty) {
padding: 0.75em 15px;
}
.edited-notes {
.collapsible-inner-body {
display: flex;
flex-wrap: wrap;
gap: 0.3em;
.badge {
margin: 0;
color: inherit;
text-transform: none;
text-decoration: none;
display: inline-block;
transition: background-color 250ms ease-in, color 250ms ease-in;
&:hover {
background-color: var(--link-hover-background);
color: var(--link-hover-color);
}
}
}
}
.promoted-attributes-widget {
.promoted-attributes-container {
margin: 0;
padding: 0;
}
}
}
}

View File

@@ -1,7 +1,7 @@
import "./NoteTitleActions.css";
import clsx from "clsx";
import { useEffect, useRef, useState } from "preact/hooks";
import { useEffect, useState } from "preact/hooks";
import NoteContext from "../../components/note_context";
import FNote from "../../entities/fnote";
@@ -9,29 +9,31 @@ import { t } from "../../services/i18n";
import CollectionProperties from "../note_bars/CollectionProperties";
import { checkFullHeight, getExtendedWidgetType } from "../NoteDetail";
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
import SimpleBadge from "../react/Badge";
import Collapsible, { ExternallyControlledCollapsible } from "../react/Collapsible";
import { useNoteContext, useNoteProperty, useTriliumEvent } from "../react/hooks";
import { useNoteContext, useNoteLabel, useNoteProperty, useTriliumEvent, useTriliumOptionBool } from "../react/hooks";
import NoteLink, { NewNoteLink } from "../react/NoteLink";
import { useEditedNotes } from "../ribbon/EditedNotesTab";
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
import NoteTypeSwitcher from "./NoteTypeSwitcher";
export default function NoteTitleActions() {
const { note, ntxId, componentId, noteContext } = useNoteContext();
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
const noteType = useNoteProperty(note, "type");
const items = [
note && <PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />,
note && noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />,
note && !isHiddenNote && noteType === "book" && <CollectionProperties note={note} />
].filter(Boolean);
return (
<div className={clsx("title-actions", items.length > 0 && "visible")}>
{items}
<div className="title-actions">
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
{!isHiddenNote && note && noteType === "book" && <CollectionProperties note={note} />}
<EditedNotes />
<NoteTypeSwitcher />
</div>
);
}
function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null | undefined }) {
function SearchProperties({ note, ntxId }: { note: FNote | null | undefined, ntxId: string | null | undefined }) {
return (note &&
<Collapsible
title={t("search_definition.search_parameters")}
@@ -64,10 +66,43 @@ function PromotedAttributes({ note, componentId, noteContext }: {
return (note && (
<ExternallyControlledCollapsible
key={note.noteId}
title={t("promoted_attributes.promoted_attributes")}
title={t("note_title.promoted_attributes")}
expanded={expanded} setExpanded={setExpanded}
>
<PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />
</ExternallyControlledCollapsible>
));
}
//#region Edited Notes
function EditedNotes() {
const { note } = useNoteContext();
const [ dateNote ] = useNoteLabel(note, "dateNote");
const [ editedNotesOpenInRibbon ] = useTriliumOptionBool("editedNotesOpenInRibbon");
return (note && dateNote &&
<Collapsible
className="edited-notes"
title={t("note_title.edited_notes")}
initiallyExpanded={editedNotesOpenInRibbon}
>
<EditedNotesContent note={note} />
</Collapsible>
);
}
function EditedNotesContent({ note }: { note: FNote }) {
const editedNotes = useEditedNotes(note);
return (editedNotes !== undefined &&
(editedNotes.length > 0 ? editedNotes?.map(editedNote => (
<NewNoteLink
className="badge"
notePath={editedNote.noteId}
showNoteIcon
/>
)) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
)));
}
//#endregion

View File

@@ -0,0 +1,33 @@
@keyframes note-type-switcher-intro {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.note-type-switcher {
--badge-radius: 12px;
position: relative;
top: 5px;
padding: .25em 0;
display: flex;
align-items: center;
overflow-x: auto;
min-width: 0;
gap: 5px;
min-height: 35px;
>* {
flex-shrink: 0;
animation: note-type-switcher-intro 200ms ease-in;
}
.ext-badge {
--color: var(--input-background-color);
color: var(--main-text-color);
font-size: 0.9rem;
flex-shrink: 0;
}
}

View File

@@ -0,0 +1,182 @@
import "./NoteTypeSwitcher.css";
import { NoteType } from "@triliumnext/commons";
import { useEffect, useMemo, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { NOTE_TYPES, NoteTypeMapping } from "../../services/note_types";
import server from "../../services/server";
import { Badge, BadgeWithDropdown } from "../react/Badge";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useNoteBlob, useNoteContext, useNoteProperty, useTriliumEvent } from "../react/hooks";
import { onWheelHorizontalScroll } from "../widget_utils";
const SWITCHER_PINNED_NOTE_TYPES = new Set<NoteType>([ "text", "code", "book", "canvas" ]);
const supportedNoteTypes = new Set<NoteType>([
"text", "code"
]);
export default function NoteTypeSwitcher() {
const { note } = useNoteContext();
const blob = useNoteBlob(note);
const currentNoteType = useNoteProperty(note, "type");
const { pinnedNoteTypes, restNoteTypes } = useMemo(() => {
const pinnedNoteTypes: NoteTypeMapping[] = [];
const restNoteTypes: NoteTypeMapping[] = [];
for (const noteType of NOTE_TYPES) {
if (noteType.reserved || noteType.static || noteType.type === "book") continue;
if (SWITCHER_PINNED_NOTE_TYPES.has(noteType.type)) {
pinnedNoteTypes.push(noteType);
} else {
restNoteTypes.push(noteType);
}
}
return { pinnedNoteTypes, restNoteTypes };
}, []);
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
<div
className="note-type-switcher"
onWheel={onWheelHorizontalScroll}
>
{note && blob?.contentLength === 0 && (
<>
<div className="intro">{t("note_title.note_type_switcher_label", { type: currentNoteTypeData?.title.toLocaleLowerCase() })}</div>
{pinnedNoteTypes.map(noteType => noteType.type !== currentNoteType && (
<Badge
key={noteType.type}
text={noteType.title}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(note.noteId, noteType)}
/>
))}
{collectionTemplates.length > 0 && <CollectionNoteTypes noteId={note.noteId} collectionTemplates={collectionTemplates} />}
{builtinTemplates.length > 0 && <TemplateNoteTypes noteId={note.noteId} builtinTemplates={builtinTemplates} />}
{restNoteTypes.length > 0 && <MoreNoteTypes noteId={note.noteId} restNoteTypes={restNoteTypes} />}
</>
)}
</div>
);
}
function MoreNoteTypes({ noteId, restNoteTypes }: { noteId: string, restNoteTypes: NoteTypeMapping[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_others")}
icon="bx bx-dots-vertical-rounded"
>
{restNoteTypes.map(noteType => (
<FormListItem
key={noteType.type}
icon={`bx ${noteType.icon}`}
onClick={() => switchNoteType(noteId, noteType)}
>{noteType.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function CollectionNoteTypes({ noteId, collectionTemplates }: { noteId: string, collectionTemplates: FNote[] }) {
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_collection")}
icon="bx bx-book"
>
{collectionTemplates.map(collectionTemplate => (
<FormListItem
key={collectionTemplate.noteId}
icon={collectionTemplate.getIcon()}
onClick={() => setTemplate(noteId, collectionTemplate.noteId)}
>{collectionTemplate.title}</FormListItem>
))}
</BadgeWithDropdown>
);
}
function TemplateNoteTypes({ noteId, builtinTemplates }: { noteId: string, builtinTemplates: FNote[] }) {
const [ userTemplates, setUserTemplates ] = useState<FNote[]>([]);
async function refreshTemplates() {
const templateNoteIds = await server.get<string[]>("search-templates");
const templateNotes = await froca.getNotes(templateNoteIds);
setUserTemplates(templateNotes);
}
// First load.
useEffect(() => {
refreshTemplates();
}, []);
// React to external changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(attr => attr.type === "label" && attr.name === "template")) {
refreshTemplates();
}
});
return (
<BadgeWithDropdown
text={t("note_title.note_type_switcher_templates")}
icon="bx bx-copy-alt"
>
{userTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
{userTemplates.length > 0 && <FormDropdownDivider />}
{builtinTemplates.map(template => <TemplateItem key={template.noteId} noteId={noteId} template={template} />)}
</BadgeWithDropdown>
);
}
function TemplateItem({ noteId, template }: { noteId: string, template: FNote }) {
return (
<FormListItem
icon={template.getIcon()}
onClick={() => setTemplate(noteId, template.noteId)}
>{template.title}</FormListItem>
);
}
function switchNoteType(noteId: string, { type, mime }: NoteTypeMapping) {
return server.put(`notes/${noteId}/type`, { type, mime });
}
function setTemplate(noteId: string, templateId: string) {
return attributes.setRelation(noteId, "template", templateId);
}
function useBuiltinTemplates() {
const [ templates, setTemplates ] = useState<{
builtinTemplates: FNote[];
collectionTemplates: FNote[];
}>({
builtinTemplates: [],
collectionTemplates: []
});
async function loadBuiltinTemplates() {
const templatesRoot = await froca.getNote("_templates");
if (!templatesRoot) return;
const childNotes = await templatesRoot.getChildNotes();
const builtinTemplates: FNote[] = [];
const collectionTemplates: FNote[] = [];
for (const childNote of childNotes) {
if (!childNote.hasLabel("template")) continue;
if (childNote.hasLabel("collection")) {
collectionTemplates.push(childNote);
} else {
builtinTemplates.push(childNote);
}
}
setTemplates({ builtinTemplates, collectionTemplates });
}
useEffect(() => {
loadBuiltinTemplates();
}, []);
return templates;
}

View File

@@ -1,15 +1,15 @@
.component.status-bar {
contain: none;
border-top: 1px solid var(--main-border-color);
background-color: var(--left-pane-background-color);
> .status-bar-main-row {
min-height: 28px;
display: flex;
align-items: center;
background-color: var(--left-pane-background-color);
padding-inline: 0.25em;
font-size: 0.85em;
> .breadcrumb {
flex-grow: 1;
--icon-button-size: 23px;
@@ -155,11 +155,6 @@
}
}
.dropdown-code-note-switcher {
max-height: 90vh;
overflow: scroll;
}
.backlinks-widget > .dropdown-menu {
--menu-padding-size: .9em;
@@ -248,16 +243,82 @@
> .attribute-list {
font-size: 0.9em;
padding: 0.5em 0.75em;
.inherited-attributes-widget > div {
padding: 0;
font-size: 0.9em;
.attributes-panel-label {
opacity: .5;
margin-inline-end: 4px;
font-weight: 600;
}
.attribute-list-editor {
padding: 0 !important;
}
.inherited-attributes-widget {
display: inline;
> div {
display: inline;
padding: 0;
}
}
.attribute-list-editor-wrapper {
display: flex;
flex-direction: column-reverse;
padding-bottom: 0 !important;
.attribute-list-editor {
padding-block: 0 !important;
padding-inline: 0 100px !important ;
}
.attribute-errors {
padding: 4px 0;
color: var(--dropdown-item-icon-destructive-color);
font-style: italic;
}
.ck.ck-editor__editable::after {
/* Remove a hidden spinner that causes overflow */
display: none;
}
}
}
div.similar-notes-widget div.similar-notes-wrapper {
max-height: unset;
}
button.select-button:not(:focus-visible) {
outline: none;
}
}
.bottom-panel {
margin: 0 !important;
padding: 0;
.bottom-panel-title-bar {
display: flex;
padding: 6px 12px;
background: var(--bottom-panel-title-bar-background-color);
justify-content: space-between;
align-items: center;
.bottom-panel-title-bar-caption {
text-transform: uppercase;
letter-spacing: .3pt;
font-weight: 600;
font-size: .85em;
}
}
.bottom-panel-content {
border-bottom: 1px solid var(--main-border-color);
background: var(--bottom-panel-background-color);
padding: 8px 12px;
max-height: 40vh;
overflow-y: auto;
}
}

View File

@@ -266,11 +266,15 @@ function NoteInfoValue({ text, title, value }: { text: string; title?: string, v
);
}
function SimilarNotesPane({ note, similarNotesShown }: NoteInfoContext) {
function SimilarNotesPane({ note, similarNotesShown, setSimilarNotesShown }: NoteInfoContext) {
return (similarNotesShown &&
<div className="similar-notes-pane">
<BottomPanel title={t("similar_notes.title")}
className="similar-notes-pane"
visible={similarNotesShown}
setVisible={setSimilarNotesShown}
>
<SimilarNotesTab note={note} />
</div>
</BottomPanel>
);
}
//#endregion
@@ -367,15 +371,20 @@ function AttributesPane({ note, noteContext, attributesShown, setAttributesShown
}), [ api ]));
return (context &&
<div className={clsx("attribute-list", !attributesShown && "hidden-ext")}>
<InheritedAttributesTab {...context} />
<BottomPanel title={t("attributes_panel.title")}
className="attribute-list"
visible={attributesShown}
setVisible={setAttributesShown}>
<span class="attributes-panel-label">{t("inherited_attribute_list.title")}</span>
<InheritedAttributesTab {...context} emptyListString="inherited_attribute_list.none" />
<AttributeEditor
{...context}
api={api}
ntxId={noteContext.ntxId}
/>
</div>
</BottomPanel>
);
}
//#endregion
@@ -419,10 +428,10 @@ function CodeNoteSwitcher({ note }: StatusBarContext) {
return (note.type === "code" &&
<>
<StatusBarDropdown
icon="bx bx-code-curly"
icon={correspondingMimeType?.icon ?? "bx bx-code-curly"}
text={correspondingMimeType?.title}
title={t("status_bar.code_note_switcher")}
dropdownContainerClassName="dropdown-code-note-switcher"
dropdownContainerClassName="dropdown-code-note-switcher tn-dropdown-menu-scrollable"
>
<NoteTypeCodeNoteList
currentMimeType={currentNoteMime}
@@ -439,3 +448,26 @@ function CodeNoteSwitcher({ note }: StatusBarContext) {
);
}
//#endregion
//#region Bottom panel
interface BottomPanelParams {
children: ComponentChildren;
title: string;
visible: boolean;
setVisible?: (visible: boolean) => void;
className?: string;
}
function BottomPanel({ children, title, visible, setVisible, className }: BottomPanelParams) {
return <div className={clsx("bottom-panel", className, {"hidden-ext": !visible})}>
<div className="bottom-panel-title-bar">
<span className="bottom-panel-title-bar-caption">{title}</span>
<button class="icon-action bx bx-x" onClick={() => setVisible?.(false)} />
</div>
<div class={clsx("bottom-panel-content")}>
{children}
</div>
</div>;
}
//#endregion

View File

@@ -37,7 +37,7 @@ body.experimental-feature-new-layout {
.title-row {
container-type: size;
transition: border 400ms ease-out;
&.note-split-title {
border-bottom: 1px solid var(--main-border-color);
@@ -56,13 +56,13 @@ body.experimental-feature-new-layout {
--note-icon-container-padding-size: 6px;
margin-inline: 0;
}
.note-title-widget {
--note-title-size: 18px;
--note-title-padding-inline: 0;
}
@container (max-width: 700px) {
@container (max-width: 600px) {
.note-title-widget {
--note-title-size: 1.25rem;
--note-title-padding-inline: 4px;
@@ -80,7 +80,7 @@ body.experimental-feature-new-layout {
.ext-badge .text {
display: none;
}
.ext-badge {
--size: 2em;
width: var(--size);

View File

@@ -50,16 +50,30 @@
white-space: nowrap;
border-radius: var(--badge-radius);
button,
.btn {
min-width: 0;
overflow: hidden;
}
.ext-badge {
border-radius: 0;
.text {
display: inline-flex;
align-items: center;
min-width: 0;
.text-inner {
min-width: 0;
text-overflow: ellipsis;
overflow: hidden;
}
.arrow {
font-size: 1.3em;
margin-left: 0.25em;
margin-inline-start: 0.25em;
margin-inline-end: 0;
}
}
}

View File

@@ -32,7 +32,8 @@ export function Badge({ icon, className, text, tooltip, href, ...containerProps
fallbackPlacements: [ "bottom" ],
animation: false,
html: true,
title: tooltip
title: tooltip,
customClass: "pre-wrap-text"
});
const content = <>
@@ -59,7 +60,10 @@ export function BadgeWithDropdown({ text, children, tooltip, className, dropdown
<Dropdown
className={`dropdown-badge dropdown-${className}`}
text={<Badge
text={<>{text} <Icon className="arrow" icon="bx bx-chevron-down" /></>}
text={<>
<span class="text-inner">{text}</span>
<Icon className="arrow" icon="bx bx-chevron-down" />
</>}
className={className}
{...props}
/>}

View File

@@ -2,10 +2,11 @@ import { Dropdown as BootstrapDropdown, Tooltip } from "bootstrap";
import { ComponentChildren, HTMLAttributes } from "preact";
import { CSSProperties, HTMLProps } from "preact/compat";
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
import { useTooltip, useUniqueName } from "./hooks";
type DataAttributes = {
[key: `data-${string}`]: string | number | boolean | undefined;
[key: `data-${string}`]: string | number | boolean | undefined;
};
export interface DropdownProps extends Pick<HTMLProps<HTMLDivElement>, "id" | "className"> {
@@ -66,14 +67,14 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
return () => {
resizeObserver.disconnect();
dropdown.dispose();
}
};
}, []);
const onShown = useCallback(() => {
setShown(true);
externalOnShown?.();
hideTooltip();
}, [ hideTooltip ])
}, [ hideTooltip ]);
const onHidden = useCallback(() => {
setShown(false);
@@ -122,7 +123,7 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
{...buttonProps}
>
{text}
<span className="caret"></span>
<span className="caret" />
</button>
<ul
@@ -130,9 +131,15 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi
style={dropdownContainerStyle}
aria-labelledby={ariaId}
ref={dropdownContainerRef}
onClick={(e) => {
// Prevent clicks directly inside the dropdown from closing.
if (e.target === dropdownContainerRef.current) {
e.stopPropagation();
}
}}
>
{shown && children}
</ul>
</div>
)
);
}

View File

@@ -1,8 +1,8 @@
import { Tooltip } from "bootstrap";
import { useEffect, useRef, useMemo, useCallback } from "preact/hooks";
import { ComponentChildren, CSSProperties } from "preact";
import { useCallback, useEffect, useMemo, useRef } from "preact/hooks";
import { escapeQuotes } from "../../services/utils";
import { ComponentChildren } from "preact";
import { CSSProperties, memo } from "preact/compat";
import { useUniqueName } from "./hooks";
interface FormCheckboxProps {
@@ -18,30 +18,30 @@ interface FormCheckboxProps {
containerStyle?: CSSProperties;
}
const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => {
export default function FormCheckbox({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) {
const labelRef = useRef<HTMLLabelElement>(null);
const id = useUniqueName(name);
useEffect(() => {
if (!hint || !labelRef.current) return;
const tooltipInstance = Tooltip.getOrCreateInstance(labelRef.current, {
html: true,
template: '<div class="tooltip tooltip-top" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>'
});
return () => tooltipInstance?.dispose();
}, [hint]); // Proper dependency
const labelStyle = useMemo(() =>
const labelStyle = useMemo(() =>
hint ? { textDecoration: "underline dotted var(--main-text-color)" } : undefined,
[hint]
[hint]
);
const handleChange = useCallback((e: Event) => {
onChange((e.target as HTMLInputElement).checked);
}, [onChange]);
const titleText = useMemo(() => hint ? escapeQuotes(hint) : undefined, [hint]);
return (
@@ -65,6 +65,4 @@ const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint
</label>
</div>
);
});
export default FormCheckbox;
}

View File

@@ -105,22 +105,17 @@ export function NewNoteLink({ notePath, viewScope, noContextMenu, showNoteIcon,
const [ archived ] = useNoteLabelBoolean(note, "archived");
return (
<span>
<span>
{icon && <Icon icon={icon} />}
<a
className={clsx("tn-link", colorClass, {
"no-tooltip-preview": noPreview,
archived
})}
href={calculateHash({ notePath, viewScope })}
data-no-context-menu={noContextMenu}
{...linkProps}
>
{title}
</a>
</span>
</span>
<a
className={clsx("tn-link", colorClass, {
"no-tooltip-preview": noPreview,
archived
})}
href={calculateHash({ notePath, viewScope })}
data-no-context-menu={noContextMenu}
{...linkProps}
>
{icon && <><Icon icon={icon} />&nbsp;</>}
{title}
</a>
);
}

View File

@@ -369,7 +369,8 @@ export function useActiveNoteContext() {
useEffect(() => {
setNote(noteContext?.note);
}, [ notePath ]);
setNotePath(noteContext?.notePath);
}, [ notePath, noteContext?.note, noteContext?.notePath ]);
useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], () => {
const noteContext = appContext.tabManager.getActiveContext() ?? undefined;
@@ -634,7 +635,8 @@ export function useLegacyWidget<T extends BasicWidget>(widgetFactory: () => T, {
const renderedWidget = widget.render();
return [ widget, renderedWidget ];
}, [ noteContext, parentComponent, widgetFactory]);
}, [ noteContext, parentComponent ]); // eslint-disable-line react-hooks/exhaustive-deps
// widgetFactory() is intentionally left out
// Attach the widget to the parent.
useEffect(() => {

View File

@@ -9,7 +9,11 @@ import RawHtml from "../react/RawHtml";
import { joinElements } from "../react/react_utils";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail";
export default function InheritedAttributesTab({ note, componentId }: Pick<TabContext, "note" | "componentId">) {
type InheritedAttributesTabArgs = Pick<TabContext, "note" | "componentId"> & {
emptyListString?: string;
}
export default function InheritedAttributesTab({ note, componentId, emptyListString }: InheritedAttributesTabArgs) {
const [ inheritedAttributes, setInheritedAttributes ] = useState<FAttribute[]>();
const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget());
@@ -63,7 +67,7 @@ export default function InheritedAttributesTab({ note, componentId }: Pick<TabCo
/>
)), " ")
) : (
<>{t("inherited_attribute_list.no_inherited_attributes")}</>
<>{t(emptyListString ?? "inherited_attribute_list.no_inherited_attributes")}</>
)}
</div>

View File

@@ -283,6 +283,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
return (
<>
{!hidden && <div
className="attribute-list-editor-wrapper"
ref={wrapperRef}
style="position: relative; padding-top: 10px; padding-bottom: 10px"
onKeyDown={(e) => {
@@ -296,106 +297,107 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
setTimeout(() => save(), 100);
}
}}
>
<CKEditor
apiRef={editorRef}
className="attribute-list-editor"
tabIndex={200}
editor={CKEditorAttributeEditor}
currentValue={currentValue}
config={{
toolbar: { items: [] },
placeholder: t("attribute_editor.placeholder"),
mention: { feeds: mentionSetup },
licenseKey: "GPL",
language: "en"
}}
onChange={(currentValue) => {
currentValueRef.current = currentValue ?? "";
const oldValue = getPreprocessedData(lastSavedContent.current ?? "").trimEnd();
const newValue = getPreprocessedData(currentValue ?? "").trimEnd();
setNeedsSaving(oldValue !== newValue);
setError(undefined);
}}
onClick={(e, pos) => {
if (pos && pos.textNode && pos.textNode.data) {
const clickIndex = getClickIndex(pos);
let parsedAttrs: Attribute[];
try {
parsedAttrs = attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current), true);
} catch (e: unknown) {
// the input is incorrect because the user messed up with it and now needs to fix it manually
console.log(e);
return null;
}
let matchedAttr: Attribute | null = null;
for (const attr of parsedAttrs) {
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr;
break;
}
}
setTimeout(() => {
if (matchedAttr) {
attributeDetailWidget.showAttributeDetail({
allAttributes: parsedAttrs,
attribute: matchedAttr,
isOwned: true,
x: e.pageX,
y: e.pageY
});
setState("showAttributeDetail");
} else {
setState("showHelpTooltip");
}
}, 100);
} else {
setState("showHelpTooltip");
}
}}
onKeyDown={() => attributeDetailWidget.hide()}
onBlur={() => save()}
onInitialized={() => editorRef.current?.focus()}
disableNewlines disableSpellcheck
/>
<div className="attribute-editor-buttons">
{ needsSaving && <ActionButton
icon="bx bx-save"
className="save-attributes-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.save_attributes"))}
onClick={save}
/> }
<ActionButton
icon="bx bx-plus"
className="add-new-attribute-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.add_a_new_attribute"))}
onClick={(e) => {
// Prevent automatic hiding of the context menu due to the button being clicked.
e.stopPropagation();
contextMenu.show<AttributeCommandNames>({
x: e.pageX,
y: e.pageY,
orientation: "left",
items: [
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ kind: "separator" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
],
selectMenuItemHandler: (item) => handleAddNewAttributeCommand(item.command)
});
> <div style="position: relative;">
<CKEditor
apiRef={editorRef}
className="attribute-list-editor"
tabIndex={200}
editor={CKEditorAttributeEditor}
currentValue={currentValue}
config={{
toolbar: { items: [] },
placeholder: t("attribute_editor.placeholder"),
mention: { feeds: mentionSetup },
licenseKey: "GPL",
language: "en"
}}
onChange={(currentValue) => {
currentValueRef.current = currentValue ?? "";
const oldValue = getPreprocessedData(lastSavedContent.current ?? "").trimEnd();
const newValue = getPreprocessedData(currentValue ?? "").trimEnd();
setNeedsSaving(oldValue !== newValue);
setError(undefined);
}}
onClick={(e, pos) => {
if (pos && pos.textNode && pos.textNode.data) {
const clickIndex = getClickIndex(pos);
let parsedAttrs: Attribute[];
try {
parsedAttrs = attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current), true);
} catch (e: unknown) {
// the input is incorrect because the user messed up with it and now needs to fix it manually
console.log(e);
return null;
}
let matchedAttr: Attribute | null = null;
for (const attr of parsedAttrs) {
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr;
break;
}
}
setTimeout(() => {
if (matchedAttr) {
attributeDetailWidget.showAttributeDetail({
allAttributes: parsedAttrs,
attribute: matchedAttr,
isOwned: true,
x: e.pageX,
y: e.pageY
});
setState("showAttributeDetail");
} else {
setState("showHelpTooltip");
}
}, 100);
} else {
setState("showHelpTooltip");
}
}}
onKeyDown={() => attributeDetailWidget.hide()}
onBlur={() => save()}
onInitialized={() => editorRef.current?.focus()}
disableNewlines disableSpellcheck
/>
<div className="attribute-editor-buttons">
{ needsSaving && <ActionButton
icon="bx bx-save"
className="save-attributes-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.save_attributes"))}
onClick={save}
/> }
<ActionButton
icon="bx bx-plus"
className="add-new-attribute-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.add_a_new_attribute"))}
onClick={(e) => {
// Prevent automatic hiding of the context menu due to the button being clicked.
e.stopPropagation();
contextMenu.show<AttributeCommandNames>({
x: e.pageX,
y: e.pageY,
orientation: "left",
items: [
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ kind: "separator" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
],
selectMenuItemHandler: (item) => handleAddNewAttributeCommand(item.command)
});
}}
/>
</div>
</div>
{ error && (

View File

@@ -5,6 +5,7 @@ import { useEffect, useState } from "preact/hooks";
import FNote from "../entities/fnote";
import attributes from "../services/attributes";
import { t } from "../services/i18n";
import { isElectron } from "../services/utils";
import HelpButton from "./react/HelpButton";
import { useNoteContext, useTriliumEvent, useTriliumOption } from "./react/hooks";
import InfoBar from "./react/InfoBar";
@@ -68,7 +69,11 @@ export function useShareInfo(note: FNote | null | undefined) {
}
});
return { link, linkHref, isSharedExternally: !!syncServerHost };
return {
link,
linkHref,
isSharedExternally: !isElectron() || !!syncServerHost // on server we can't reliably detect if the note is shared locally or available publicly.
};
}
function getShareId(note: FNote) {

View File

@@ -6,10 +6,10 @@ import { VNode } from "preact";
import { useEffect, useRef } from "preact/hooks";
import appContext from "../../components/app_context";
import { WidgetsByParent } from "../../services/bundle";
import { t } from "../../services/i18n";
import options from "../../services/options";
import { DEFAULT_GUTTER_SIZE } from "../../services/resizer";
import BasicWidget from "../basic_widget";
import Button from "../react/Button";
import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumOptionBool, useTriliumOptionJson } from "../react/hooks";
import Icon from "../react/Icon";
@@ -23,12 +23,12 @@ const MIN_WIDTH_PERCENT = 5;
interface RightPanelWidgetDefinition {
el: VNode;
enabled: boolean;
position: number;
position?: number;
}
export default function RightPanelContainer({ customWidgets }: { customWidgets: BasicWidget[] }) {
export default function RightPanelContainer({ widgetsByParent }: { widgetsByParent: WidgetsByParent }) {
const [ rightPaneVisible, setRightPaneVisible ] = useTriliumOptionBool("rightPaneVisible");
const items = useItems(rightPaneVisible, customWidgets);
const items = useItems(rightPaneVisible, widgetsByParent);
useSplit(rightPaneVisible);
return (
@@ -51,7 +51,7 @@ export default function RightPanelContainer({ customWidgets }: { customWidgets:
);
}
function useItems(rightPaneVisible: boolean, customWidgets: BasicWidget[]) {
function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) {
const { note } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type");
const [ highlightsList ] = useTriliumOptionJson<string[]>("highlightsList");
@@ -61,23 +61,38 @@ function useItems(rightPaneVisible: boolean, customWidgets: BasicWidget[]) {
{
el: <TableOfContents />,
enabled: (noteType === "text" || noteType === "doc"),
position: 10,
},
{
el: <HighlightsList />,
enabled: noteType === "text" && highlightsList.length > 0,
position: 20,
},
...customWidgets.map((w, i) => ({
el: <CustomWidget key={w._noteId} originalWidget={w as LegacyRightPanelWidget} />,
...widgetsByParent.getLegacyWidgets("right-pane").map((widget) => ({
el: <CustomLegacyWidget key={widget._noteId} originalWidget={widget as LegacyRightPanelWidget} />,
enabled: true,
position: w.position ?? 30 + i * 10
}))
position: widget.position
})),
...widgetsByParent.getPreactWidgets("right-pane").map((widget) => {
const El = widget.render;
return {
el: <El />,
enabled: true,
position: widget.position
};
})
];
// Assign a position to items that don't have one yet.
let pos = 10;
for (const definition of definitions) {
if (!definition.position) {
definition.position = pos;
pos += 10;
}
}
return definitions
.filter(e => e.enabled)
.toSorted((a, b) => a.position - b.position)
.toSorted((a, b) => (a.position ?? 10) - (b.position ?? 10))
.map(e => e.el);
}
@@ -99,7 +114,7 @@ function useSplit(visible: boolean) {
}, [ visible ]);
}
function CustomWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) {
function CustomLegacyWidget({ originalWidget }: { originalWidget: LegacyRightPanelWidget }) {
const containerRef = useRef<HTMLDivElement>(null);
return (

View File

@@ -1,16 +1,18 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { getThemeById, default as VanillaCodeMirror } from "@triliumnext/codemirror";
import { TypeWidgetProps } from "../type_widget";
import "./code.css";
import CodeMirror, { CodeMirrorProps } from "./CodeMirror";
import utils from "../../../services/utils";
import { useEditorSpacedUpdate, useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteBlob, useSyncedRef, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import { t } from "../../../services/i18n";
import { default as VanillaCodeMirror, getThemeById } from "@triliumnext/codemirror";
import { useEffect, useRef, useState } from "preact/hooks";
import appContext, { CommandListenerData } from "../../../components/app_context";
import TouchBar, { TouchBarButton } from "../../react/TouchBar";
import { refToJQuerySelector } from "../../react/react_utils";
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
import FNote from "../../../entities/fnote";
import { t } from "../../../services/i18n";
import utils from "../../../services/utils";
import { useEditorSpacedUpdate, useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteBlob, useNoteProperty, useSyncedRef, useTriliumEvent, useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import { refToJQuerySelector } from "../../react/react_utils";
import TouchBar, { TouchBarButton } from "../../react/TouchBar";
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
import { TypeWidgetProps } from "../type_widget";
import CodeMirror, { CodeMirrorProps } from "./CodeMirror";
interface CodeEditorProps {
/** By default, the code editor will try to match the color of the scrolling container to match the one from the theme for a full-screen experience. If the editor is embedded, it makes sense not to have this behaviour. */
@@ -51,7 +53,7 @@ export function ReadOnlyCode({ note, viewScope, ntxId, parentComponent }: TypeWi
mime={note.mime}
readOnly
/>
)
);
}
function formatViewSource(note: FNote, content: string) {
@@ -74,6 +76,7 @@ export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentC
const editorRef = useRef<VanillaCodeMirror>(null);
const containerRef = useRef<HTMLPreElement>(null);
const [ vimKeymapEnabled ] = useTriliumOptionBool("vimKeymapEnabled");
const mime = useNoteProperty(note, "mime");
const spacedUpdate = useEditorSpacedUpdate({
note,
noteContext,
@@ -107,7 +110,7 @@ export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentC
<CodeEditor
ntxId={ntxId} parentComponent={parentComponent}
editorRef={editorRef} containerRef={containerRef}
mime={note.mime}
mime={mime ?? "text/plain"}
className="note-detail-code-editor"
placeholder={t("editable_code.placeholder")}
vimKeybindings={vimKeymapEnabled}
@@ -130,7 +133,7 @@ export function EditableCode({ note, ntxId, noteContext, debounceUpdate, parentC
)}
</TouchBar>
</>
)
);
}
export function CodeEditor({ parentComponent, ntxId, containerRef: externalContainerRef, editorRef: externalEditorRef, mime, onInitialized, lineWrapping, noBackgroundChange, ...editorProps }: CodeEditorProps & CodeMirrorProps & Pick<TypeWidgetProps, "parentComponent" | "ntxId">) {

View File

@@ -1,6 +1,6 @@
{
"formatVersion": 2,
"appVersion": "0.99.3",
"appVersion": "0.100.0",
"files": [
{
"isClone": false,
@@ -5507,51 +5507,7 @@
}
],
"dataFileName": "chartjs-plugin-datalabe.min.js",
"attachments": [],
"dirFileName": "chartjs-plugin-datalabels.min.js",
"children": [
{
"isClone": true,
"noteId": "piyimQhwfcy5",
"notePath": [
"root",
"rvaX6hEaQlmk",
"KZVWidxicAfn",
"xRQuuwkaobBM",
"GXUcReLM6dSe",
"oLPbgCo7djD7",
"AlL9eFopYuHg",
"9GZB2MeW51xv",
"3jaioienOLTR",
"piyimQhwfcy5"
],
"title": "chart.js",
"prefix": null,
"dataFileName": "chart.js.clone.html",
"type": "text",
"format": "html"
}
]
},
{
"isClone": true,
"noteId": "piyimQhwfcy5",
"notePath": [
"root",
"rvaX6hEaQlmk",
"KZVWidxicAfn",
"xRQuuwkaobBM",
"GXUcReLM6dSe",
"oLPbgCo7djD7",
"AlL9eFopYuHg",
"9GZB2MeW51xv",
"piyimQhwfcy5"
],
"title": "chart.js",
"prefix": null,
"dataFileName": "chart.js.clone.html",
"type": "text",
"format": "html"
"attachments": []
}
]
},
@@ -6044,6 +6000,86 @@
],
"dataFileName": "Custom request handler.js",
"attachments": []
},
{
"isClone": false,
"noteId": "DAybX9h5jOoG",
"notePath": [
"root",
"rvaX6hEaQlmk",
"KZVWidxicAfn",
"DAybX9h5jOoG"
],
"title": "Render note with JSX",
"notePosition": 100,
"prefix": null,
"isExpanded": false,
"type": "render",
"mime": "",
"attributes": [
{
"type": "label",
"name": "widget",
"value": "",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "renderNote",
"value": "xzqr5J1V4YwY",
"isInheritable": false,
"position": 20
}
],
"attachments": [],
"dirFileName": "Render note with JSX",
"children": [
{
"isClone": false,
"noteId": "xzqr5J1V4YwY",
"notePath": [
"root",
"rvaX6hEaQlmk",
"KZVWidxicAfn",
"DAybX9h5jOoG",
"xzqr5J1V4YwY"
],
"title": "JSX",
"notePosition": 12,
"prefix": null,
"isExpanded": false,
"type": "code",
"mime": "text/jsx",
"attributes": [],
"dataFileName": "JSX.jsx",
"attachments": [],
"dirFileName": "JSX",
"children": [
{
"isClone": false,
"noteId": "mqDw6BebfE58",
"notePath": [
"root",
"rvaX6hEaQlmk",
"KZVWidxicAfn",
"DAybX9h5jOoG",
"xzqr5J1V4YwY",
"mqDw6BebfE58"
],
"title": "FormElements",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "code",
"mime": "text/jsx",
"attributes": [],
"dataFileName": "FormElements.jsx",
"attachments": []
}
]
}
]
}
]
}

View File

@@ -540,14 +540,6 @@
<ul>
<li><a href="root/Trilium%20Demo/Scripting%20examples/Statistics/Attribute%20count/template/js/renderPieChart/chartjs-plugin-datalabe.min.js"
target="detail">chartjs-plugin-datalabels.min.js</a>
<ul>
<li><a href="root/Trilium%20Demo/Scripting%20examples/Weight%20Tracker/Implementation/JS%20code/chart.js"
target="detail">chart.js</a>
</li>
</ul>
</li>
<li><a href="root/Trilium%20Demo/Scripting%20examples/Weight%20Tracker/Implementation/JS%20code/chart.js"
target="detail">chart.js</a>
</li>
</ul>
</li>
@@ -633,6 +625,18 @@
<li><a href="root/Trilium%20Demo/Scripting%20examples/Custom%20request%20handler.js"
target="detail">Custom request handler</a>
</li>
<li>Render note with JSX
<ul>
<li><a href="root/Trilium%20Demo/Scripting%20examples/Render%20note%20with%20JSX/JSX.jsx"
target="detail">JSX</a>
<ul>
<li><a href="root/Trilium%20Demo/Scripting%20examples/Render%20note%20with%20JSX/JSX/FormElements.jsx"
target="detail">FormElements</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
width="150" height="150">
</figure>
<p><strong>Welcome to Trilium Notes!</strong>
</p>
<p>This is a "demo" document packaged with Trilium to showcase some of its
features and also give you some ideas on how you might structure your notes.
@@ -25,17 +26,22 @@
you wish.</p>
<p>If you need any help, visit <a href="https://triliumnotes.org">triliumnotes.org</a> or
our <a href="https://github.com/TriliumNext">GitHub repository</a>
</p>
<h2>Cleanup</h2>
<p>Once you're finished with experimenting and want to cleanup these pages,
you can simply delete them all.</p>
<h2>Formatting</h2>
<p>Trilium supports classic formatting like <em>italic</em>, <strong>bold</strong>, <em><strong>bold and italic</strong></em>.
You can add links pointing to <a href="https://triliumnotes.org/">external pages</a> or&nbsp;
<a
class="reference-link" href="Trilium%20Demo/Formatting%20examples">Formatting examples</a>.</p>
<h3>Lists</h3>
<p><strong>Ordered:</strong>
</p>
<ol>
<li data-list-item-id="e877cc655d0239b8bb0f38696ad5d8abb">First Item</li>
@@ -50,6 +56,7 @@
</li>
</ol>
<p><strong>Unordered:</strong>
</p>
<ul>
<li data-list-item-id="e68bf4b518a16671c314a72073c3d900a">Item</li>
@@ -60,6 +67,7 @@
</li>
</ul>
<h3>Block quotes</h3>
<blockquote>
<p>Whereof one cannot speak, thereof one must be silent”</p>
<p> Ludwig Wittgenstein</p>

View File

@@ -14,17 +14,22 @@
<div class="ck-content">
<h2>Main characters</h2>
<p>… here put main characters …</p>
<p>&nbsp;</p>
<h2>Plot</h2>
<p>… describe main plot lines …</p>
<p>&nbsp;</p>
<h2>Tone</h2>
<p>&nbsp;</p>
<h2>Genre</h2>
<p>scifi / drama / romance</p>
<p>&nbsp;</p>
<h2>Similar books</h2>
<ul>
<li></li>
</ul>

View File

@@ -16,8 +16,10 @@
<p>Check out <a href="https://www.amazon.com/amz-books/book-deals">Kindle Daily Deals</a>:</p>
<ul>
<li data-list-item-id="e5bec34ede90d88a3ad5f7362ab60cbfb">Cixin Liu - <a href="https://www.amazon.com/Dark-Forest-Remembrance-Earths-Past/dp/0765386690/">The Dark Forest</a>
</li>
<li data-list-item-id="ef10faa539920a4fd817f09ba564d69d3">Ann Leckie - <a href="https://www.amazon.com/Ancillary-Sword-Imperial-Radch-Leckie/dp/0316246654/">Ancillary Sword</a>
</li>
</ul>
</div>

View File

@@ -18,21 +18,25 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">buy milk&nbsp;&nbsp;</span>
</label>
</li>
<li>
<label class="todo-list__label">
<input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">do the laundry&nbsp;&nbsp;</span>
</label>
</li>
<li>
<label class="todo-list__label">
<input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">watch TV&nbsp;&nbsp;</span>
</label>
</li>
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">eat ice cream&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -28,6 +28,7 @@
}</code></pre>
<p>For larger pieces of code it is better to use a code note, which uses
a fully-fledged code editor (CodeMirror). For an example of a code note,
see&nbsp;<a class="reference-link" href="../Scripting%20examples/Custom%20request%20handler.js">Custom request handler</a>.</p>

View File

@@ -15,7 +15,9 @@
<div class="ck-content">
<p><span class="math-tex">\(% \f is defined as #1f(#2) using the macro \f\relax{x} = \int_{-\infty}^\infty &nbsp; &nbsp; \f\hat\xi\,e^{2 \pi i \xi x} &nbsp; &nbsp; \,d\xi\)</span>Some
math examples:</p><span class="math-tex">\[\displaystyle \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots} } } }\]</span>
<p>Another:</p><span class="math-tex">\[\displaystyle \left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)\]</span>
<p>Inline math is also possible:&nbsp;<span class="math-tex">\(c^2 = a^2 + b^2\)</span>&nbsp;</p>
<p>&nbsp;</p>
</div>

View File

@@ -15,6 +15,7 @@
<div class="ck-content">
<p>How to be a stoic from Massimo Pigliuci:</p>
<p><a href="https://www.amazon.com/gp/product/B01K3WN1BY">https://www.amazon.com/gp/product/B01K3WN1BY</a>
</p>
</div>
</div>

View File

@@ -22,6 +22,7 @@
<p>This page demonstrates two things:</p>
<ul>
<li>possibility to <a href="#root/_hidden/_help/_help_KSZ04uQ2D1St/_help_iPIMuisry3hd/_help_nBAXQFj20hS1">include one note into another</a>
</li>
<li>PDF preview - you can read PDFs directly in Trilium!</li>
</ul>

View File

@@ -14,6 +14,7 @@
<div class="ck-content">
<p>You can read some explanation on how this journal works here: <a href="https://github.com/zadam/trilium/wiki/Day-notes">https://github.com/zadam/trilium/wiki/Day-notes</a>
</p>
</div>
</div>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -17,6 +17,7 @@
<li>XBox</li>
<li>Candles</li>
<li><a href="https://www.amazon.ca/Anker-SoundCore-Portable-Bluetooth-Resistance/dp/B01MTB55WH?pd_rd_wg=honW8&amp;pd_rd_r=c9bb7c0f-0051-4da7-991f-4ca711a1b3e3&amp;pd_rd_w=ciUpR&amp;ref_=pd_gw_simh&amp;pf_rd_r=K10XKX0NGPDNTYYP4BS4&amp;pf_rd_p=5f1b460b-78c1-580e-929e-2878fe4859e8">Portable speakers</a>
</li>
<li>...?</li>
</ul>

View File

@@ -14,8 +14,10 @@
<div class="ck-content">
<p>Wiki: <a href="https://en.wikipedia.org/wiki/Trusted_timestamping">https://en.wikipedia.org/wiki/Trusted_timestamping</a>
</p>
<p>Bozho: <a href="https://techblog.bozho.net/using-trusted-timestamping-java/">https://techblog.bozho.net/using-trusted-timestamping-java/</a>
</p>
<p><strong>Trusted timestamping</strong> is the process of <a href="https://en.wikipedia.org/wiki/Computer_security">securely</a> keeping
track of the creation and modification time of a document. Security here

View File

@@ -16,6 +16,7 @@
<p>Miscellaneous notes done on monday ...</p>
<p>&nbsp;</p>
<p>Interesting video: <a href="https://www.youtube.com/watch?v=_eSAF_qT_FY&amp;feature=youtu.be">https://www.youtube.com/watch?v=_eSAF_qT_FY&amp;feature=youtu.be</a>
</p>
<p>&nbsp;</p>
<p>&nbsp;</p>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
width="209" height="300">
</figure>
<p>Maybe CodeNames? <a href="https://boardgamegeek.com/boardgame/178900/codenames">https://boardgamegeek.com/boardgame/178900/codenames</a>
</p>
</div>
</div>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -18,6 +18,7 @@
<li>
<label class="todo-list__label">
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">&nbsp;&nbsp;</span>
</label>
</li>
</ul>

View File

@@ -24,14 +24,17 @@
<span
class="footnote-reference" data-footnote-reference="" data-footnote-index="1"
data-footnote-id="6qz4pm021mi" role="doc-noteref" id="fnref6qz4pm021mi"><sup><a href="#fn6qz4pm021mi">[1]</a></sup>
</span>
</p>
<ol class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
data-footnote-id="6qz4pm021mi" role="doc-endnote" id="fn6qz4pm021mi"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="6qz4pm021mi"><sup><strong><a href="#fnref6qz4pm021mi">^</a></strong></sup></span>
<div
class="footnote-content" data-footnote-content="">
<p><a href="https://www.thecollector.com/what-are-the-seven-wonders-of-the-world/">What Are the 7 Wonders of the World? (with HD Images) | TheCollector</a>
</p>
</div>
</li>

View File

@@ -26,13 +26,16 @@
been brought to its knees.<span class="footnote-reference" data-footnote-reference=""
data-footnote-index="1" data-footnote-id="o6g991vkrwj" role="doc-noteref"
id="fnrefo6g991vkrwj"><sup><a href="#fno6g991vkrwj">[1]</a></sup></span>
</p>
<ol class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
data-footnote-id="o6g991vkrwj" role="doc-endnote" id="fno6g991vkrwj"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="o6g991vkrwj"><sup><strong><a href="#fnrefo6g991vkrwj">^</a></strong></sup></span>
<div
class="footnote-content" data-footnote-content="">
<p><a href="https://www.thecollector.com/what-are-the-seven-wonders-of-the-world/">What Are the 7 Wonders of the World? (with HD Images) | TheCollector</a>
</p>
</div>
</li>

View File

@@ -22,13 +22,16 @@
around 1450 in polished drystone walls.<span class="footnote-reference"
data-footnote-reference="" data-footnote-index="1" data-footnote-id="4prjheuho88"
role="doc-noteref" id="fnref4prjheuho88"><sup><a href="#fn4prjheuho88">[1]</a></sup></span>
</p>
<ol class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
data-footnote-id="4prjheuho88" role="doc-endnote" id="fn4prjheuho88"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="4prjheuho88"><sup><strong><a href="#fnref4prjheuho88">^</a></strong></sup></span>
<div
class="footnote-content" data-footnote-content="">
<p><a href="https://www.thecollector.com/what-are-the-seven-wonders-of-the-world/">What Are the 7 Wonders of the World? (with HD Images) | TheCollector</a>
</p>
</div>
</li>

View File

@@ -23,13 +23,16 @@
by earthquakes.<span class="footnote-reference" data-footnote-reference=""
data-footnote-index="1" data-footnote-id="ej5sd0bakne" role="doc-noteref"
id="fnrefej5sd0bakne"><sup><a href="#fnej5sd0bakne">[1]</a></sup></span>
</p>
<ol class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
data-footnote-id="ej5sd0bakne" role="doc-endnote" id="fnej5sd0bakne"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="ej5sd0bakne"><sup><strong><a href="#fnrefej5sd0bakne">^</a></strong></sup></span>
<div
class="footnote-content" data-footnote-content="">
<p><a href="https://www.thecollector.com/what-are-the-seven-wonders-of-the-world/">What Are the 7 Wonders of the World? (with HD Images) | TheCollector</a>
</p>
</div>
</li>

View File

@@ -26,14 +26,17 @@
<span
class="footnote-reference" data-footnote-reference="" data-footnote-index="1"
data-footnote-id="4kitkusvyi3" role="doc-noteref" id="fnref4kitkusvyi3"><sup><a href="#fn4kitkusvyi3">[1]</a></sup>
</span>
</p>
<ol class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
data-footnote-id="4kitkusvyi3" role="doc-endnote" id="fn4kitkusvyi3"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="4kitkusvyi3"><sup><strong><a href="#fnref4kitkusvyi3">^</a></strong></sup></span>
<div
class="footnote-content" data-footnote-content="">
<p><a href="https://www.thecollector.com/what-are-the-seven-wonders-of-the-world/">What Are the 7 Wonders of the World? (with HD Images) | TheCollector</a>
</p>
</div>
</li>

View File

@@ -23,14 +23,17 @@
<span
class="footnote-reference" data-footnote-reference="" data-footnote-index="1"
data-footnote-id="o0o2das7ljm" role="doc-noteref" id="fnrefo0o2das7ljm"><sup><a href="#fno0o2das7ljm">[1]</a></sup>
</span>
</p>
<ol class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
data-footnote-id="o0o2das7ljm" role="doc-endnote" id="fno0o2das7ljm"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="o0o2das7ljm"><sup><strong><a href="#fnrefo0o2das7ljm">^</a></strong></sup></span>
<div
class="footnote-content" data-footnote-content="">
<p><a href="https://www.thecollector.com/what-are-the-seven-wonders-of-the-world/">What Are the 7 Wonders of the World? (with HD Images) | TheCollector</a>
</p>
</div>
</li>

View File

@@ -23,13 +23,16 @@
the complex.<span class="footnote-reference" data-footnote-reference=""
data-footnote-index="1" data-footnote-id="zzzjn52iwk" role="doc-noteref"
id="fnrefzzzjn52iwk"><sup><a href="#fnzzzjn52iwk">[1]</a></sup></span>
</p>
<ol class="footnote-section footnotes" data-footnote-section="" role="doc-endnotes">
<li class="footnote-item" data-footnote-item="" data-footnote-index="1"
data-footnote-id="zzzjn52iwk" role="doc-endnote" id="fnzzzjn52iwk"><span class="footnote-back-link" data-footnote-back-link="" data-footnote-id="zzzjn52iwk"><sup><strong><a href="#fnrefzzzjn52iwk">^</a></strong></sup></span>
<div
class="footnote-content" data-footnote-content="">
<p><a href="https://www.thecollector.com/what-are-the-seven-wonders-of-the-world/">What Are the 7 Wonders of the World? (with HD Images) | TheCollector</a>
</p>
</div>
</li>

View File

@@ -0,0 +1,108 @@
import {
ActionButton, Button, LinkButton,
Admonition, Collapsible, FormGroup,
Dropdown, FormListItem, FormDropdownDivider, FormDropdownSubmenu,
NoteAutocomplete, NoteLink, Modal,
CKEditor,
useEffect, useState
} from "trilium:preact";
import { showMessage } from "trilium:api";
export default function() {
const [ time, setTime ] = useState();
const lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam accumsan eu odio non gravida. Pellentesque ornare, arcu condimentum molestie dignissim, nibh turpis ultrices elit, eget elementum nunc erat at erat. Maecenas vehicula consectetur elit, nec fermentum elit venenatis eu.";
useEffect(() => {
const interval = setInterval(() => setTime(new Date().toLocaleString()), 1000);
return () => clearInterval(interval);
}, []);
return (
<div style={{ padding: 20, display: "flex", flexDirection: "column", gap: "1em" }}>
<h1>Widget showcase</h1>
<Buttons />
<Admonition type="note">
<strong>Admonition</strong><br />
{lorem}
</Admonition>
<Collapsible title="Collapsible" initiallyExpanded>
{lorem}
</Collapsible>
<FormElements />
<NoteElements />
<ModalSample />
<DropdownSample />
</div>
);
}
function Buttons() {
const onClick = () => showMessage("A button was pressed");
return (
<>
<h2>Buttons</h2>
<div style={{ display: "flex", gap: "1em", alignItems: "center" }}>
<ActionButton icon="bx bx-rocket" text="Action button" onClick={onClick} />
<Button icon="bx bx-rocket" text="Simple button" onClick={onClick} />
<LinkButton text="Link button" onClick={onClick} />
</div>
</>
)
}
function NoteElements() {
const [ noteId, setNoteId ] = useState("");
return (
<div>
<h2>Note elements</h2>
<FormGroup name="note-autocomplete" label="Note autocomplete">
<NoteAutocomplete
placeholder="Select a note"
noteId={noteId} noteIdChanged={setNoteId}
/>
</FormGroup>
<FormGroup name="note-link" label="Note link">
{noteId
? <NoteLink notePath={noteId} showNoteIcon />
: <span>Select a note first</span>}
</FormGroup>
</div>
);
}
function ModalSample() {
const [ shown, setShown ] = useState(false);
return (
<>
<h2>Modal</h2>
<Button text="Open modal" onClick={() => setShown(true)} />
<Modal title="Modal title" size="md" show={shown} onHidden={() => setShown(false)}>
Modal goes here.
</Modal>
</>
)
}
function DropdownSample() {
return (
<>
<h2>Dropdown menu</h2>
<Dropdown text="Dropdown" hideToggleArrow>
<FormListItem icon="bx bx-cut">Cut</FormListItem>
<FormListItem icon="bx bx-copy">Copy</FormListItem>
<FormListItem icon="bx bx-paste">Paste</FormListItem>
<FormDropdownDivider />
<FormDropdownSubmenu title="Submenu">
<FormListItem>More items</FormListItem>
</FormDropdownSubmenu>
</Dropdown>
</>
)
}

View File

@@ -0,0 +1,84 @@
import {
useState,
FormCheckbox, FormDropdownList, FormFileUploadButton, FormGroup, FormRadioGroup, FormTextArea,
FormTextBox, FormToggle, Slider, RawHtml, LoadingSpinner, Icon,
} from "trilium:preact";
export default function FormElements() {
const [ checkboxChecked, setCheckboxChecked ] = useState(false);
const [ dropdownValue, setDropdownValue ] = useState("key-1");
const [ radioGroupValue, setRadioGroupValue ] = useState("key-1");
const [ sliderValue, setSliderValue ] = useState(50);
return (
<>
<h2>Form elements</h2>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "1em" }}>
<FormGroup name="checkbox" label="Checkbox">
<FormCheckbox label="Checkbox" currentValue={checkboxChecked} onChange={setCheckboxChecked} />
</FormGroup>
<FormGroup name="toggle" label="Toggle">
<FormToggle switchOnName="Off" switchOffName="On" currentValue={checkboxChecked} onChange={setCheckboxChecked} />
</FormGroup>
<FormGroup name="dropdown" label="Dropdown">
<FormDropdownList
values={[
{ key: "key-1", name: "First item" },
{ key: "key-2", name: "Second item" },
{ key: "key-3", name: "Third item" },
]}
currentValue={dropdownValue} onChange={setDropdownValue}
keyProperty="key" titleProperty="name"
/>
</FormGroup>
<FormGroup name="radio-group" label="Radio group">
<FormRadioGroup
values={[
{ value: "key-1", label: "First item" },
{ value: "key-2", label: "Second item" },
{ value: "key-3", label: "Third item" },
]}
currentValue={radioGroupValue} onChange={setRadioGroupValue}
/>
</FormGroup>
<FormGroup name="text-box" label="Text box">
<FormTextBox
placeholder="Type something..."
currentValue="" onChange={(newValue) => {}}
/>
</FormGroup>
<FormGroup name="text-area" label="Text area">
<FormTextArea
placeholder="Type something bigger..."
currentValue="" onChange={(newValue) => {}}
/>
</FormGroup>
<FormGroup name="slider" label="Slider">
<Slider
min={1} max={100}
value={sliderValue} onChange={setSliderValue}
/>
</FormGroup>
<FormGroup name="file-upload" label="File upload">
<FormFileUploadButton
text="Upload"
onChange={(files) => {
const file = files?.[0];
if (!file) return;
showMessage(`Got file "${file.name}" of size ${file.size} B and type ${file.type}.`);
}}
/>
</FormGroup>
<FormGroup name="icon" label="Icon">
<Icon icon="bx bx-smile" />
</FormGroup>
<FormGroup name="loading-spinner" label="Loading spinner">
<LoadingSpinner />
</FormGroup>
<FormGroup name="raw-html" label="Raw HTML">
<RawHtml html="<strong>Hi</strong> <em>there</em>" />
</FormGroup>
</div>
</>
)
}

View File

@@ -1,21 +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>chart.js</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>chart.js</h1>
<div class="ck-content">
<p>This is a clone of a note. Go to its <a href="../../../../../Weight%20Tracker/Implementation/JS%20code/chart.js">primary location</a>.</p>
</div>
</div>
</body>
</html>

View File

@@ -1,21 +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>chart.js</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>chart.js</h1>
<div class="ck-content">
<p>This is a clone of a note. Go to its <a href="../../../../../../Weight%20Tracker/Implementation/JS%20code/chart.js">primary location</a>.</p>
</div>
</div>
</body>
</html>

View File

@@ -18,6 +18,7 @@
width="209" height="300">
</figure>
<p>Maybe CodeNames? <a href="https://boardgamegeek.com/boardgame/178900/codenames">https://boardgamegeek.com/boardgame/178900/codenames</a>
</p>
</div>
</div>

View File

@@ -14,6 +14,7 @@
<div class="ck-content">
<p><a href="https://en.wikipedia.org/wiki/The_Black_Swan:_The_Impact_of_the_Highly_Improbable">https://en.wikipedia.org/wiki/The_Black_Swan:_The_Impact_of_the_Highly_Improbable</a>
</p>
<p><em><strong>The Black Swan: The Impact of the Highly Improbable</strong></em> is
a 2007 book by author and former <a href="https://en.wikipedia.org/wiki/Options_trader">options trader</a>

View File

@@ -25,6 +25,7 @@
and <a href="https://en.wikipedia.org/wiki/Apple_Inc.">Apple's</a> <a href="https://en.wikipedia.org/wiki/MacOS">macOS</a> (formerly
OS X). A version <a href="https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux">is also available for Windows 10</a>.</p>
<p><a href="https://en.wikipedia.org/wiki/Bash_(Unix_shell)">Bash on Wikipedia</a>
</p>
</div>
</div>

View File

@@ -14,6 +14,7 @@
<div class="ck-content">
<h3>Login shell</h3>
<p>As a "login shell", Bash reads and sets (executes) the user's profile
from /etc/profile and one of ~/.bash_profile, ~/.bash_login, or ~/.profile
(in that order, using the first one that's readable!).</p>
@@ -23,6 +24,7 @@
that only make sense for the initial user login. That's why all UNIX® shells
have (should have) a "login" mode.</p>
<p><em><strong>Methods to start Bash as a login shell:</strong></em>
</p>
<ul>
<li>the first character of argv[0] is - (a hyphen): traditional UNIX® shells
@@ -31,17 +33,20 @@
<li>Bash is started with the --login option</li>
</ul>
<p><em><strong>Methods to test for login shell mode:</strong></em>
</p>
<ul>
<li>the shell option <a href="http://wiki.bash-hackers.org/internals/shell_options#login_shell">login_shell</a> is
set</li>
</ul>
<p><em><strong>Related switches:</strong></em>
</p>
<ul>
<li>--noprofile disables reading of all profile files</li>
</ul>
<h3>Interactive shell</h3>
<p>When Bash starts as an interactive non-login shell, it reads and executes
commands from ~/.bashrc. This file should contain, for example, aliases,
since they need to be defined in every shell as they're not inherited from
@@ -51,11 +56,13 @@
The classic way to have a system-wide rc file is to source /etc/bashrc
from every user's ~/.bashrc.</p>
<p><em><strong>Methods to test for interactive-shell mode:</strong></em>
</p>
<ul>
<li>the special parameter $- contains the letter i (lowercase I)</li>
</ul>
<p><em><strong>Related switches:</strong></em>
</p>
<ul>
<li>-i forces the interactive mode</li>
@@ -65,6 +72,7 @@
~/.bashrc)</li>
</ul>
<h3>SH mode</h3>
<p>When Bash starts in SH compatiblity mode, it tries to mimic the startup
behaviour of historical versions of sh as closely as possible, while conforming
to the POSIX® standard as well. The profile files read are /etc/profile
@@ -74,6 +82,7 @@
file.</p>
<p>After the startup files are read, Bash enters the <a href="http://wiki.bash-hackers.org/scripting/bashbehaviour#posix_run_mode">POSIX(r) compatiblity mode (for running, not for starting!)</a>.</p>
<p><em><strong>Bash starts in sh compatiblity mode when:</strong></em>
</p>
<ul>
<li>

View File

@@ -14,6 +14,7 @@
<div class="ck-content">
<p>Documentation: <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html">http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html</a>
</p><pre><code class="language-text-x-sh">#!/bin/bash

View File

@@ -20,6 +20,7 @@
href="https://en.wikipedia.org/wiki/Node.js#cite_note-b1-31">[31]</a>Developers can create scalable servers without using <a href="https://en.wikipedia.org/wiki/Thread_(computing)">threading</a>,
by using a simplified model of <a href="https://en.wikipedia.org/wiki/Event-driven_programming">event-driven programming</a> that
uses callbacks to signal the completion of a task.<a href="https://en.wikipedia.org/wiki/Node.js#cite_note-b1-31">[31]</a>
</p>
</div>
</div>

View File

@@ -550,9 +550,9 @@
}
@media print{
.ck-content figure.table:not(.layout-table):not(:has(> figcaption)){
display:block;
.ck-content figure.table:not(.layout-table){
width:fit-content;
height:fit-content;
}
.ck-content figure.table:not(.layout-table) > table{
height:initial;
@@ -601,4 +601,33 @@
.ck-content .table td,
.ck-content .table th{
overflow-wrap:break-word;
}
}
:root{
--ck-content-table-style-spacing:1.5em;
}
.ck-content .table.table-style-align-left{
float:left;
margin-right:var(--ck-content-table-style-spacing);
}
.ck-content .table.table-style-align-right{
float:right;
margin-left:var(--ck-content-table-style-spacing);
}
.ck-content .table.table-style-align-center{
margin-left:auto;
margin-right:auto;
}
.ck-content .table.table-style-block-align-left{
margin-left:0;
margin-right:auto;
}
.ck-content .table.table-style-block-align-right{
margin-left:auto;
margin-right:0;
}

View File

@@ -30,7 +30,8 @@
"dependencies": {
"better-sqlite3": "12.5.0",
"html-to-text": "9.0.5",
"node-html-parser": "7.0.1"
"node-html-parser": "7.0.1",
"sucrase": "3.35.1"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.71.2",

Binary file not shown.

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More