Compare commits

..

147 Commits

Author SHA1 Message Date
Elian Doran
3bea6af20e chore(release): 0.92.1-beta 2025-02-22 12:58:30 +02:00
Elian Doran
3c83112240 Merge pull request #1250 from TriliumNext/feat_typescript-codenote
feat(options_init): add typescript as default set codeNotesMimeTypes value
2025-02-22 12:55:40 +02:00
Elian Doran
4e876ed24d fix(build): build errors after changing meta data type 2025-02-22 12:52:25 +02:00
Elian Doran
94ce01bbc2 chore(docs): update documentation 2025-02-22 12:49:09 +02:00
Elian Doran
411e3dfa0e server(export): export Markdown using ATX heading syntax (closes #1251) 2025-02-22 12:45:21 +02:00
Elian Doran
a1bfc6aae7 feat(hidden_subtree): update help note icons 2025-02-22 12:31:39 +02:00
Elian Doran
97bc103e76 feat(in_app_help): support folder icons 2025-02-22 12:30:47 +02:00
Panagiotis Papadopoulos
9da1f55409 feat(options_init): add typescript as default set codeNotesMimeTypes value
since it is a very widespread/popular language, it makes sense to have it on by default.

closes #1219
2025-02-22 11:19:32 +01:00
Elian Doran
017fba518d feat(in_app_help): remove help notes that are no longer present in structure 2025-02-22 12:09:11 +02:00
Elian Doran
63584c153c feat(views/calendar): filter notes by ancestor 2025-02-22 11:31:36 +02:00
Adorian Doran
a4a2e55415 Merge branch 'develop' of https://github.com/TriliumNext/Notes into develop 2025-02-22 11:13:24 +02:00
Adorian Doran
4a1691ac31 style(next): promoted attributes widget: update narrow view breakpoint 2025-02-22 11:13:20 +02:00
Elian Doran
5a8d5c59f5 feat(views/calendar): support workspaceCalendarRoot 2025-02-22 10:59:03 +02:00
Elian Doran
a9cebe312f refactor(views/calendar): use specific API for date notes for performance 2025-02-22 10:59:03 +02:00
Elian Doran
43f79ca813 feat(views/calendar): click to go to day note 2025-02-22 10:59:03 +02:00
Elian Doran
5c1db3cab2 feat(views/calendar): disable interaction in calendar root mode 2025-02-22 10:59:03 +02:00
Elian Doran
bc4d820cb0 feat(views/calendar): display child notes of days 2025-02-22 10:59:03 +02:00
Elian Doran
f4e6edd19e feat(views/calendar): basic recursion for calendar root 2025-02-22 10:59:03 +02:00
Elian Doran
530340f753 fix(client): sporadious crash with classic toolbar not being available 2025-02-22 10:59:03 +02:00
Adorian Doran
fcc1068b06 style(next): promoted attributes widget: improve appearance 2025-02-22 10:55:01 +02:00
Adorian Doran
6d19e315f4 style(next): promoted attributes widget: improve appearance on narrow width 2025-02-22 10:19:26 +02:00
Adorian Doran
7269c1b0aa style(next): promoted attributes widget: improve appearance 2025-02-22 10:10:58 +02:00
Adorian Doran
8e69cf79a6 client: promoted attributes widget: add placeholder for blank input fields 2025-02-22 10:05:14 +02:00
Elian Doran
46f543ad54 Merge pull request #1207 from maphew/bare2share2
Bare2share - Redirect bare domain to defined #shareRoot
2025-02-22 09:25:30 +02:00
Elian Doran
38dbf6efcd Merge pull request #1220 from TriliumNext/feat_friendly-numbers-note-revision
feat: friendly numbers note revision
2025-02-22 09:24:37 +02:00
Elian Doran
8e68ddafd5 Merge pull request #1239 from TriliumNext/i18n_use-empty-string-instead-of-english
i18n: use empty string instead of English string
2025-02-22 09:24:14 +02:00
Elian Doran
0c43b387ce Merge pull request #1228 from TriliumNext/feat_friendly-numbers-protected_session_timeout
feat: friendly numbers protected session timeout
2025-02-22 09:23:23 +02:00
Elian Doran
8aa560eb82 Merge pull request #1243 from TriliumNext/renovate/typedoc-0.x
chore(deps): update dependency typedoc to v0.27.8
2025-02-22 09:22:23 +02:00
Elian Doran
3a2b8e9791 Merge pull request #1242 from TriliumNext/renovate/node-22.x
chore(deps): update dependency @types/node to v22.13.5
2025-02-22 09:22:09 +02:00
Elian Doran
468b3b6027 Merge pull request #1244 from TriliumNext/renovate/swagger-ui-express-4.x
fix(deps): update dependency @types/swagger-ui-express to v4.1.8
2025-02-22 09:21:48 +02:00
Elian Doran
f872073f65 Merge pull request #1245 from TriliumNext/renovate/ws-8.x
fix(deps): update dependency ws to v8.18.1
2025-02-22 09:21:23 +02:00
Elian Doran
ef4fc0a180 feat(html_sanitizer): enforce strict style rules (closes #931) 2025-02-22 09:18:47 +02:00
Adorian Doran
1d47df5f28 style(next): forms/input groups: improve CSS 2025-02-22 02:37:13 +02:00
Adorian Doran
77264b5385 style(next): forms/input groups: fix icons being skewed in certain conditions 2025-02-22 02:35:24 +02:00
Adorian Doran
41e925dc94 style(next): forms: add support for date and time input fields 2025-02-22 02:31:20 +02:00
Adorian Doran
789178061b style(next): promoted attributes widget: improve CSS, add right margin 2025-02-22 02:21:19 +02:00
renovate[bot]
8011969b9d fix(deps): update dependency ws to v8.18.1 2025-02-22 00:06:30 +00:00
renovate[bot]
9ab2fe85bd fix(deps): update dependency @types/swagger-ui-express to v4.1.8 2025-02-22 00:06:21 +00:00
renovate[bot]
6c818427fc chore(deps): update dependency typedoc to v0.27.8 2025-02-22 00:06:14 +00:00
renovate[bot]
7dab171a0c chore(deps): update dependency @types/node to v22.13.5 2025-02-22 00:06:04 +00:00
Adorian Doran
c680c3476b Merge branch 'develop' of https://github.com/TriliumNext/Notes into develop 2025-02-22 02:05:22 +02:00
Adorian Doran
bf0b6ce554 style(next): promoted attributes widget: improve appearance 2025-02-22 02:05:19 +02:00
Elian Doran
bedc61c3d0 feat(import/zip): support UTF-16 LE with BOM (closes #1241) 2025-02-22 01:37:22 +02:00
Elian Doran
c925ae5f15 feat(import/single): support UTF-16 LE with BOM for markdown notes 2025-02-22 01:37:22 +02:00
Elian Doran
77ee7f96c1 feat(import/single): support UTF-16 LE with BOM for text notes 2025-02-22 01:37:22 +02:00
Elian Doran
cadd78524c feat(import/single): support UTF-16 LE with BOM for code notes 2025-02-22 01:37:22 +02:00
Elian Doran
fd4f35e879 feat(import/single): support UTF-16 LE with BOM for HTML 2025-02-22 01:37:22 +02:00
Adorian Doran
39f00bd568 style(next): promoted attributes widget: tweak input groups 2025-02-22 01:35:10 +02:00
Adorian Doran
ee2d4c6830 style(next): promoted attributes widget: tweak checkboxes 2025-02-22 01:30:55 +02:00
Adorian Doran
e93d47f664 style(next): promoted attributes widget: tweak buttons 2025-02-22 00:53:24 +02:00
Adorian Doran
0c88c4c3ee style(next): partially restyle the promoted attributes widget 2025-02-21 21:31:26 +02:00
Elian Doran
81bdd57398 feat(views/calendar): display event icon 2025-02-21 18:40:54 +02:00
Elian Doran
fe5182ebc6 feat(views/calendar): add option to show week numbers 2025-02-21 17:56:34 +02:00
Elian Doran
42d46bdb72 feat(views/calendar): add option to hide weekends 2025-02-21 17:56:34 +02:00
Elian Doran
710cf68c06 feat(views/calendar): make calendar full height 2025-02-21 17:56:34 +02:00
Elian Doran
300bb561bb Merge pull request #1240 from rom1dep/calendar_enh_color
feat(view/calendar): support coloring
2025-02-21 17:56:24 +02:00
Panagiotis Papadopoulos
96961898ca i18n: use empty string instead of English string
it will fallback to the English string by default
2025-02-21 08:04:43 +01:00
Panagiotis Papadopoulos
1520913686 i18n: use empty string instead of English string
it will fallback to the English string by default
2025-02-21 07:57:07 +01:00
Panagiotis Papadopoulos
6fae7a98f5 i18n: use empty string instead of English string
it will fallback to the English string by default
2025-02-21 07:41:50 +01:00
Elian Doran
57dc168c26 Merge pull request #1223 from TriliumNext/feat_timeSelector-min-input
feat: time selector add support for handling min required input
2025-02-20 22:49:14 +02:00
Elian Doran
946d9aee40 feat(settings/share): add subtitles 2025-02-20 22:34:51 +02:00
Elian Doran
16b16927ef feat(settings/share): add title to section 2025-02-20 22:08:04 +02:00
Elian Doran
c2e4def523 Merge branch 'develop' into feat_friendly-numbers-note-revision 2025-02-20 21:45:49 +02:00
Elian Doran
1e11625f14 Merge branch 'develop' into feat_friendly-numbers-protected_session_timeout 2025-02-20 21:45:30 +02:00
Elian Doran
5495677fc2 Merge branch 'develop' into feat_timeSelector-min-input 2025-02-20 21:45:24 +02:00
Elian Doran
2cefdf8b9f Merge pull request #1229 from McSido/feature/colorful-marker-icons
Enhance geomap marker icons with color
2025-02-20 21:43:42 +02:00
Elian Doran
57b3035559 feat(geomap): refresh map if color attribute is changed 2025-02-20 21:39:35 +02:00
Elian Doran
4f84ad8b81 Merge pull request #1230 from TriliumNext/renovate/tsx-4.x
chore(deps): update dependency tsx to v4.19.3
2025-02-20 21:31:28 +02:00
Elian Doran
1626767f30 Merge pull request #1231 from TriliumNext/renovate/vitest-monorepo
chore(deps): update vitest monorepo to v3.0.6
2025-02-20 21:30:57 +02:00
Elian Doran
91003af092 Merge pull request #1232 from TriliumNext/renovate/electron-forge-monorepo
chore(deps): update electron-forge monorepo to v7.7.0
2025-02-20 21:30:11 +02:00
Elian Doran
860de346a7 feat(import/single): treat mdx as markdown 2025-02-20 20:38:58 +02:00
Elian Doran
324a3d0d8b feat(import): trim .mdx extension from import 2025-02-20 20:29:00 +02:00
Elian Doran
f9e4ae7210 feat(import/zip): treat mdx as markdown (closes #1236) 2025-02-20 20:25:42 +02:00
matt wilkie
bdd6395a76 works! verify shareRoot is set and note is shared 2025-02-20 09:08:24 -07:00
Elian Doran
1c118f2aa9 feat(startup): display migration errors using system modal 2025-02-20 18:06:19 +02:00
Elian Doran
4010cb2789 Merge branch 'develop' into renovate/electron-forge-monorepo 2025-02-20 17:35:40 +02:00
Elian Doran
f83beafd76 Merge branch 'develop' into renovate/vitest-monorepo 2025-02-20 17:35:38 +02:00
Elian Doran
366264f3a9 Merge branch 'develop' into renovate/tsx-4.x 2025-02-20 17:35:35 +02:00
Elian Doran
ba91ed1855 Merge branch 'develop' into feature/colorful-marker-icons 2025-02-20 17:34:54 +02:00
matt wilkie
2734e230ab WIP: 1st step at verifying shareRoot is set 2025-02-20 08:12:51 -07:00
Elian Doran
ca1d5207d8 fix(build): try using ARM runner for building docker 2025-02-20 17:10:16 +02:00
Elian Doran
f6b6b2e740 feat(test): ensure backend translations are valid JSON 2025-02-20 12:42:42 +02:00
Elian Doran
c255af67c9 fix(i18n): Chinese translations missing due to invalid JSON 2025-02-20 12:40:11 +02:00
Elian Doran
34b4e6d069 feat(test): ensure frontend translations are valid JSON 2025-02-20 12:39:56 +02:00
Elian Doran
4e01534d76 refactor(i18n): move list of locales out of options 2025-02-20 12:27:33 +02:00
renovate[bot]
fa05f15753 chore(deps): update electron-forge monorepo to v7.7.0 2025-02-20 02:57:15 +00:00
renovate[bot]
68c7df797d chore(deps): update vitest monorepo to v3.0.6 2025-02-20 02:57:00 +00:00
renovate[bot]
587a051430 chore(deps): update dependency tsx to v4.19.3 2025-02-20 02:56:46 +00:00
McSido
04a6175630 feat(geomap): enhance icon building to include color class 2025-02-19 22:52:38 +01:00
Panagiotis Papadopoulos
bf6c5dfb20 i18n: adjust protected_session_timeout_label
we are not limited to seconds now anymore (at least in the UI :-))
2025-02-19 22:45:07 +01:00
Panagiotis Papadopoulos
19816493d6 chore: rename ProtectedSessionTimeoutOption to plural
matches the remaining Options widgets
2025-02-19 22:37:08 +01:00
Panagiotis Papadopoulos
cecde349b7 feat: add ProtectedSessionTimeoutOption to content_widget 2025-02-19 22:35:02 +01:00
Panagiotis Papadopoulos
1a80a379dc refactor(password): move password.ts to password subfolder 2025-02-19 22:32:48 +01:00
Panagiotis Papadopoulos
739eaf9fc0 refactor(password): remove protected_session_timeout TPL element
-> now part of protected_session_timeout widget
2025-02-19 22:27:35 +01:00
Panagiotis Papadopoulos
7f173b287a feat(protected_session_timeout): add minimumSeconds 2025-02-19 22:21:49 +01:00
Matt Wilkie
5a6c3ae426 use the standard classes
per @pano9000 advice, https://github.com/TriliumNext/Notes/pull/1207#issuecomment-2667896424
2025-02-19 08:33:03 -07:00
Elian Doran
6b5d905ebe fix(client/file): flicker when too big alert is not needed 2025-02-19 13:43:50 +02:00
Elian Doran
61f2e35717 feat(client/file): trim big files in order to improve performance 2025-02-19 13:41:05 +02:00
Elian Doran
4a34d5b2df Merge pull request #1227 from greg1904/feat/german_translation
feat/german translation - adding missing translations from base english and adding adjustments of inconsistent german translations
2025-02-19 09:39:37 +02:00
greg1904
549917c1f1 small consistency adjustment 2025-02-18 23:56:15 +01:00
greg1904
4ed3a28e29 german adjustments 2025-02-18 23:49:27 +01:00
greg1904
c261bf7f7a Kind --> Unternotiz and some other additions 2025-02-18 23:30:14 +01:00
greg1904
62c9e865f5 added some fields in translation.json + switched "Hinweis" to "Notiz" 2025-02-18 23:12:57 +01:00
Panagiotis Papadopoulos
bd75a26803 feat: use TimeSelector for protected_session_timeout
to be used in the Password options
2025-02-18 22:37:20 +01:00
Panagiotis Papadopoulos
9a1d26e129 feat: add protectedSessionTimeoutScale option
to be used in the protected_session_timeout widget, when it is ported to use TimeSelector
2025-02-18 22:37:20 +01:00
Panagiotis Papadopoulos
bf41c54bd0 feat: set TimeSelector minimumSeconds for revision_snapshot_interval and remove TODO 2025-02-18 22:32:43 +01:00
Panagiotis Papadopoulos
28148b32d2 i18n: adjust labels for update revision_snapshot_interval options
we don't do seconds only anymore, so strings needed adjusting
2025-02-18 22:29:56 +01:00
Panagiotis Papadopoulos
eb08a976dd feat: use TimeSelector for revision_snapshot_interval
added one TODO that needs to be handled in TimeSelector: minimum allowed value (!in seconds!)
2025-02-18 22:29:56 +01:00
Panagiotis Papadopoulos
eeb99cf37c feat: add revisionSnapshotTimeIntervalTimeScale option
to be used in the revision_snapshot_interval widget, when it is ported to use TimeSelector
2025-02-18 22:29:56 +01:00
Panagiotis Papadopoulos
a0c2715980 i18n(time_selector): add i18n for minimum_input 2025-02-18 22:26:32 +01:00
greg1904
e35ff07b9b Launchbar --> Startleiste 2025-02-18 22:25:05 +01:00
Panagiotis Papadopoulos
ce1f418aa7 feat(time_selector): make use of optionsService 2025-02-18 22:18:58 +01:00
greg1904
fbc4206908 adding new fields for launchbar to german translation 2025-02-18 22:18:55 +01:00
Panagiotis Papadopoulos
bb4c3ae6ff feat(time_selector): add minimumSeconds handling 2025-02-18 22:18:11 +01:00
Panagiotis Papadopoulos
0332ade13c refactor(time_selector): simplify setting of includedTimeScales 2025-02-18 22:16:13 +01:00
Panagiotis Papadopoulos
2d968b8e9c refactor(time_selector): mark methods as private 2025-02-18 22:15:36 +01:00
Panagiotis Papadopoulos
fd2c65dcc0 feat(time_selector): add minimumSeconds class property 2025-02-18 22:14:56 +01:00
Matt Wilkie
aab35955bf remove duplicated sync_2 2025-02-17 16:45:47 -07:00
Romain DEP.
bcb40b531f feat(view/calendar): support coloring 2025-02-17 22:20:20 +01:00
Matt Wilkie
32bb43f9c1 fix indent 2025-02-17 13:55:55 -07:00
Matt Wilkie
86ab2d4008 Merge remote-tracking branch 'origin/develop' into bare2share2
# Conflicts:
#	src/public/translations/en/translation.json
2025-02-17 13:54:40 -07:00
Matt Wilkie
657638ee54 responding to code review (thanks pano!) 2025-02-17 13:46:03 -07:00
Elian Doran
50d37bbcb1 chore(client/ts): port note_type 2025-02-17 22:28:54 +02:00
Elian Doran
6706332be3 Merge pull request #1204 from TriliumNext/feat_timeScaleSelectWidget
feat: add time selector option widget
2025-02-17 22:23:29 +02:00
Matt Wilkie
2ec2d784ec results of npx prettier 2025-02-17 13:19:55 -07:00
Elian Doran
16caae191e fix(geomap): stuck viewport and zoom when switching between two geomaps 2025-02-17 21:59:02 +02:00
Elian Doran
c7d75b759c feat(webview): remove disclaimer 2025-02-17 21:50:59 +02:00
Elian Doran
b837c57d06 chore(lock): update 2025-02-17 21:49:34 +02:00
Elian Doran
ef3a75d58e feat(webview): set up some sandboxing for <iframe> 2025-02-17 21:49:24 +02:00
Elian Doran
59b474df35 feat(webview): render in browser using iframe 2025-02-17 21:26:00 +02:00
Elian Doran
fd47412d51 chore(client/ts): port web_view 2025-02-17 21:22:58 +02:00
Elian Doran
237f2ead73 fix(ci): server platform name missing due to invalid input 2025-02-17 21:16:53 +02:00
Elian Doran
558bee72e9 fix(ci): add missing zip for Windows on nightly 2025-02-17 21:15:12 +02:00
Elian Doran
ed082f34d5 fix(ci): add missing zip for Windows 2025-02-17 21:01:06 +02:00
Elian Doran
fabafeac86 fix(ci): missing os name in server release artifact 2025-02-17 19:53:25 +02:00
Panagiotis Papadopoulos
d26d668741 feat(time_selector): avoid "template concatenation" 2025-02-17 09:00:44 +01:00
Panagiotis Papadopoulos
fc8f805b28 i18n(time_selector): adjust attachment erasure message
as we now are allowing entering the time in other formats (minutes, hours, days) – it doesn't make sense for the label to say "after X seconds".
2025-02-17 06:52:29 +01:00
matt wilkie
ed8b8e50a4 feature complete; tested and working on local linux machine 2025-02-16 22:47:50 -07:00
matt wilkie
bc66e98533 okay, we can start npm server now, but new db redirects to share 2025-02-16 22:17:59 -07:00
matt wilkie
7af4e52766 brought over changes from 879035d
The last known good state before I got sidetracked into docker changes
2025-02-16 21:29:38 -07:00
Panagiotis Papadopoulos
ccbed7bbc3 feat(time_selector): use showError since this is an error 2025-02-16 18:30:56 +01:00
Panagiotis Papadopoulos
10ba467202 chore(prettier): run prettier on time_selector related files 2025-02-16 18:30:56 +01:00
Panagiotis Papadopoulos
793b0c9fe8 feat(time_selector): add possibility to omit time scales 2025-02-16 18:30:56 +01:00
Panagiotis Papadopoulos
35a3d326f7 i18n(time_selector): translate invalid_input message 2025-02-16 18:30:56 +01:00
Panagiotis Papadopoulos
f672054441 feat(time_selector): use time_selector in attachment_erasure_timeout 2025-02-16 18:30:56 +01:00
Panagiotis Papadopoulos
e0e530b219 feat(time_selector): use time_selector in note_erasure_timeout 2025-02-16 18:30:56 +01:00
Panagiotis Papadopoulos
c0714a92d5 feat(time_selector): add time_selector options widget 2025-02-16 18:30:56 +01:00
133 changed files with 3329 additions and 1488 deletions

View File

@@ -1,4 +1,7 @@
inputs: inputs:
os:
description: "One of the supported platforms: windows"
required: true
arch: arch:
description: "The architecture to build for: x64, arm64" description: "The architecture to build for: x64, arm64"
required: true required: true

View File

@@ -33,7 +33,7 @@ jobs:
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set IMAGE_NAME to lowercase - name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase - name: Set TEST_TAG to lowercase
@@ -47,16 +47,16 @@ jobs:
with: with:
node-version: 20 node-version: 20
cache: "npm" cache: "npm"
- name: Install npm dependencies - name: Install npm dependencies
run: npm ci run: npm ci
- name: Install Playwright Browsers - name: Install Playwright Browsers
run: npx playwright install --with-deps run: npx playwright install --with-deps
- name: Run the TypeScript build - name: Run the TypeScript build
run: npx tsc run: npx tsc
- name: Create server-package.json - name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json run: cat package.json | grep -v electron > server-package.json
@@ -69,12 +69,12 @@ jobs:
tags: ${{ env.TEST_TAG }} tags: ${{ env.TEST_TAG }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
- name: Validate container run output - name: Validate container run output
run: | run: |
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }}) CONTAINER_ID=$(docker run -d --log-driver=journald --rm --network=host -e TRILIUM_PORT=8082 --volume ./integration-tests/db:/home/node/trilium-data --name trilium_local ${{ env.TEST_TAG }})
echo "Container ID: $CONTAINER_ID" echo "Container ID: $CONTAINER_ID"
- name: Wait for the healthchecks to pass - name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v3 uses: stringbean/docker-healthcheck-action@v3
with: with:
@@ -82,7 +82,7 @@ jobs:
wait-time: 50 wait-time: 50
require-status: running require-status: running
require-healthy: true require-healthy: true
- name: Run Playwright tests - name: Run Playwright tests
run: TRILIUM_DOCKER=1 npx playwright test run: TRILIUM_DOCKER=1 npx playwright test
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
@@ -100,7 +100,20 @@ jobs:
build: build:
name: Build Docker images name: Build Docker images
runs-on: ubuntu-latest strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
image: ubuntu-latest
- dockerfile: Dockerfile
platform: linux/arm64
image: ubuntu-24.04-arm
- dockerfile: Dockerfile
platform: linux/arm/v7
image: ubuntu-24.04-arm
runs-on: ${{ matrix.image }}
needs: needs:
- test_docker - test_docker
permissions: permissions:
@@ -108,16 +121,6 @@ jobs:
packages: write packages: write
attestations: write attestations: write
id-token: write id-token: write
strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
- dockerfile: Dockerfile
platform: linux/arm64
- dockerfile: Dockerfile
platform: linux/arm/v7
steps: steps:
- name: Prepare - name: Prepare
run: | run: |
@@ -144,13 +147,13 @@ jobs:
type=sha type=sha
flavor: | flavor: |
latest=false latest=false
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@@ -169,14 +172,14 @@ jobs:
registry: ${{ env.GHCR_REGISTRY }} registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ${{ env.DOCKERHUB_REGISTRY }} registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push by digest - name: Build and push by digest
id: build id: build
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
@@ -186,13 +189,13 @@ jobs:
platforms: ${{ matrix.platform }} platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest - name: Export digest
run: | run: |
mkdir -p /tmp/digests mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}" digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}" touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -220,7 +223,7 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
@@ -237,14 +240,14 @@ jobs:
registry: ${{ env.GHCR_REGISTRY }} registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ${{ env.DOCKERHUB_REGISTRY }} registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push - name: Create manifest list and push
working-directory: /tmp/digests working-directory: /tmp/digests
run: | run: |
@@ -255,7 +258,7 @@ jobs:
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \ -t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) $(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \ -t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) $(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
@@ -267,25 +270,25 @@ jobs:
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \ -t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) $(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \ -t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) $(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Small delay to ensure stable tag is fully propagated # Small delay to ensure stable tag is fully propagated
sleep 5 sleep 5
# Now update latest tags # Now update latest tags
docker buildx imagetools create \ docker buildx imagetools create \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ -t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
docker buildx imagetools create \ docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ -t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
fi fi
- name: Inspect image - name: Inspect image
run: | run: |
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

View File

@@ -29,7 +29,7 @@ jobs:
extension: [deb, rpm, zip, flatpak] extension: [deb, rpm, zip, flatpak]
- name: windows - name: windows
image: windows-latest image: windows-latest
extension: exe extension: [exe, zip]
runs-on: ${{ matrix.os.image }} runs-on: ${{ matrix.os.image }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -26,7 +26,7 @@ jobs:
extension: [deb, rpm, zip, flatpak] extension: [deb, rpm, zip, flatpak]
- name: windows - name: windows
image: windows-latest image: windows-latest
extension: exe extension: [exe, zip]
runs-on: ${{ matrix.os.image }} runs-on: ${{ matrix.os.image }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -75,6 +75,7 @@ jobs:
- name: Run the build - name: Run the build
uses: ./.github/actions/build-server uses: ./.github/actions/build-server
with: with:
os: linux
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
- name: Publish release - name: Publish release

View File

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

898
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "TriliumNext Notes", "productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes", "description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.92.0-beta", "version": "0.92.1-beta",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "./dist/electron-main.js", "main": "./dist/electron-main.js",
"author": { "author": {
@@ -26,7 +26,6 @@
"server:start-test": "npm run server:switch && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts", "server:start-test": "npm run server:switch && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts",
"server:qstart": "npm run server:switch && npm run server:start", "server:qstart": "npm run server:switch && npm run server:start",
"server:switch": "rimraf ./node_modules/better-sqlite3 && npm install", "server:switch": "rimraf ./node_modules/better-sqlite3 && npm install",
"electron:start": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./electron-main.ts --inspect=5858 .", "electron:start": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./electron-main.ts --inspect=5858 .",
"electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev electron --inspect=5858 .", "electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev electron --inspect=5858 .",
"electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"", "electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
@@ -37,30 +36,23 @@
"electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"", "electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"electron:qstart": "npm run electron:switch && npm run electron:start", "electron:qstart": "npm run electron:switch && npm run electron:start",
"electron:switch": "electron-rebuild", "electron:switch": "electron-rebuild",
"electron-forge:start": "npm run build:prepare-dist && electron-forge start", "electron-forge:start": "npm run build:prepare-dist && electron-forge start",
"electron-forge:make": "npm run build:prepare-dist && electron-forge make", "electron-forge:make": "npm run build:prepare-dist && electron-forge make",
"electron-forge:package": "npm run build:prepare-dist && electron-forge package", "electron-forge:package": "npm run build:prepare-dist && electron-forge package",
"docs:build-backend": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts", "docs:build-backend": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts",
"docs:build-frontend": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js", "docs:build-frontend": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"docs:build": "npm run docs:build-backend && npm run docs:build-frontend", "docs:build": "npm run docs:build-backend && npm run docs:build-frontend",
"build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts", "build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts",
"build:prepare-dist": "npm run build:webpack && rimraf ./dist && tsc && tsx ./bin/copy-dist.ts", "build:prepare-dist": "npm run build:webpack && rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
"test": "cross-env TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest",
"test": "cross-env TRILIUM_DATA_DIR=./integration-tests/db vitest",
"test:coverage": "cross-env TRILIUM_DATA_DIR=./integration-tests/db vitest --coverage", "test:coverage": "cross-env TRILIUM_DATA_DIR=./integration-tests/db vitest --coverage",
"test:playwright": "playwright test", "test:playwright": "playwright test",
"test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test:integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "test:integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"dev:watch-dist": "tsx ./bin/watch-dist.ts", "dev:watch-dist": "tsx ./bin/watch-dist.ts",
"dev:prettier-check": "prettier . --check", "dev:prettier-check": "prettier . --check",
"dev:prettier-fix": "prettier . --write", "dev:prettier-fix": "prettier . --write",
"chore:update-build-info": "tsx bin/update-build-info.ts", "chore:update-build-info": "tsx bin/update-build-info.ts",
"chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts", "chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts",
"chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000", "chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
@@ -81,7 +73,7 @@
"@types/js-yaml": "4.0.9", "@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.16", "@types/leaflet": "1.9.16",
"@types/react-dom": "18.3.5", "@types/react-dom": "18.3.5",
"@types/swagger-ui-express": "4.1.7", "@types/swagger-ui-express": "4.1.8",
"archiver": "7.0.1", "archiver": "7.0.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
@@ -89,6 +81,7 @@
"better-sqlite3": "11.8.1", "better-sqlite3": "11.8.1",
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"boxicons": "2.1.4", "boxicons": "2.1.4",
"chardet": "2.0.0",
"cheerio": "1.0.0", "cheerio": "1.0.0",
"chokidar": "4.0.3", "chokidar": "4.0.3",
"cls-hooked": "4.2.2", "cls-hooked": "4.2.2",
@@ -155,6 +148,7 @@
"source-map-support": "0.5.21", "source-map-support": "0.5.21",
"split.js": "1.6.5", "split.js": "1.6.5",
"stream-throttle": "0.1.3", "stream-throttle": "0.1.3",
"strip-bom": "5.0.0",
"striptags": "3.2.0", "striptags": "3.2.0",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"tmp": "0.2.3", "tmp": "0.2.3",
@@ -162,19 +156,19 @@
"turndown": "7.2.0", "turndown": "7.2.0",
"unescape": "1.0.1", "unescape": "1.0.1",
"vanilla-js-wheel-zoom": "9.0.4", "vanilla-js-wheel-zoom": "9.0.4",
"ws": "8.18.0", "ws": "8.18.1",
"xml2js": "0.6.2", "xml2js": "0.6.2",
"yauzl": "3.2.0" "yauzl": "3.2.0"
}, },
"devDependencies": { "devDependencies": {
"@electron-forge/cli": "7.6.1", "@electron-forge/cli": "7.7.0",
"@electron-forge/maker-deb": "7.6.1", "@electron-forge/maker-deb": "7.7.0",
"@electron-forge/maker-dmg": "7.6.1", "@electron-forge/maker-dmg": "7.7.0",
"@electron-forge/maker-flatpak": "7.6.1", "@electron-forge/maker-flatpak": "7.7.0",
"@electron-forge/maker-rpm": "7.6.1", "@electron-forge/maker-rpm": "7.7.0",
"@electron-forge/maker-squirrel": "7.6.1", "@electron-forge/maker-squirrel": "7.7.0",
"@electron-forge/maker-zip": "7.6.1", "@electron-forge/maker-zip": "7.7.0",
"@electron-forge/plugin-auto-unpack-natives": "7.6.1", "@electron-forge/plugin-auto-unpack-natives": "7.7.0",
"@electron/rebuild": "3.7.1", "@electron/rebuild": "3.7.1",
"@playwright/test": "1.50.1", "@playwright/test": "1.50.1",
"@types/archiver": "6.0.3", "@types/archiver": "6.0.3",
@@ -198,7 +192,7 @@
"@types/leaflet-gpx": "1.3.7", "@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/multer": "1.4.12", "@types/multer": "1.4.12",
"@types/node": "22.13.4", "@types/node": "22.13.5",
"@types/react": "18.3.18", "@types/react": "18.3.18",
"@types/safe-compare": "1.1.2", "@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.0",
@@ -212,7 +206,7 @@
"@types/ws": "8.5.14", "@types/ws": "8.5.14",
"@types/xml2js": "0.4.14", "@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33", "@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.0.5", "@vitest/coverage-v8": "3.0.6",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"electron": "34.2.0", "electron": "34.2.0",
"esm": "3.2.25", "esm": "3.2.25",
@@ -224,10 +218,10 @@
"rimraf": "6.0.1", "rimraf": "6.0.1",
"swagger-jsdoc": "6.2.8", "swagger-jsdoc": "6.2.8",
"tslib": "2.8.1", "tslib": "2.8.1",
"tsx": "4.19.2", "tsx": "4.19.3",
"typedoc": "0.27.7", "typedoc": "0.27.8",
"typescript": "5.7.3", "typescript": "5.7.3",
"vitest": "3.0.5", "vitest": "3.0.6",
"webpack": "5.98.0", "webpack": "5.98.0",
"webpack-cli": "6.0.1", "webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2" "webpack-dev-middleware": "7.4.2"

View File

@@ -1,3 +1,35 @@
/**
* Reads the level of indentation of the first line and trims the identation for all the text by that amount.
*
* For example, for:
*
* ```json
* {
* "hello": "world"
* }
* ```
*
* it results in:
*
* ```json
* {
* "hello": "world"
* }
* ```
*
* This is meant to be used as a template string, where it allows the indentation of the template without affecting whitespace changes.
*
* @example const html = trimIndentation`\
* <h1>Heading 1</h1>
* <h2>Heading 2</h2>
* <h3>Heading 3</h3>
* <h4>Heading 4</h4>
* <h5>Heading 5</h5>
* <h6>Heading 6</h6>
* `;
* @param strings
* @returns
*/
export function trimIndentation(strings: TemplateStringsArray) { export function trimIndentation(strings: TemplateStringsArray) {
const str = strings.toString(); const str = strings.toString();

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -72,7 +72,7 @@
<li>It's possible to drag across multiple days to set both the start and end <li>It's possible to drag across multiple days to set both the start and end
date of a particular note. date of a particular note.
<br> <br>
<img src="4_Calendar View_image.png" width="425" height="91"> <img src="3_Calendar View_image.png" width="425" height="91">
</li> </li>
<li>Creating new notes from the calendar will respect the <code>~child:template</code> relation <li>Creating new notes from the calendar will respect the <code>~child:template</code> relation
if set on the book note.</li> if set on the book note.</li>
@@ -81,7 +81,7 @@
<ul> <ul>
<li>Hovering the mouse over an event will display information about the note. <li>Hovering the mouse over an event will display information about the note.
<br> <br>
<img src="5_Calendar View_image.png" width="323" height="160"> <img src="4_Calendar View_image.png" width="323" height="160">
</li> </li>
<li>Left clicking the event will go to that note. Middle clicking will open <li>Left clicking the event will go to that note. Middle clicking will open
the note in a new tab and right click will offer more options including the note in a new tab and right click will offer more options including
@@ -91,102 +91,210 @@
edge of the event and dragging the mouse around.</li> edge of the event and dragging the mouse around.</li>
</ul> </ul>
<h2>Configuring the calendar</h2> <h2>Configuring the calendar</h2>
<ul> <p>The following attributes can be added to the book type:</p>
<li>The first day of the week can be either Sunday or Monday and can be adjusted <figure class="table"
from the application settings.</li> style="width:100%;">
</ul> <table class="ck-table-resized">
<h2>How the calendar works</h2> <colgroup>
<p> <col style="width:29.49%;">
<img class="image-style-align-left" src="7_Calendar View_image.png" width="329" <col style="width:70.51%;">
height="116">The calendar displays all the child notes of the book that have a <code>#startDate</code>. </colgroup>
An <code>#endDate</code> can optionally be added.</p> <thead>
<p>If editing the start date and end date from the note itself is desirable, <tr>
the following attributes can be added to the book note:</p><pre><code class="language-text-x-trilium-auto">#viewType=calendar #label:startDate(inheritable)="promoted,alias=Start Date,single,date" #label:endDate(inheritable)="promoted,alias=End Date,single,date" #hidePromotedAttributes </code></pre> <th>Name</th>
<p>This will result in:</p> <th>Description</th>
<p> </tr>
<img src="9_Calendar View_image.png" width="264" height="164"> </thead>
</p>
<h2>Advanced use-cases</h2>
<h3>Using a different attribute as event title</h3>
<p>By default, events are displayed on the calendar by their note title.
However, it is possible to configure a different attribute to be displayed
instead.</p>
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
calendar/book note), with the value being <code>#name</code> where <code>name</code> can
be any label. The attribute can also come through inheritance such as a
template attribute. If the note does not have the requested label, the
title of the note will be used instead.</p>
<figure class="table">
<table>
<tbody> <tbody>
<tr> <tr>
<td> <td><code>#calendar:hideWeekends</code>
<figure class="image">
<img style="aspect-ratio:631/115;" src="10_Calendar View_image.png" width="631"
height="115">
</figure>
</td> </td>
<td> <td>When present (regardless of value), it will hide Saturday and Sundays
<figure class="image"> from the calendar.</td>
<img style="aspect-ratio:445/124;" src="11_Calendar View_image.png" width="445" </tr>
height="124"> <tr>
</figure> <td><code>#calendar:weekNumbers</code>
</td> </td>
<td>When present (regardless of value), it will show the number of the week
on the calendar.</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</figure> </figure>
<h3>Using a relation attribute as event title</h3> <p>In addition, the first day of the week can be either Sunday or Monday
<p>Similarly to using an attribute, use <code>#calendar:title</code> and set and can be adjusted from the application settings.</p>
it to <code>~name</code> where <code>name</code> is the name of the relation <h2>Configuring the calendar events</h2>
to use.</p> <p>For each note of the calendar, the following attributes can be used:</p>
<p>Moreover, if there are more relations of the same name, they will be displayed <figure
as multiple events coming from the same note.</p> class="table">
<figure class="table">
<table> <table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody> <tbody>
<tr> <tr>
<td> <td><code>#startDate</code>
<figure class="image">
<img style="aspect-ratio:666/118;" src="15_Calendar View_image.png" width="666"
height="118">
</figure>
</td> </td>
<td> <td>The date the event starts, which will display it in the calendar. The
<figure class="image"> format is <code>YYYY-MM-DD</code> (year, month and day separated by a minus
<img style="aspect-ratio:294/151;" src="14_Calendar View_image.png" width="294" sign).</td>
height="151"> </tr>
</figure> <tr>
<td><code>#endDate</code>
</td> </td>
<td>Similar to <code>startDate</code>, mentions the end date if the event spans
across multiple days. The date is inclusive, so the end day is also considered.
The attribute can be missing for single-day events.</td>
</tr>
<tr>
<td><code>#color</code>
</td>
<td>Displays the event with a specified color (named such as <code>red</code>, <code>gray</code> or
hex such as <code>#FF0000</code>). This will also change the color of the
note in other places such as the note tree.</td>
</tr>
<tr>
<td><code>#calendar:color</code>
</td>
<td>Similar to <code>#color</code>, but applies the color only for the event
in the calendar and not for other places such as the note tree.</td>
</tr>
<tr>
<td><code>#iconClass</code>
</td>
<td>If present, the icon of the note will be displayed to the left of the
event title.</td>
</tr>
<tr>
<td><code>#calendar:title</code>
</td>
<td>Changes the title of an event to point to an attribute of the note other
than the title, either a label (e.g. <code>#assignee</code>) or a relation
(e.g. <code>~for</code>). See <i>Advanced use-cases</i> for more information.</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</figure> </figure>
<p>Note that it's even possible to have a <code>#calendar:title</code> on the <h2>How the calendar works</h2>
target note (e.g. “John Smith”) which will try to render an attribute of <p>
it. Note that it's not possible to use a relation here as well for safety <img class="image-style-align-left" src="7_Calendar View_image.png" width="329"
reasons (an accidental recursion &nbsp;of attributes could cause the application height="116">The calendar displays all the child notes of the book that have a <code>#startDate</code>.
to loop infinitely).</p> An <code>#endDate</code> can optionally be added.</p>
<figure class="table"> <p>If editing the start date and end date from the note itself is desirable,
<table> the following attributes can be added to the book note:</p><pre><code class="language-text-x-trilium-auto">#viewType=calendar #label:startDate(inheritable)="promoted,alias=Start Date,single,date" #label:endDate(inheritable)="promoted,alias=End Date,single,date" #hidePromotedAttributes </code></pre>
<tbody> <p>This will result in:</p>
<tr> <p>
<td> <img src="9_Calendar View_image.png" width="264" height="164">
<figure class="image"> </p>
<img style="aspect-ratio:364/121;" src="16_Calendar View_image.png" width="364" <p>When not used in a Journal, the calendar is recursive. That is, it will
height="121"> look for events not just in its child notes but also in the children of
</figure> these child notes.</p>
</td> <h2>Use-cases</h2>
<td> <h3>Using with the Journal / calendar</h3>
<figure class="image"> <p>It is possible to integrate the calendar view into the Journal with day
<img style="aspect-ratio:296/150;" src="17_Calendar View_image.png" width="296" notes. In order to do so change the note type of the Journal note (calendar
height="150"> root) to Book and then select the Calendar View.</p>
</figure> <p>Based on the <code>#calendarRoot</code> (or <code>#workspaceCalendarRoot</code>)
</td> attribute, the calendar will know that it's in a calendar and apply the
</tr> following:</p>
</tbody> <ul>
</table> <li>The calendar events are now rendered based on their <code>dateNote</code> attribute
</figure> rather than <code>startDate</code>.</li>
<li>Interactive editing such as dragging over an empty era or resizing an
event is no longer possible.</li>
<li>Clicking on the empty space on a date will automatically open that day's
note or create it if it does not exist.</li>
<li>Direct children of a day note will be displayed on the calendar despite
not having a <code>dateNote</code> attribute. Children of the child notes
will not be displayed.</li>
</ul>
<figure class="image">
<img style="aspect-ratio:1217/724;" src="18_Calendar View_image.png" width="1217"
height="724">
</figure>
<h3>Using a different attribute as event title</h3>
<p>By default, events are displayed on the calendar by their note title.
However, it is possible to configure a different attribute to be displayed
instead.</p>
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
calendar/book note), with the value being <code>#name</code> where <code>name</code> can
be any label. The attribute can also come through inheritance such as a
template attribute. If the note does not have the requested label, the
title of the note will be used instead.</p>
<figure class="table">
<table>
<tbody>
<tr>
<td>
<figure class="image">
<img style="aspect-ratio:631/115;" src="10_Calendar View_image.png" width="631"
height="115">
</figure>
</td>
<td>
<figure class="image">
<img style="aspect-ratio:445/124;" src="11_Calendar View_image.png" width="445"
height="124">
</figure>
</td>
</tr>
</tbody>
</table>
</figure>
<h3>Using a relation attribute as event title</h3>
<p>Similarly to using an attribute, use <code>#calendar:title</code> and set
it to <code>~name</code> where <code>name</code> is the name of the relation
to use.</p>
<p>Moreover, if there are more relations of the same name, they will be displayed
as multiple events coming from the same note.</p>
<figure class="table">
<table>
<tbody>
<tr>
<td>
<figure class="image">
<img style="aspect-ratio:666/118;" src="15_Calendar View_image.png" width="666"
height="118">
</figure>
</td>
<td>
<figure class="image">
<img style="aspect-ratio:294/151;" src="13_Calendar View_image.png" width="294"
height="151">
</figure>
</td>
</tr>
</tbody>
</table>
</figure>
<p>Note that it's even possible to have a <code>#calendar:title</code> on the
target note (e.g. “John Smith”) which will try to render an attribute of
it. Note that it's not possible to use a relation here as well for safety
reasons (an accidental recursion &nbsp;of attributes could cause the application
to loop infinitely).</p>
<figure class="table">
<table>
<tbody>
<tr>
<td>
<figure class="image">
<img style="aspect-ratio:364/121;" src="16_Calendar View_image.png" width="364"
height="121">
</figure>
</td>
<td>
<figure class="image">
<img style="aspect-ratio:296/150;" src="17_Calendar View_image.png" width="296"
height="150">
</figure>
</td>
</tr>
</tbody>
</table>
</figure>
</div> </div>
</div> </div>
</body> </body>

View File

@@ -0,0 +1,19 @@
<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>Canvas</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Canvas</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<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>Code</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Code</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -5,12 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css"> <link rel="stylesheet" href="../../style.css">
<base target="_parent"> <base target="_parent">
<title data-trilium-title>Geo map</title> <title data-trilium-title>Geo Map</title>
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<h1 data-trilium-h1>Geo map</h1> <h1 data-trilium-h1>Geo Map</h1>
<div class="ck-content"> <div class="ck-content">
<h2>Creating a new geo map</h2> <h2>Creating a new geo map</h2>
@@ -26,7 +26,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1256/1044;" src="10_Geo map_image.png" width="1256" <img style="aspect-ratio:1256/1044;" src="9_Geo Map_image.png" width="1256"
height="1044"> height="1044">
</figure> </figure>
</td> </td>
@@ -36,7 +36,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1720/1396;" src="3_Geo map_image.png" width="1720" <img style="aspect-ratio:1720/1396;" src="3_Geo Map_image.png" width="1720"
height="1396"> height="1396">
</figure> </figure>
</td> </td>
@@ -69,18 +69,18 @@
<p>To create a marker, first navigate to the desired point on the map. Then <p>To create a marker, first navigate to the desired point on the map. Then
press the press the
<img class="image_resized" style="aspect-ratio:72/66;width:7.37%;" <img class="image_resized" style="aspect-ratio:72/66;width:7.37%;"
src="5_Geo map_image.png" width="72" height="66">button on the top-right of the map.</p> src="4_Geo Map_image.png" width="72" height="66">button on the top-right of the map.</p>
<p>If the button is not visible, make sure the button section is visible <p>If the button is not visible, make sure the button section is visible
by pressing the chevron button ( by pressing the chevron button (
<img class="image_resized" style="aspect-ratio:72/66;width:7.51%;" <img class="image_resized" style="aspect-ratio:72/66;width:7.51%;"
src="11_Geo map_image.png" width="72" height="66">) in the top-right of the map.</p> src="10_Geo Map_image.png" width="72" height="66">) in the top-right of the map.</p>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1730/416;" src="14_Geo map_image.png" width="1730" <img style="aspect-ratio:1730/416;" src="14_Geo Map_image.png" width="1730"
height="416"> height="416">
</figure> </figure>
<p>&nbsp;</p> <p>&nbsp;</p>
@@ -96,7 +96,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:1586/404;" src="1_Geo map_image.png" width="1586" <img style="aspect-ratio:1586/404;" src="1_Geo Map_image.png" width="1586"
height="404"> height="404">
</figure> </figure>
<p>&nbsp;</p> <p>&nbsp;</p>
@@ -107,7 +107,7 @@
<th>4</th> <th>4</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:1696/608;" src="7_Geo map_image.png" width="1696" <img style="aspect-ratio:1696/608;" src="6_Geo Map_image.png" width="1696"
height="608"> height="608">
</figure> </figure>
<p>&nbsp;</p> <p>&nbsp;</p>
@@ -122,7 +122,7 @@
<p>The location of a marker is stored in the <code>#geolocation</code> attribute <p>The location of a marker is stored in the <code>#geolocation</code> attribute
of the child notes:</p> of the child notes:</p>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:1288/278;" src="4_Geo map_image.png" width="1288" <img style="aspect-ratio:1288/278;" src="12_Geo Map_image.png" width="1288"
height="278"> height="278">
</figure> </figure>
<p>This value can be added manually if needed. The value of the attribute <p>This value can be added manually if needed. The value of the attribute
@@ -168,7 +168,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image image-style-align-center image_resized" style="width:100%;"> <figure class="image image-style-align-center image_resized" style="width:100%;">
<img style="aspect-ratio:732/918;" src="16_Geo map_image.png" width="732" <img style="aspect-ratio:732/918;" src="16_Geo Map_image.png" width="732"
height="918"> height="918">
</figure> </figure>
</td> </td>
@@ -185,7 +185,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:518/84;" src="19_Geo map_image.png" width="518" <img style="aspect-ratio:518/84;" src="19_Geo Map_image.png" width="518"
height="84"> height="84">
</figure> </figure>
</td> </td>
@@ -199,7 +199,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1074/276;" src="12_Geo map_image.png" width="1074" <img style="aspect-ratio:1074/276;" src="11_Geo Map_image.png" width="1074"
height="276"> height="276">
</figure> </figure>
</td> </td>
@@ -225,7 +225,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:562/454;" src="17_Geo map_image.png" width="562" <img style="aspect-ratio:562/454;" src="17_Geo Map_image.png" width="562"
height="454"> height="454">
</figure> </figure>
</td> </td>
@@ -236,7 +236,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:696/480;" src="13_Geo map_image.png" width="696" <img style="aspect-ratio:696/480;" src="13_Geo Map_image.png" width="696"
height="480"> height="480">
</figure> </figure>
</td> </td>
@@ -250,7 +250,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:640/276;" src="2_Geo map_image.png" width="640" <img style="aspect-ratio:640/276;" src="2_Geo Map_image.png" width="640"
height="276"> height="276">
</figure> </figure>
</td> </td>
@@ -275,7 +275,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:226/74;" src="8_Geo map_image.png" width="226" <img style="aspect-ratio:226/74;" src="7_Geo Map_image.png" width="226"
height="74"> height="74">
</figure> </figure>
</td> </td>
@@ -286,7 +286,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:322/222;" src="6_Geo map_image.png" width="322" <img style="aspect-ratio:322/222;" src="5_Geo Map_image.png" width="322"
height="222"> height="222">
</figure> </figure>
</td> </td>
@@ -297,7 +297,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:620/530;" src="15_Geo map_image.png" width="620" <img style="aspect-ratio:620/530;" src="15_Geo Map_image.png" width="620"
height="530"> height="530">
</figure> </figure>
</td> </td>

View File

@@ -0,0 +1,19 @@
<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>Mind Map</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Mind Map</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<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>Note Map</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Note Map</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<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>Relation Map</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Relation Map</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<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>Render Note</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Render Note</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<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>Saved Search</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Saved Search</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,19 @@
<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>Text</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Text</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,45 @@
<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>Web View</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Web View</h1>
<div class="ck-content">
<h2>Configuration</h2>
<p>A webview needs to know which URL to render, and it can be provided by
setting the <code>webViewSrc</code> attribute, such as:</p><pre><code class="language-text-x-trilium-auto">#webViewSrc="https://www.wikipedia.org"</code></pre>
<h2>Web view on the server vs. Electron</h2>
<p>When accessing Trilium via a browser instead of the desktop application,
the web view will still try to render the content of the desired webpage.
However, since it's running in a browser there are quite a few limitations
as opposed to the desktop one.</p>
<p>More specifically, quite a few websites oppose being embedded in another
website (technically they have a non-permisive <code>X-Frame-Options</code> header).
This is not bypassable by Trilium so the page will simply fail to render.</p>
<p>You can diagnose this by right clicking the Trilium web page → Inspect
(element) and looking in the “Console” tab for errors such as:</p>
<ul>
<li><code>Refused to display 'https://www.google.com/' in a frame because it set 'X-Frame-Options' to 'sameorigin'.</code>
</li>
<li><code>Refused to frame 'https://duckduckgo.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'self' https://html.duckduckgo.com".</code>
</li>
</ul>
<p>There are a few websites that do render such as <code>wikipedia.org</code>.</p>
<p>Do note that we are also applying some sandboxing constraints on the server
side, so if you have any issues other than the unresolvable <code>X-Frame-Options</code> described
above, feel free to report them.</p>
<p>On the desktop side, a different technology is used which bypasses the
constraints of an <code>iframe</code> (<code>webview</code>).</p>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,60 @@
<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>Using promoted attributes to configure scripts</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Using promoted attributes to configure scripts</h1>
<div class="ck-content">
<p>A good use case of promoted attributes is to easily define the various
parameters a script might need, for example an input and output note if
it's processing data, or a checkbox to define a particular change in behavior
for the script.</p>
<p>
<img src="Using promoted attributes .png" width="425" height="179">
</p>
<h2>Using check boxes to toggle flags</h2>
<p>Instead of asking the user to modify a boolean value in the script, it's
much more intuitive to use a checkbox for it as a promoted attribute.</p>
<p>To do so, first define the promoted attribute:</p><pre><code class="language-text-x-trilium-auto">#label:groupByExtension="promoted,alias=Group by extension,single,boolean"</code></pre>
<p>Then use it:</p><pre><code class="language-application-javascript-env-frontend">const byExtension = api.currentNote.getLabelValue("groupByExtension") === "true";
if (byExtension) {
// Do something.
}</code></pre>
<p>This will work equally well in both front-end and back-end scripts.</p>
<h2>Using relations to select notes</h2>
<p>One common use case for a script is to read data from another note and
perhaps output its result in another note. To do so we need to define the
following promoted attributes:</p><pre><code class="language-text-x-trilium-auto">#relation:input="promoted,alias=Input,single" #relation:output="promoted,alias=Output,single"</code></pre>
<p>Once we have this, we can add some basic error handling to ensure that
the fields are completed by the user:</p><pre><code class="language-text-x-trilium-auto">const inputNoteId = api.currentNote.getRelationValue("input");
if (!inputNoteId) {
api.showError("Missing input.");
return;
}
const outputNoteId = api.currentNote.getRelationValue("output");
if (!outputNoteId) {
api.showError("Missing output.");
return;
}</code></pre>
<p>Note that here we are using <code>api.showError</code> which is only available
for frontend notes. If you are writing a backend note, simply remove <code>api.showError</code> but
the user will no feedback on why the script did not execute properly.</p>
<p>Afterwards we can simply read the note and do something with it:</p><pre><code class="language-text-x-trilium-auto">const note = api.getNote(inputNoteId);
if (!note) {
return;
}
const content = note.getContent().toString("utf-8");</code></pre>
</div>
</div>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../../style.css"> <link rel="stylesheet" href="../../style.css">
<base target="_parent"> <base target="_parent">
<title data-trilium-title>Creating a custom theme</title> <title data-trilium-title>Creating a custom theme</title>
</head> </head>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../../style.css"> <link rel="stylesheet" href="../../style.css">
<base target="_parent"> <base target="_parent">
<title data-trilium-title>Customize the Next theme</title> <title data-trilium-title>Customize the Next theme</title>
</head> </head>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../../style.css"> <link rel="stylesheet" href="../../style.css">
<base target="_parent"> <base target="_parent">
<title data-trilium-title>Reference</title> <title data-trilium-title>Reference</title>
</head> </head>
@@ -155,7 +155,7 @@ body.electron:not(.native-titlebar) {
}</code></pre> }</code></pre>
<h2>Custom fonts</h2> <h2>Custom fonts</h2>
<p>Currently the only way to include a custom font is to use&nbsp;<a class="reference-link" <p>Currently the only way to include a custom font is to use&nbsp;<a class="reference-link"
href="../Custom%20resource%20providers.html">Custom resource providers</a>. href="../Advanced%20topics/Custom%20resource%20providers.html">Custom resource providers</a>.
Basically import a font into Trilium and assign it <code>#customResourceProvider=fonts/myfont.ttf</code> and Basically import a font into Trilium and assign it <code>#customResourceProvider=fonts/myfont.ttf</code> and
then import the font in CSS via <code>/custom/fonts/myfont.ttf</code>.</p> then import the font in CSS via <code>/custom/fonts/myfont.ttf</code>.</p>
<h2>Dark and light themes</h2> <h2>Dark and light themes</h2>

View File

@@ -19,9 +19,17 @@
</li> </li>
<li>Note Types <li>Note Types
<ul> <ul>
<li><a href="User%20Guide/Note%20Types/Mermaid%20Diagram.html" target="detail">Mermaid Diagram</a> <li><a href="User%20Guide/Note%20Types/Text.html" target="detail">Text</a>
</li> </li>
<li><a href="User%20Guide/Note%20Types/Geo%20map.html" target="detail">Geo map</a> <li><a href="User%20Guide/Note%20Types/Code.html" target="detail">Code</a>
</li>
<li><a href="User%20Guide/Note%20Types/Saved%20Search.html" target="detail">Saved Search</a>
</li>
<li><a href="User%20Guide/Note%20Types/Relation%20Map.html" target="detail">Relation Map</a>
</li>
<li><a href="User%20Guide/Note%20Types/Note%20Map.html" target="detail">Note Map</a>
</li>
<li><a href="User%20Guide/Note%20Types/Render%20Note.html" target="detail">Render Note</a>
</li> </li>
<li>Book <li>Book
<ul> <ul>
@@ -29,6 +37,16 @@
</li> </li>
</ul> </ul>
</li> </li>
<li><a href="User%20Guide/Note%20Types/Mermaid%20Diagram.html" target="detail">Mermaid Diagram</a>
</li>
<li><a href="User%20Guide/Note%20Types/Canvas.html" target="detail">Canvas</a>
</li>
<li><a href="User%20Guide/Note%20Types/Web%20View.html" target="detail">Web View</a>
</li>
<li><a href="User%20Guide/Note%20Types/Mind%20Map.html" target="detail">Mind Map</a>
</li>
<li><a href="User%20Guide/Note%20Types/Geo%20Map.html" target="detail">Geo Map</a>
</li>
</ul> </ul>
</li> </li>
<li>Shared notes <li>Shared notes
@@ -38,6 +56,18 @@
</li> </li>
</ul> </ul>
</li> </li>
<li>Theme development
<ul>
<li><a href="User%20Guide/Theme%20development/Creating%20a%20custom%20theme.html"
target="detail">Creating a custom theme</a>
</li>
<li><a href="User%20Guide/Theme%20development/Customize%20the%20Next%20theme.html"
target="detail">Customize the Next theme</a>
</li>
<li><a href="User%20Guide/Theme%20development/Reference.html" target="detail">Reference</a>
</li>
</ul>
</li>
<li>Scripting <li>Scripting
<ul> <ul>
<li>Examples <li>Examples
@@ -47,38 +77,28 @@
</li> </li>
</ul> </ul>
</li> </li>
<li><a href="User%20Guide/Scripting/Using%20promoted%20attributes%20to%20c.html"
target="detail">Using promoted attributes to configure scripts</a>
</li>
</ul> </ul>
</li> </li>
<li>Advanced usage <li>Advanced topics
<ul> <ul>
<li>Theme development <li><a href="User%20Guide/Advanced%20topics/Custom%20resource%20providers.html"
<ul>
<li><a href="User%20Guide/Advanced%20usage/Theme%20development/Creating%20a%20custom%20theme.html"
target="detail">Creating a custom theme</a>
</li>
<li><a href="User%20Guide/Advanced%20usage/Theme%20development/Customize%20the%20Next%20theme.html"
target="detail">Customize the Next theme</a>
</li>
<li><a href="User%20Guide/Advanced%20usage/Theme%20development/Reference.html"
target="detail">Reference</a>
</li>
</ul>
</li>
<li><a href="User%20Guide/Advanced%20usage/Custom%20resource%20providers.html"
target="detail">Custom resource providers</a> target="detail">Custom resource providers</a>
</li> </li>
<li>REST API <li>REST API
<ul> <ul>
<li>ETAPI <li>ETAPI
<ul> <ul>
<li><a href="User%20Guide/Advanced%20usage/REST%20API/ETAPI/API%20Reference.dat" <li><a href="User%20Guide/Advanced%20topics/REST%20API/ETAPI/API%20Reference.dat"
target="detail">API Reference</a> target="detail">API Reference</a>
</li> </li>
</ul> </ul>
</li> </li>
<li>Internal API <li>Internal API
<ul> <ul>
<li><a href="User%20Guide/Advanced%20usage/REST%20API/Internal%20API/API%20Reference.dat" <li><a href="User%20Guide/Advanced%20topics/REST%20API/Internal%20API/API%20Reference.dat"
target="detail">API Reference</a> target="detail">API Reference</a>
</li> </li>
</ul> </ul>

View File

@@ -40,6 +40,10 @@ export default class NoteListRenderer {
} }
} }
get isFullHeight() {
return this.viewMode?.isFullHeight;
}
async renderList() { async renderList() {
if (!this.viewMode) { if (!this.viewMode) {
return null; return null;

View File

@@ -30,6 +30,27 @@ function parseDate(str: string) {
} }
} }
// Source: https://stackoverflow.com/a/30465299/4898894
function getMonthsInDateRange(startDate: string, endDate: string) {
const start = startDate.split('-');
const end = endDate.split('-');
const startYear = parseInt(start[0]);
const endYear = parseInt(end[0]);
const dates = [];
for (let i = startYear; i <= endYear; i++) {
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
const startMon = i === startYear ? parseInt(start[1])-1 : 0;
for(let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j+1) {
const month = j+1;
const displayMonth = month < 10 ? '0'+month : month;
dates.push([i, displayMonth].join('-'));
}
}
return dates;
}
function padNum(num: number) { function padNum(num: number) {
return `${num <= 9 ? "0" : ""}${num}`; return `${num <= 9 ? "0" : ""}${num}`;
} }
@@ -621,6 +642,7 @@ export default {
reloadFrontendApp, reloadFrontendApp,
reloadTray, reloadTray,
parseDate, parseDate,
getMonthsInDateRange,
formatDateISO, formatDateISO,
formatDateTime, formatDateTime,
formatTimeInterval, formatTimeInterval,

View File

@@ -15,6 +15,11 @@ const TPL = `
.note-list-widget .note-list { .note-list-widget .note-list {
padding: 10px; padding: 10px;
} }
.note-list-widget.full-height,
.note-list-widget.full-height .note-list-widget-content {
height: 100%;
}
</style> </style>
<div class="note-list-widget-content"> <div class="note-list-widget-content">
@@ -68,6 +73,7 @@ export default class NoteListWidget extends NoteContextAwareWidget {
async renderNoteList(note: FNote) { async renderNoteList(note: FNote) {
const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds()); const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds());
this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight);
await noteListRenderer.renderList(); await noteListRenderer.renderList();
this.viewMode = noteListRenderer.viewMode; this.viewMode = noteListRenderer.viewMode;
} }
@@ -111,8 +117,10 @@ export default class NoteListWidget extends NoteContextAwareWidget {
this.checkRenderStatus(); this.checkRenderStatus();
} }
if (this.viewMode) { // Inform the view mode of changes and refresh if needed.
this.viewMode.entitiesReloadedEvents(e); if (this.viewMode && this.viewMode.onEntitiesReloaded(e)) {
this.refresh();
this.checkRenderStatus();
} }
} }
} }

View File

@@ -3,8 +3,19 @@ import mimeTypesService from "../services/mime_types.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js";
import dialogService from "../services/dialog.js"; import dialogService from "../services/dialog.js";
import { t } from "../services/i18n.js"; import { t } from "../services/i18n.js";
import type FNote from "../entities/fnote.js";
import type { NoteType } from "../entities/fnote.js";
import type { EventData } from "../components/app_context.js";
const NOTE_TYPES = [ interface NoteTypeMapping {
type: NoteType;
mime?: string;
title: string;
isBeta?: boolean;
selectable: boolean;
}
const NOTE_TYPES: NoteTypeMapping[] = [
// The suggested note type ordering method: insert the item into the corresponding group, // The suggested note type ordering method: insert the item into the corresponding group,
// then ensure the items within the group are ordered alphabetically. // then ensure the items within the group are ordered alphabetically.
@@ -67,9 +78,16 @@ const TPL = `
`; `;
export default class NoteTypeWidget extends NoteContextAwareWidget { export default class NoteTypeWidget extends NoteContextAwareWidget {
private dropdown!: bootstrap.Dropdown;
private $noteTypeDropdown!: JQuery<HTMLElement>;
private $noteTypeButton!: JQuery<HTMLElement>;
private $noteTypeDesc!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
//@ts-ignore
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$widget.on("show.bs.dropdown", () => this.renderDropdown()); this.$widget.on("show.bs.dropdown", () => this.renderDropdown());
@@ -81,7 +99,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
this.$widget.on("click", ".dropdown-item", () => this.dropdown.toggle()); this.$widget.on("click", ".dropdown-item", () => this.dropdown.toggle());
} }
async refreshWithNote(note) { async refreshWithNote(note: FNote) {
this.$noteTypeButton.prop("disabled", () => NOT_SELECTABLE_NOTE_TYPES.includes(note.type)); this.$noteTypeButton.prop("disabled", () => NOT_SELECTABLE_NOTE_TYPES.includes(note.type));
this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime)); this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime));
@@ -93,8 +111,12 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
async renderDropdown() { async renderDropdown() {
this.$noteTypeDropdown.empty(); this.$noteTypeDropdown.empty();
if (!this.note) {
return;
}
for (const noteType of NOTE_TYPES.filter((nt) => nt.selectable)) { for (const noteType of NOTE_TYPES.filter((nt) => nt.selectable)) {
let $typeLink; let $typeLink: JQuery<HTMLElement>;
const $title = $("<span>").text(noteType.title); const $title = $("<span>").text(noteType.title);
if (noteType.isBeta) { if (noteType.isBeta) {
@@ -110,7 +132,9 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
const type = $typeLink.attr("data-note-type"); const type = $typeLink.attr("data-note-type");
const noteType = NOTE_TYPES.find((nt) => nt.type === type); const noteType = NOTE_TYPES.find((nt) => nt.type === type);
this.save(noteType.type, noteType.mime); if (noteType) {
this.save(noteType.type, noteType.mime);
}
}); });
} else { } else {
this.$noteTypeDropdown.append('<div class="dropdown-divider"></div>'); this.$noteTypeDropdown.append('<div class="dropdown-divider"></div>');
@@ -136,7 +160,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
.on("click", (e) => { .on("click", (e) => {
const $link = $(e.target).closest(".dropdown-item"); const $link = $(e.target).closest(".dropdown-item");
this.save("code", $link.attr("data-mime-type")); this.save("code", $link.attr("data-mime-type") ?? "");
}); });
if (this.note.type === "code" && this.note.mime === mimeType.mime) { if (this.note.type === "code" && this.note.mime === mimeType.mime) {
@@ -149,7 +173,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
} }
} }
async findTypeTitle(type, mime) { async findTypeTitle(type: NoteType, mime: string) {
if (type === "code") { if (type === "code") {
const mimeTypes = mimeTypesService.getMimeTypes(); const mimeTypes = mimeTypesService.getMimeTypes();
const found = mimeTypes.find((mt) => mt.mime === mime); const found = mimeTypes.find((mt) => mt.mime === mime);
@@ -162,12 +186,12 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
} }
} }
async save(type, mime) { async save(type: NoteType, mime?: string) {
if (type === this.note.type && mime === this.note.mime) { if (type === this.note?.type && mime === this.note?.mime) {
return; return;
} }
if (type !== this.note.type && !(await this.confirmChangeIfContent())) { if (type !== this.note?.type && !(await this.confirmChangeIfContent())) {
return; return;
} }
@@ -175,16 +199,20 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
} }
async confirmChangeIfContent() { async confirmChangeIfContent() {
if (!this.note) {
return;
}
const blob = await this.note.getBlob(); const blob = await this.note.getBlob();
if (!blob.content || !blob.content.trim().length) { if (!blob?.content || !blob.content.trim().length) {
return true; return true;
} }
return await dialogService.confirm(t("note_types.confirm-change")); return await dialogService.confirm(t("note_types.confirm-change"));
} }
async entitiesReloadedEvent({ loadResults }) { async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.isNoteReloaded(this.noteId)) { if (loadResults.isNoteReloaded(this.noteId)) {
this.refresh(); this.refresh();
} }

View File

@@ -16,7 +16,7 @@ const TPL = `
flex-shrink: 0.4; flex-shrink: 0.4;
overflow: auto; overflow: auto;
} }
.promoted-attributes-container { .promoted-attributes-container {
margin: 0 1.5em; margin: 0 1.5em;
overflow: auto; overflow: auto;
@@ -39,7 +39,7 @@ const TPL = `
display: table-cell; display: table-cell;
padding: 1px 0; padding: 1px 0;
} }
.promoted-attribute-cell div.input-group { .promoted-attribute-cell div.input-group {
margin-left: 10px; margin-left: 10px;
display: flex; display: flex;
@@ -54,9 +54,9 @@ const TPL = `
flex-grow: 0; flex-grow: 0;
width: unset; width: unset;
} }
</style> </style>
<div class="promoted-attributes-container"></div> <div class="promoted-attributes-container"></div>
</div>`; </div>`;
@@ -155,6 +155,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
.attr("data-attribute-type", valueAttr.type) .attr("data-attribute-type", valueAttr.type)
.attr("data-attribute-name", valueAttr.name) .attr("data-attribute-name", valueAttr.name)
.prop("value", valueAttr.value) .prop("value", valueAttr.value)
.prop("placeholder", t("promoted_attributes.unset-field-placeholder"))
.addClass("form-control") .addClass("form-control")
.addClass("promoted-attribute-input") .addClass("promoted-attribute-input")
.on("change", (event) => this.promotedAttributeChanged(event)); .on("change", (event) => this.promotedAttributeChanged(event));
@@ -226,6 +227,9 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
} else if (definition.labelType === "boolean") { } else if (definition.labelType === "boolean") {
$input.prop("type", "checkbox"); $input.prop("type", "checkbox");
$input.wrap($(`<label class="tn-checkbox"></label>`));
$wrapper.find(".input-group").removeClass("input-group");
if (valueAttr.value === "true") { if (valueAttr.value === "true") {
$input.prop("checked", "checked"); $input.prop("checked", "checked");
} }
@@ -272,7 +276,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
if (definition.multiplicity === "multi") { if (definition.multiplicity === "multi") {
const $addButton = $("<span>") const $addButton = $("<span>")
.addClass("bx bx-plus pointer") .addClass("bx bx-plus pointer tn-tool-button")
.prop("title", t("promoted_attributes.add_new_attribute")) .prop("title", t("promoted_attributes.add_new_attribute"))
.on("click", async () => { .on("click", async () => {
const $new = await this.createPromotedAttributeCell( const $new = await this.createPromotedAttributeCell(
@@ -292,7 +296,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
}); });
const $removeButton = $("<span>") const $removeButton = $("<span>")
.addClass("bx bx-trash pointer") .addClass("bx bx-trash pointer tn-tool-button")
.prop("title", t("promoted_attributes.remove_this_attribute")) .prop("title", t("promoted_attributes.remove_this_attribute"))
.on("click", async () => { .on("click", async () => {
const attributeId = $input.attr("data-attribute-id"); const attributeId = $input.attr("data-attribute-id");

View File

@@ -14,7 +14,8 @@ import CodeAutoReadOnlySizeOptions from "./options/code_notes/code_auto_read_onl
import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js"; import CodeMimeTypesOptions from "./options/code_notes/code_mime_types.js";
import ImageOptions from "./options/images/images.js"; import ImageOptions from "./options/images/images.js";
import SpellcheckOptions from "./options/spellcheck.js"; import SpellcheckOptions from "./options/spellcheck.js";
import PasswordOptions from "./options/password.js"; import PasswordOptions from "./options/password/password.js";
import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js"
import EtapiOptions from "./options/etapi.js"; import EtapiOptions from "./options/etapi.js";
import BackupOptions from "./options/backup.js"; import BackupOptions from "./options/backup.js";
import SyncOptions from "./options/sync.js"; import SyncOptions from "./options/sync.js";
@@ -35,6 +36,7 @@ import RibbonOptions from "./options/appearance/ribbon.js";
import LocalizationOptions from "./options/appearance/i18n.js"; import LocalizationOptions from "./options/appearance/i18n.js";
import CodeBlockOptions from "./options/appearance/code_block.js"; import CodeBlockOptions from "./options/appearance/code_block.js";
import EditorOptions from "./options/text_notes/editor.js"; import EditorOptions from "./options/text_notes/editor.js";
import ShareSettingsOptions from "./options/other/share_settings.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import type NoteContextAwareWidget from "../note_context_aware_widget.js"; import type NoteContextAwareWidget from "../note_context_aware_widget.js";
@@ -64,7 +66,7 @@ const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
_optionsCodeNotes: [VimKeyBindingsOptions, WrapLinesOptions, CodeAutoReadOnlySizeOptions, CodeMimeTypesOptions], _optionsCodeNotes: [VimKeyBindingsOptions, WrapLinesOptions, CodeAutoReadOnlySizeOptions, CodeMimeTypesOptions],
_optionsImages: [ImageOptions], _optionsImages: [ImageOptions],
_optionsSpellcheck: [SpellcheckOptions], _optionsSpellcheck: [SpellcheckOptions],
_optionsPassword: [PasswordOptions], _optionsPassword: [PasswordOptions, ProtectedSessionTimeoutOptions],
_optionsEtapi: [EtapiOptions], _optionsEtapi: [EtapiOptions],
_optionsBackup: [BackupOptions], _optionsBackup: [BackupOptions],
_optionsSync: [SyncOptions], _optionsSync: [SyncOptions],
@@ -76,14 +78,14 @@ const CONTENT_WIDGETS: Record<string, (typeof NoteContextAwareWidget)[]> = {
RevisionsSnapshotIntervalOptions, RevisionsSnapshotIntervalOptions,
RevisionSnapshotsLimitOptions, RevisionSnapshotsLimitOptions,
NetworkConnectionsOptions, NetworkConnectionsOptions,
HtmlImportTagsOptions HtmlImportTagsOptions,
ShareSettingsOptions
], ],
_optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions], _optionsAdvanced: [DatabaseIntegrityCheckOptions, DatabaseAnonymizationOptions, AdvancedSyncOptions, VacuumDatabaseOptions],
_backendLog: [BackendLogWidget] _backendLog: [BackendLogWidget]
}; };
export default class ContentWidgetTypeWidget extends TypeWidget { export default class ContentWidgetTypeWidget extends TypeWidget {
private $content!: JQuery<HTMLElement>; private $content!: JQuery<HTMLElement>;
static getType() { static getType() {

View File

@@ -223,7 +223,9 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
} }
$classicToolbarWidget.empty(); $classicToolbarWidget.empty();
$classicToolbarWidget[0].appendChild(editor.ui.view.toolbar.element); if ($classicToolbarWidget.length) {
$classicToolbarWidget[0].appendChild(editor.ui.view.toolbar.element);
}
if (utils.isMobile()) { if (utils.isMobile()) {
$classicToolbarWidget.addClass("visible"); $classicToolbarWidget.addClass("visible");

View File

@@ -4,6 +4,8 @@ import { t } from "../../services/i18n.js";
import type { EventData } from "../../components/app_context.js"; import type { EventData } from "../../components/app_context.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
const TEXT_MAX_NUM_CHARS = 5000;
const TPL = ` const TPL = `
<div class="note-detail-file note-detail-printable"> <div class="note-detail-file note-detail-printable">
<style> <style>
@@ -29,6 +31,10 @@ const TPL = `
} }
</style> </style>
<div class="file-preview-too-big alert alert-info hidden-ext">
${t("file.too_big", { maxNumChars: TEXT_MAX_NUM_CHARS })}
</div>
<pre class="file-preview-content"></pre> <pre class="file-preview-content"></pre>
<div class="file-preview-not-available alert alert-info"> <div class="file-preview-not-available alert alert-info">
@@ -46,6 +52,7 @@ export default class FileTypeWidget extends TypeWidget {
private $previewContent!: JQuery<HTMLElement>; private $previewContent!: JQuery<HTMLElement>;
private $previewNotAvailable!: JQuery<HTMLElement>; private $previewNotAvailable!: JQuery<HTMLElement>;
private $previewTooBig!: JQuery<HTMLElement>;
private $pdfPreview!: JQuery<HTMLElement>; private $pdfPreview!: JQuery<HTMLElement>;
private $videoPreview!: JQuery<HTMLElement>; private $videoPreview!: JQuery<HTMLElement>;
private $audioPreview!: JQuery<HTMLElement>; private $audioPreview!: JQuery<HTMLElement>;
@@ -58,6 +65,7 @@ export default class FileTypeWidget extends TypeWidget {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$previewContent = this.$widget.find(".file-preview-content"); this.$previewContent = this.$widget.find(".file-preview-content");
this.$previewNotAvailable = this.$widget.find(".file-preview-not-available"); this.$previewNotAvailable = this.$widget.find(".file-preview-not-available");
this.$previewTooBig = this.$widget.find(".file-preview-too-big");
this.$pdfPreview = this.$widget.find(".pdf-preview"); this.$pdfPreview = this.$widget.find(".pdf-preview");
this.$videoPreview = this.$widget.find(".video-preview"); this.$videoPreview = this.$widget.find(".video-preview");
this.$audioPreview = this.$widget.find(".audio-preview"); this.$audioPreview = this.$widget.find(".audio-preview");
@@ -73,12 +81,17 @@ export default class FileTypeWidget extends TypeWidget {
this.$previewContent.empty().hide(); this.$previewContent.empty().hide();
this.$pdfPreview.attr("src", "").empty().hide(); this.$pdfPreview.attr("src", "").empty().hide();
this.$previewNotAvailable.hide(); this.$previewNotAvailable.hide();
this.$previewTooBig.addClass("hidden-ext");
this.$videoPreview.hide(); this.$videoPreview.hide();
this.$audioPreview.hide(); this.$audioPreview.hide();
if (blob?.content) { if (blob?.content) {
this.$previewContent.show().scrollTop(0); this.$previewContent.show().scrollTop(0);
this.$previewContent.text(blob.content); const trimmedContent = blob.content.substring(0, TEXT_MAX_NUM_CHARS);
if (trimmedContent.length !== blob.content.length) {
this.$previewTooBig.removeClass("hidden-ext");
}
this.$previewContent.text(trimmedContent);
} else if (note.mime === "application/pdf") { } else if (note.mime === "application/pdf") {
this.$pdfPreview.show().attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open`)); this.$pdfPreview.show().attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open`));
} else if (note.mime.startsWith("video/")) { } else if (note.mime.startsWith("video/")) {

View File

@@ -135,10 +135,22 @@ export default class GeoMapTypeWidget extends TypeWidget {
throw new Error(t("geo-map.unable-to-load-map")); throw new Error(t("geo-map.unable-to-load-map"));
} }
if (!this.note) { this.#restoreViewportAndZoom();
// Restore markers.
await this.#reloadMarkers();
const updateFn = () => this.spacedUpdate.scheduleUpdate();
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
map.on("click", (e) => this.#onMapClicked(e));
}
async #restoreViewportAndZoom() {
const map = this.geoMapWidget.map;
if (!map || !this.note) {
return; return;
} }
const blob = await this.note.getBlob(); const blob = await this.note.getBlob();
let parsedContent: MapData = {}; let parsedContent: MapData = {};
@@ -150,14 +162,6 @@ export default class GeoMapTypeWidget extends TypeWidget {
const center = parsedContent.view?.center ?? DEFAULT_COORDINATES; const center = parsedContent.view?.center ?? DEFAULT_COORDINATES;
const zoom = parsedContent.view?.zoom ?? DEFAULT_ZOOM; const zoom = parsedContent.view?.zoom ?? DEFAULT_ZOOM;
map.setView(center, zoom); map.setView(center, zoom);
// Restore markers.
await this.#reloadMarkers();
const updateFn = () => this.spacedUpdate.scheduleUpdate();
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
map.on("click", (e) => this.#onMapClicked(e));
} }
async #reloadMarkers() { async #reloadMarkers() {
@@ -218,7 +222,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el)); const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el));
const L = this.L; const L = this.L;
const icon = this.#buildIcon(note.getIcon(), note.title); const icon = this.#buildIcon(note.getIcon(), note.getColorClass(), note.title);
const marker = L.marker(L.latLng(lat, lng), { const marker = L.marker(L.latLng(lat, lng), {
icon, icon,
@@ -253,12 +257,12 @@ export default class GeoMapTypeWidget extends TypeWidget {
this.currentMarkerData[note.noteId] = marker; this.currentMarkerData[note.noteId] = marker;
} }
#buildIcon(bxIconClass: string, title: string) { #buildIcon(bxIconClass: string, colorClass: string, title: string) {
return this.L.divIcon({ return this.L.divIcon({
html: `\ html: `\
<img class="icon" src="${asset_path}/node_modules/leaflet/dist/images/marker-icon.png" /> <img class="icon" src="${asset_path}/node_modules/leaflet/dist/images/marker-icon.png" />
<img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" /> <img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" />
<span class="bx ${bxIconClass}"></span> <span class="bx ${bxIconClass} ${colorClass}"></span>
<span class="title-label">${title}</span>`, <span class="title-label">${title}</span>`,
iconSize: [ 25, 41 ], iconSize: [ 25, 41 ],
iconAnchor: [ 12, 41 ] iconAnchor: [ 12, 41 ]
@@ -343,6 +347,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
async doRefresh(note: FNote) { async doRefresh(note: FNote) {
await this.geoMapWidget.refresh(); await this.geoMapWidget.refresh();
this.#restoreViewportAndZoom();
await this.#reloadMarkers(); await this.#reloadMarkers();
} }
@@ -356,7 +361,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
// If any of note has its location attribute changed. // If any of note has its location attribute changed.
// TODO: Should probably filter by parent here as well. // TODO: Should probably filter by parent here as well.
const attributeRows = loadResults.getAttributeRows(); const attributeRows = loadResults.getAttributeRows();
if (attributeRows.find((at) => at.name === LOCATION_ATTRIBUTE)) { if (attributeRows.find((at) => [ LOCATION_ATTRIBUTE, "color" ].includes(at.name ?? ""))) {
this.#reloadMarkers(); this.#reloadMarkers();
} }
} }

View File

@@ -2,33 +2,37 @@ import OptionsWidget from "../options_widget.js";
import server from "../../../../services/server.js"; import server from "../../../../services/server.js";
import toastService from "../../../../services/toast.js"; import toastService from "../../../../services/toast.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js"; import TimeSelector from "../time_selector.js";
const TPL = ` const TPL = `
<div class="options-section"> <div class="options-section">
<h4>${t("attachment_erasure_timeout.attachment_erasure_timeout")}</h4> <h4>${t("attachment_erasure_timeout.attachment_erasure_timeout")}</h4>
<p>${t("attachment_erasure_timeout.attachment_auto_deletion_description")}</p> <p>${t("attachment_erasure_timeout.attachment_auto_deletion_description")}</p>
<div id="time-selector-placeholder"></div>
<div class="form-group">
<label>${t("attachment_erasure_timeout.erase_attachments_after_x_seconds")}</label>
<input class="erase-unused-attachments-after-time-in-seconds form-control options-number-input" type="number" min="0">
</div>
<p>${t("attachment_erasure_timeout.manual_erasing_description")}</p> <p>${t("attachment_erasure_timeout.manual_erasing_description")}</p>
<button class="erase-unused-attachments-now-button btn btn-secondary">${t("attachment_erasure_timeout.erase_unused_attachments_now")}</button> <button class="erase-unused-attachments-now-button btn btn-secondary">${t("attachment_erasure_timeout.erase_unused_attachments_now")}</button>
</div>`; </div>`;
export default class AttachmentErasureTimeoutOptions extends OptionsWidget { export default class AttachmentErasureTimeoutOptions extends TimeSelector {
private $eraseUnusedAttachmentsAfterTimeInSeconds!: JQuery<HTMLElement>;
private $eraseUnusedAttachmentsNowButton!: JQuery<HTMLElement>; private $eraseUnusedAttachmentsNowButton!: JQuery<HTMLElement>;
constructor() {
super({
widgetId: "erase-unused-attachments-after",
widgetLabelId: "attachment_erasure_timeout.erase_attachments_after",
optionValueId: "eraseUnusedAttachmentsAfterSeconds",
optionTimeScaleId: "eraseUnusedAttachmentsAfterTimeScale"
});
super.doRender();
}
doRender() { doRender() {
const $timeSelector = this.$widget;
this.$widget = $(TPL); this.$widget = $(TPL);
this.$eraseUnusedAttachmentsAfterTimeInSeconds = this.$widget.find(".erase-unused-attachments-after-time-in-seconds"); // inject TimeSelector widget template
this.$eraseUnusedAttachmentsAfterTimeInSeconds.on("change", () => this.updateOption("eraseUnusedAttachmentsAfterSeconds", this.$eraseUnusedAttachmentsAfterTimeInSeconds.val())); this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector);
this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button"); this.$eraseUnusedAttachmentsNowButton = this.$widget.find(".erase-unused-attachments-now-button");
this.$eraseUnusedAttachmentsNowButton.on("click", () => { this.$eraseUnusedAttachmentsNowButton.on("click", () => {
@@ -37,8 +41,4 @@ export default class AttachmentErasureTimeoutOptions extends OptionsWidget {
}); });
}); });
} }
async optionsLoaded(options: OptionMap) {
this.$eraseUnusedAttachmentsAfterTimeInSeconds.val(options.eraseUnusedAttachmentsAfterSeconds);
}
} }

View File

@@ -3,118 +3,42 @@ import server from "../../../../services/server.js";
import toastService from "../../../../services/toast.js"; import toastService from "../../../../services/toast.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js"; import type { OptionMap } from "../../../../../../services/options_interface.js";
import TimeSelector from "../time_selector.js";
const TPL = ` const TPL = `
<div class="options-section"> <div class="options-section">
<h4>${t("note_erasure_timeout.note_erasure_timeout_title")}</h4> <h4>${t("note_erasure_timeout.note_erasure_timeout_title")}</h4>
<p>${t("note_erasure_timeout.note_erasure_description")}</p> <p>${t("note_erasure_timeout.note_erasure_description")}</p>
<div id="time-selector-placeholder"></div>
<div class="form-group">
<label for="erase-entities-after-time">${t("note_erasure_timeout.erase_notes_after")}</label>
<div class="d-flex gap-2">
<input id="erase-entities-after-time" class="form-control options-number-input" type="number" min="0" steps="1" required>
<!-- TriliumNextTODO: i18n the strings when refactoring this to a standalone widget -->
<select id="erase-entities-after-time-scale" class="form-select duration-selector" required>
<option value="1">${t("duration.seconds")}</option>
<option value="60">${t("duration.minutes")}</option>
<option value="3600">${t("duration.hours")}</option>
<option value="86400">${t("duration.days")}</option>
</select>
</div>
</div>
<p>${t("note_erasure_timeout.manual_erasing_description")}</p> <p>${t("note_erasure_timeout.manual_erasing_description")}</p>
<button id="erase-deleted-notes-now-button" class="btn btn-secondary">${t("note_erasure_timeout.erase_deleted_notes_now")}</button> <button id="erase-deleted-notes-now-button" class="btn btn-secondary">${t("note_erasure_timeout.erase_deleted_notes_now")}</button>
<style>
.duration-selector {
width: auto;
}
</style>
</div>`; </div>`;
export default class NoteErasureTimeoutOptions extends OptionsWidget { export default class NoteErasureTimeoutOptions extends TimeSelector {
private $eraseEntitiesAfterTime!: JQuery<HTMLInputElement>;
private $eraseEntitiesAfterTimeScale!: JQuery<HTMLSelectElement>;
private $eraseDeletedNotesButton!: JQuery<HTMLButtonElement>; private $eraseDeletedNotesButton!: JQuery<HTMLButtonElement>;
private eraseEntitiesAfterTimeInSeconds!: string | number;
constructor() {
super({
widgetId: "erase-entities-after",
widgetLabelId: "note_erasure_timeout.erase_notes_after",
optionValueId: "eraseEntitiesAfterTimeInSeconds",
optionTimeScaleId: "eraseEntitiesAfterTimeScale"
});
super.doRender();
}
doRender() { doRender() {
const $timeSelector = this.$widget;
// inject TimeSelector widget template
this.$widget = $(TPL); this.$widget = $(TPL);
this.$eraseEntitiesAfterTime = this.$widget.find("#erase-entities-after-time"); this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector)
this.$eraseEntitiesAfterTimeScale = this.$widget.find("#erase-entities-after-time-scale");
this.$eraseEntitiesAfterTime.on("change", () => {
const time = this.$eraseEntitiesAfterTime.val();
const timeScale = this.$eraseEntitiesAfterTimeScale.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string" || !time) return;
this.eraseEntitiesAfterTimeInSeconds = this.convertTime(time, timeScale).toOption();
this.updateOption("eraseEntitiesAfterTimeInSeconds", this.eraseEntitiesAfterTimeInSeconds);
});
this.$eraseEntitiesAfterTimeScale.on("change", () => {
const timeScale = this.$eraseEntitiesAfterTimeScale.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string") return;
//calculate the new displayed value
const displayedTime = this.convertTime(this.eraseEntitiesAfterTimeInSeconds, timeScale).toDisplay();
this.updateOption("eraseEntitiesAfterTimeScale", timeScale);
this.$eraseEntitiesAfterTime.val(displayedTime).trigger("change");
});
this.$eraseDeletedNotesButton = this.$widget.find("#erase-deleted-notes-now-button"); this.$eraseDeletedNotesButton = this.$widget.find("#erase-deleted-notes-now-button");
this.$eraseDeletedNotesButton.on("click", () => { this.$eraseDeletedNotesButton.on("click", () => {
server.post("notes/erase-deleted-notes-now").then(() => { server.post("notes/erase-deleted-notes-now").then(() => {
toastService.showMessage(t("note_erasure_timeout.deleted_notes_erased")); toastService.showMessage(t("note_erasure_timeout.deleted_notes_erased"));
}); });
}); });
} }
async optionsLoaded(options: OptionMap) {
this.eraseEntitiesAfterTimeInSeconds = options.eraseEntitiesAfterTimeInSeconds;
const displayedTime = this.convertTime(options.eraseEntitiesAfterTimeInSeconds, options.eraseEntitiesAfterTimeScale).toDisplay();
this.$eraseEntitiesAfterTime.val(displayedTime);
this.$eraseEntitiesAfterTimeScale.val(options.eraseEntitiesAfterTimeScale);
}
convertTime(time: string | number, timeScale: string | number) {
const value = typeof time === "number" ? time : parseInt(time);
if (Number.isNaN(value)) {
throw new Error(`Time needs to be a valid integer, but received: ${time}`);
}
const operand = typeof timeScale === "number" ? timeScale : parseInt(timeScale);
if (Number.isNaN(operand) || operand < 1) {
throw new Error(`TimeScale needs to be a valid integer >= 1, but received: ${timeScale}`);
}
return {
toOption: () => Math.ceil(value * operand),
toDisplay: () => Math.ceil(value / operand),
}
}
handleTimeValidation() {
if (this.$eraseEntitiesAfterTime.is(":invalid")) {
// TriliumNextTODO: i18n
toastService.showMessage("The entered time value is not a valid number.");
return false
}
return true;
}
} }

View File

@@ -1,30 +1,31 @@
import OptionsWidget from "../options_widget.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js"; import TimeSelector from "../time_selector.js";
const TPL = ` const TPL = `
<div class="options-section"> <div class="options-section">
<h4>${t("revisions_snapshot_interval.note_revisions_snapshot_interval_title")}</h4> <h4>${t("revisions_snapshot_interval.note_revisions_snapshot_interval_title")}</h4>
<p class="use-tn-links">${t("revisions_snapshot_interval.note_revisions_snapshot_description")}</p> <p class="use-tn-links">${t("revisions_snapshot_interval.note_revisions_snapshot_description")}</p>
<div id="time-selector-placeholder"></div>
<div class="form-group">
<label>${t("revisions_snapshot_interval.snapshot_time_interval_label")}</label>
<input class="revision-snapshot-time-interval-in-seconds form-control options-number-input" type="number" min="10">
</div>
</div>`; </div>`;
export default class RevisionsSnapshotIntervalOptions extends OptionsWidget { export default class RevisionsSnapshotIntervalOptions extends TimeSelector {
private $revisionsTimeInterval!: JQuery<HTMLElement>; constructor() {
super({
widgetId: "revision-snapshot-time-interval",
widgetLabelId: "revisions_snapshot_interval.snapshot_time_interval_label",
optionValueId: "revisionSnapshotTimeInterval",
optionTimeScaleId: "revisionSnapshotTimeIntervalTimeScale",
minimumSeconds: 10
});
super.doRender();
}
doRender() { doRender() {
const $timeSelector = this.$widget;
// inject TimeSelector widget template
this.$widget = $(TPL); this.$widget = $(TPL);
this.$revisionsTimeInterval = this.$widget.find(".revision-snapshot-time-interval-in-seconds"); this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector)
this.$revisionsTimeInterval.on("change", () => this.updateOption("revisionSnapshotTimeInterval", this.$revisionsTimeInterval.val()));
}
async optionsLoaded(options: OptionMap) {
this.$revisionsTimeInterval.val(options.revisionSnapshotTimeInterval);
} }
} }

View File

@@ -0,0 +1,97 @@
import OptionsWidget from "../options_widget.js";
import options from "../../../../services/options.js";
import { t } from "../../../../services/i18n.js";
import type { OptionMap, OptionNames } from "../../../../../../services/options_interface.js";
import searchService from "../../../../services/search.js";
const TPL = `
<div class="options-section">
<h4>${t("share.title")}</h4>
<label class="tn-checkbox">
<input class="form-check-input" type="checkbox" name="redirectBareDomain" value="true">
${t("share.redirect_bare_domain")}
</label>
<p class="form-text">${t("share.redirect_bare_domain_description")}</p>
<label class="tn-checkbox">
<input class="form-check-input" type="checkbox" name="showLoginInShareTheme" value="true">
${t("share.show_login_link")}
</label>
<p class="form-text">${t("share.show_login_link_description")}</p>
</div>`;
export default class ShareSettingsOptions extends OptionsWidget {
private $shareRootCheck!: JQuery<HTMLElement>;
private $shareRootStatus!: JQuery<HTMLElement>;
doRender() {
this.$widget = $(TPL);
this.contentSized();
this.$shareRootCheck = this.$widget.find('.share-root-check');
this.$shareRootStatus = this.$widget.find('.share-root-status');
// Add change handlers for both checkboxes
this.$widget.find('input[type="checkbox"]').on("change", (e: JQuery.ChangeEvent) => {
this.save();
// Show/hide share root status section based on redirectBareDomain checkbox
const target = e.target as HTMLInputElement;
if (target.name === 'redirectBareDomain') {
this.$shareRootCheck.toggle(target.checked);
if (target.checked) {
this.checkShareRoot();
}
}
});
// Add click handler for check share root button
this.$widget.find('.check-share-root').on("click", () => this.checkShareRoot());
}
async optionsLoaded(options: OptionMap) {
const redirectBareDomain = options.redirectBareDomain === "true";
this.$widget.find('input[name="redirectBareDomain"]').prop("checked", redirectBareDomain);
this.$shareRootCheck.toggle(redirectBareDomain);
if (redirectBareDomain) {
await this.checkShareRoot();
}
this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked", options.showLoginInShareTheme === "true");
}
async checkShareRoot() {
const $button = this.$widget.find('.check-share-root');
$button.prop('disabled', true);
try {
const shareRootNotes = await searchService.searchForNotes("#shareRoot");
const sharedShareRootNote = shareRootNotes.find(note => note.isShared());
if (sharedShareRootNote) {
this.$shareRootStatus
.removeClass('text-danger')
.addClass('text-success')
.text(t("share.share_root_found", {noteTitle: sharedShareRootNote.title}));
} else {
this.$shareRootStatus
.removeClass('text-success')
.addClass('text-danger')
.text(shareRootNotes.length > 0
? t("share.share_root_not_shared", {noteTitle: shareRootNotes[0].title})
: t("share.share_root_not_found"));
}
} finally {
$button.prop('disabled', false);
}
}
async save() {
const redirectBareDomain = this.$widget.find('input[name="redirectBareDomain"]').prop("checked");
await this.updateOption<"redirectBareDomain">("redirectBareDomain", redirectBareDomain.toString());
const showLoginInShareTheme = this.$widget.find('input[name="showLoginInShareTheme"]').prop("checked");
await this.updateOption<"showLoginInShareTheme">("showLoginInShareTheme", showLoginInShareTheme.toString());
}
}

View File

@@ -1,9 +1,9 @@
import { t } from "../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import server from "../../../services/server.js"; import server from "../../../../services/server.js";
import protectedSessionHolder from "../../../services/protected_session_holder.js"; import protectedSessionHolder from "../../../../services/protected_session_holder.js";
import toastService from "../../../services/toast.js"; import toastService from "../../../../services/toast.js";
import OptionsWidget from "./options_widget.js"; import OptionsWidget from "../options_widget.js";
import type { OptionMap } from "../../../../../services/options_interface.js"; import type { OptionMap } from "../../../../../../services/options_interface.js";
const TPL = ` const TPL = `
<div class="options-section"> <div class="options-section">
@@ -32,17 +32,7 @@ const TPL = `
<button class="save-password-button btn btn-primary">${t("password.change_password")}</button> <button class="save-password-button btn btn-primary">${t("password.change_password")}</button>
</form> </form>
</div> </div>
`;
<div class="options-section">
<h4>${t("password.protected_session_timeout")}</h4>
<p>${t("password.protected_session_timeout_description")} <a class="tn-link" href="https://triliumnext.github.io/Docs/Wiki/protected-notes.html" class="external">${t("password.wiki")}</a> ${t("password.for_more_info")}</p>
<div class="form-group">
<label for="protected-session-timeout-in-seconds">${t("password.protected_session_timeout_label")}</label>
<input id="protected-session-timeout-in-seconds" class="protected-session-timeout-in-seconds form-control options-number-input" type="number" min="60">
</div>
</div>`;
// TODO: Deduplicate // TODO: Deduplicate
interface ChangePasswordResponse { interface ChangePasswordResponse {

View File

@@ -0,0 +1,30 @@
import { t } from "../../../../services/i18n.js";
import TimeSelector from "../time_selector.js";
const TPL = `
<div class="options-section">
<h4>${t("password.protected_session_timeout")}</h4>
<p>${t("password.protected_session_timeout_description")} <a class="tn-link" href="https://triliumnext.github.io/Docs/Wiki/protected-notes.html" class="external">${t("password.wiki")}</a> ${t("password.for_more_info")}</p>
<div id="time-selector-placeholder"></div>
</div>`;
export default class ProtectedSessionTimeoutOptions extends TimeSelector {
constructor() {
super({
widgetId: "protected-session-timeout",
widgetLabelId: "password.protected_session_timeout_label",
optionValueId: "protectedSessionTimeout",
optionTimeScaleId: "protectedSessionTimeoutTimeScale",
minimumSeconds: 60
});
super.doRender();
}
doRender() {
const $timeSelector = this.$widget;
// inject TimeSelector widget template
this.$widget = $(TPL);
this.$widget.find("#time-selector-placeholder").replaceWith($timeSelector)
}
}

View File

@@ -0,0 +1,140 @@
import OptionsWidget from "./options_widget.js";
import toastService from "../../../services/toast.js";
import { t } from "../../../services/i18n.js";
import type { OptionDefinitions, OptionMap } from "../../../../../services/options_interface.js";
import optionsService from "../../../services/options.js";
type TimeSelectorConstructor = {
widgetId: string;
widgetLabelId: string;
optionValueId: keyof OptionDefinitions;
optionTimeScaleId: keyof OptionDefinitions;
includedTimeScales?: Set<TimeSelectorScale>;
minimumSeconds?: number;
};
type TimeSelectorScale = "seconds" | "minutes" | "hours" | "days";
const TPL = (options: Omit<TimeSelectorConstructor, "optionValueId" | "optionTimeScaleId">) => `
<div class="form-group">
<label for="${options.widgetId}">${t(options.widgetLabelId)}</label>
<div class="d-flex gap-2">
<input id="${options.widgetId}" class="form-control options-number-input" type="number" min="0" steps="1" required>
<select id="${options.widgetId}-time-scale" class="form-select duration-selector" required>
${options.includedTimeScales?.has("seconds") ? `<option value="1">${t("duration.seconds")}</option>` : ""}
${options.includedTimeScales?.has("minutes") ? `<option value="60">${t("duration.minutes")}</option>` : ""}
${options.includedTimeScales?.has("hours") ? `<option value="3600">${t("duration.hours")}</option>` : ""}
${options.includedTimeScales?.has("days") ? `<option value="86400">${t("duration.days")}</option>` : ""}
</select>
</div>
</div>
</div>
<style>
.duration-selector {
width: auto;
}
</style>`;
export default class TimeSelector extends OptionsWidget {
private $timeValueInput!: JQuery<HTMLInputElement>;
private $timeScaleSelect!: JQuery<HTMLSelectElement>;
private internalTimeInSeconds!: string | number;
private widgetId: string;
private widgetLabelId: string;
private optionValueId: keyof OptionDefinitions;
private optionTimeScaleId: keyof OptionDefinitions;
private includedTimeScales: Set<TimeSelectorScale>;
private minimumSeconds: number;
constructor(options: TimeSelectorConstructor) {
super();
this.widgetId = options.widgetId;
this.widgetLabelId = options.widgetLabelId;
this.optionValueId = options.optionValueId;
this.optionTimeScaleId = options.optionTimeScaleId;
this.includedTimeScales = options.includedTimeScales || new Set(["seconds", "minutes", "hours", "days"]);
this.minimumSeconds = options.minimumSeconds || 0
}
doRender() {
this.$widget = $(
TPL({
widgetId: this.widgetId,
widgetLabelId: this.widgetLabelId,
includedTimeScales: this.includedTimeScales
})
);
this.$timeValueInput = this.$widget.find(`#${this.widgetId}`);
this.$timeScaleSelect = this.$widget.find(`#${this.widgetId}-time-scale`);
this.$timeValueInput.on("change", () => {
const time = this.$timeValueInput.val();
const timeScale = this.$timeScaleSelect.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string" || !time) return;
this.setInternalTimeInSeconds(this.convertTime(time, timeScale).toOption());
this.updateOption(this.optionValueId, this.internalTimeInSeconds);
});
this.$timeScaleSelect.on("change", () => {
const timeScale = this.$timeScaleSelect.val();
if (!this.handleTimeValidation() || typeof timeScale !== "string") return;
//calculate the new displayed value
const displayedTime = this.convertTime(this.internalTimeInSeconds, timeScale).toDisplay();
this.updateOption(this.optionTimeScaleId, timeScale);
this.$timeValueInput.val(displayedTime).trigger("change");
});
}
async optionsLoaded(options: OptionMap) {
const optionValue = optionsService.getInt(this.optionValueId) || 0;
const optionTimeScale = optionsService.getInt(this.optionTimeScaleId) || 1;
this.setInternalTimeInSeconds(optionValue);
const displayedTime = this.convertTime(optionValue, optionTimeScale).toDisplay();
this.$timeValueInput.val(displayedTime);
this.$timeScaleSelect.val(optionTimeScale);
}
private convertTime(time: string | number, timeScale: string | number) {
const value = typeof time === "number" ? time : parseInt(time);
if (Number.isNaN(value)) {
throw new Error(`Time needs to be a valid integer, but received: ${time}`);
}
const operand = typeof timeScale === "number" ? timeScale : parseInt(timeScale);
if (Number.isNaN(operand) || operand < 1) {
throw new Error(`TimeScale needs to be a valid integer >= 1, but received: ${timeScale}`);
}
return {
toOption: () => Math.ceil(value * operand),
toDisplay: () => Math.ceil(value / operand)
};
}
private handleTimeValidation() {
if (this.$timeValueInput.is(":invalid")) {
toastService.showError(t("time_selector.invalid_input"));
return false;
}
return true;
}
private setInternalTimeInSeconds(time: number) {
if (time < this.minimumSeconds) {
toastService.showError(t("time_selector.minimum_input", {minimumSeconds: this.minimumSeconds}));
return this.internalTimeInSeconds = this.minimumSeconds;
}
return this.internalTimeInSeconds = time;
}
}

View File

@@ -1,25 +1,36 @@
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
import TypeWidget from "./type_widget.js"; import TypeWidget from "./type_widget.js";
import attributeService from "../../services/attributes.js"; import attributeService from "../../services/attributes.js";
import type FNote from "../../entities/fnote.js";
import type { EventData } from "../../components/app_context.js";
import utils from "../../services/utils.js";
const TPL = ` const TPL = `
<div class="note-detail-web-view note-detail-printable" style="height: 100%"> <div class="note-detail-web-view note-detail-printable" style="height: 100%">
<div class="note-detail-web-view-help alert alert-warning" style="margin: 50px; padding: 20px 20px 0px 20px;"> <div class="note-detail-web-view-help alert alert-warning" style="margin: 50px; padding: 20px 20px 0px 20px;">
<h4>${t("web_view.web_view")}</h4> <h4>${t("web_view.web_view")}</h4>
<p>${t("web_view.embed_websites")}</p> <p>${t("web_view.embed_websites")}</p>
<p>${t("web_view.create_label")}</p> <p>${t("web_view.create_label")}</p>
<h4>${t("web_view.disclaimer")}</h4>
<p>${t("web_view.experimental_note")}</p>
</div> </div>
<webview class="note-detail-web-view-content"></webview> ${buildElement()}
</div>`; </div>`;
function buildElement() {
if (!utils.isElectron()) {
return `<iframe class="note-detail-web-view-content" sandbox="allow-same-origin allow-scripts"></iframe>`;
} else {
return `<webview class="note-detail-web-view-content"></webview>`;
}
}
export default class WebViewTypeWidget extends TypeWidget { export default class WebViewTypeWidget extends TypeWidget {
private $noteDetailWebViewHelp!: JQuery<HTMLElement>;
private $noteDetailWebViewContent!: JQuery<HTMLElement>;
static getType() { static getType() {
return "webView"; return "webView";
} }
@@ -34,11 +45,15 @@ export default class WebViewTypeWidget extends TypeWidget {
super.doRender(); super.doRender();
} }
async doRefresh(note) { async doRefresh(note: FNote) {
this.$widget.show(); this.$widget.show();
this.$noteDetailWebViewHelp.hide(); this.$noteDetailWebViewHelp.hide();
this.$noteDetailWebViewContent.hide(); this.$noteDetailWebViewContent.hide();
if (!this.note) {
return;
}
const webViewSrc = this.note.getLabelValue("webViewSrc"); const webViewSrc = this.note.getLabelValue("webViewSrc");
if (webViewSrc) { if (webViewSrc) {
@@ -54,17 +69,19 @@ export default class WebViewTypeWidget extends TypeWidget {
} }
cleanup() { cleanup() {
this.$noteDetailWebViewContent.removeAttribute("src"); this.$noteDetailWebViewContent.removeAttr("src");
} }
setDimensions() { setDimensions() {
const $parent = this.$widget; const $parent = this.$widget;
this.$noteDetailWebViewContent.height($parent.height()).width($parent.width()); this.$noteDetailWebViewContent
.height($parent.height() ?? 0)
.width($parent.width() ?? 0);
} }
entitiesReloadedEvent({ loadResults }) { entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.getAttributeRows().find((attr) => attr.name === "webViewSrc" && attributeService.isAffecting(attr, this.noteContext.note))) { if (loadResults.getAttributeRows().find((attr) => attr.name === "webViewSrc" && attributeService.isAffecting(attr, this.noteContext?.note))) {
this.refresh(); this.refresh();
} }
} }

View File

@@ -1,4 +1,4 @@
import type { Calendar, DateSelectArg, EventChangeArg, EventDropArg, EventSourceInput, PluginDef } from "@fullcalendar/core"; import type { Calendar, DateSelectArg, EventChangeArg, EventDropArg, EventInput, EventSourceFunc, EventSourceFuncArg, EventSourceInput, PluginDef } from "@fullcalendar/core";
import froca from "../../services/froca.js"; import froca from "../../services/froca.js";
import ViewMode, { type ViewModeArgs } from "./view_mode.js"; import ViewMode, { type ViewModeArgs } from "./view_mode.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
@@ -9,6 +9,9 @@ import options from "../../services/options.js";
import dialogService from "../../services/dialog.js"; import dialogService from "../../services/dialog.js";
import attributes from "../../services/attributes.js"; import attributes from "../../services/attributes.js";
import type { EventData } from "../../components/app_context.js"; import type { EventData } from "../../components/app_context.js";
import utils from "../../services/utils.js";
import date_notes from "../../services/date_notes.js";
import appContext from "../../components/app_context.js";
const TPL = ` const TPL = `
<div class="calendar-view"> <div class="calendar-view">
@@ -66,6 +69,7 @@ export default class CalendarView extends ViewMode {
private noteIds: string[]; private noteIds: string[];
private parentNote: FNote; private parentNote: FNote;
private calendar?: Calendar; private calendar?: Calendar;
private isCalendarRoot: boolean;
constructor(args: ViewModeArgs) { constructor(args: ViewModeArgs) {
super(args); super(args);
@@ -74,31 +78,66 @@ export default class CalendarView extends ViewMode {
this.$calendarContainer = this.$root.find(".calendar-container"); this.$calendarContainer = this.$root.find(".calendar-container");
this.noteIds = args.noteIds; this.noteIds = args.noteIds;
this.parentNote = args.parentNote; this.parentNote = args.parentNote;
console.log(args); this.isCalendarRoot = false;
args.$parent.append(this.$root); args.$parent.append(this.$root);
} }
get isFullHeight(): boolean {
return true;
}
async renderList(): Promise<JQuery<HTMLElement> | undefined> { async renderList(): Promise<JQuery<HTMLElement> | undefined> {
const isEditable = true; this.isCalendarRoot = this.parentNote.hasLabel("calendarRoot") || this.parentNote.hasLabel("workspaceCalendarRoot");
const isEditable = !this.isCalendarRoot;
const { Calendar } = await import("@fullcalendar/core"); const { Calendar } = await import("@fullcalendar/core");
const plugins: PluginDef[] = []; const plugins: PluginDef[] = [];
plugins.push((await import("@fullcalendar/daygrid")).default); plugins.push((await import("@fullcalendar/daygrid")).default);
if (isEditable || this.isCalendarRoot) {
if (isEditable) {
plugins.push((await import("@fullcalendar/interaction")).default); plugins.push((await import("@fullcalendar/interaction")).default);
} }
let eventBuilder: EventSourceFunc;
if (!this.isCalendarRoot) {
eventBuilder = async () => await this.#buildEvents(this.noteIds)
} else {
eventBuilder = async (e: EventSourceFuncArg) => await this.#buildEventsForCalendar(e);
}
const calendar = new Calendar(this.$calendarContainer[0], { const calendar = new Calendar(this.$calendarContainer[0], {
plugins, plugins,
initialView: "dayGridMonth", initialView: "dayGridMonth",
events: async () => await CalendarView.#buildEvents(this.noteIds), events: eventBuilder,
editable: isEditable, editable: isEditable,
selectable: isEditable, selectable: isEditable,
select: (e) => this.#onCalendarSelection(e), select: (e) => this.#onCalendarSelection(e),
eventChange: (e) => this.#onEventMoved(e), eventChange: (e) => this.#onEventMoved(e),
firstDay: options.getInt("firstDayOfWeek") ?? 0, firstDay: options.getInt("firstDayOfWeek") ?? 0,
locale: await CalendarView.#getLocale() weekends: !this.parentNote.hasAttribute("label", "calendar:hideWeekends"),
weekNumbers: this.parentNote.hasAttribute("label", "calendar:weekNumbers"),
locale: await CalendarView.#getLocale(),
height: "100%",
eventContent: (e => {
let html = "";
const iconClass = e.event.extendedProps.iconClass;
if (iconClass) {
html += `<span class="${iconClass}"></span> `;
}
html += utils.escapeHtml(e.event.title);
return { html };
}),
dateClick: async (e) => {
if (!this.isCalendarRoot) {
return;
}
const note = await date_notes.getDayNote(e.dateStr);
if (note) {
appContext.tabManager.getActiveContext().setNote(note.noteId);
}
}
}); });
calendar.render(); calendar.render();
this.calendar = calendar; this.calendar = calendar;
@@ -174,47 +213,113 @@ export default class CalendarView extends ViewMode {
CalendarView.#setAttribute(note, "label", "endDate", endDate); CalendarView.#setAttribute(note, "label", "endDate", endDate);
} }
entitiesReloadedEvents({ loadResults }: EventData<"entitiesReloaded">): void { onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">) {
// Refresh note IDs if they got changed. // Refresh note IDs if they got changed.
if (loadResults.getBranchRows().some((branch) => branch.parentNoteId == this.parentNote.noteId)) { if (loadResults.getBranchRows().some((branch) => branch.parentNoteId === this.parentNote.noteId)) {
this.noteIds = this.parentNote.getChildNoteIds(); this.noteIds = this.parentNote.getChildNoteIds();
} }
// Refresh calendar on attribute change.
if (loadResults.getAttributeRows().some((attribute) => attribute.noteId === this.parentNote.noteId && attribute.name?.startsWith("calendar:"))) {
return true;
}
// Refresh dataset on subnote change.
if (this.calendar && loadResults.getAttributeRows().some((a) => this.noteIds.includes(a.noteId ?? ""))) { if (this.calendar && loadResults.getAttributeRows().some((a) => this.noteIds.includes(a.noteId ?? ""))) {
this.calendar.refetchEvents(); this.calendar.refetchEvents();
} }
} }
static async #buildEvents(noteIds: string[]) { async #buildEventsForCalendar(e: EventSourceFuncArg) {
const events = [];
// Gather all the required date note IDs.
const dateRange = utils.getMonthsInDateRange(e.startStr, e.endStr);
let allDateNoteIds: string[] = [];
for (const month of dateRange) {
// TODO: Deduplicate get type.
const dateNotesForMonth = await server.get<Record<string, string>>(`special-notes/notes-for-month/${month}?calendarRoot=${this.parentNote.noteId}`);
const dateNoteIds = Object.values(dateNotesForMonth);
allDateNoteIds = [ ...allDateNoteIds, ...dateNoteIds ];
}
// Request all the date notes.
const dateNotes = await froca.getNotes(allDateNoteIds);
const childNoteToDateMapping: Record<string, string> = {};
for (const dateNote of dateNotes) {
const startDate = dateNote.getLabelValue("dateNote");
if (!startDate) {
continue;
}
events.push(await CalendarView.#buildEvent(dateNote, startDate));
if (dateNote.hasChildren()) {
const childNoteIds = dateNote.getChildNoteIds();
for (const childNoteId of childNoteIds) {
childNoteToDateMapping[childNoteId] = startDate;
}
}
}
// Request all child notes of date notes in a single run.
const childNoteIds = Object.keys(childNoteToDateMapping);
const childNotes = await froca.getNotes(childNoteIds);
for (const childNote of childNotes) {
const startDate = childNoteToDateMapping[childNote.noteId];
const event = await CalendarView.#buildEvent(childNote, startDate);
events.push(event);
}
return events.flat();
}
async #buildEvents(noteIds: string[]) {
const notes = await froca.getNotes(noteIds); const notes = await froca.getNotes(noteIds);
const events: EventSourceInput = []; const events: EventSourceInput = [];
for (const note of notes) { for (const note of notes) {
const startDate = note.getAttributeValue("label", "startDate"); let startDate = note.getLabelValue("startDate");
const customTitle = note.getAttributeValue("label", "calendar:title");
if (note.hasChildren()) {
const childrenEventData = await this.#buildEvents(note.getChildNoteIds());
if (childrenEventData.length > 0) {
events.push(childrenEventData);
}
}
if (!startDate) { if (!startDate) {
continue; continue;
} }
const titles = await CalendarView.#parseCustomTitle(customTitle, note); const endDate = note.getAttributeValue("label", "endDate");
for (const title of titles) { events.push(await CalendarView.#buildEvent(note, startDate, endDate));
const eventData: typeof events[0] = {
title: title,
start: startDate,
url: `#${note.noteId}`,
noteId: note.noteId
};
const endDate = CalendarView.#offsetDate(note.getAttributeValue("label", "endDate") ?? startDate, 1);
if (endDate) {
eventData.end = CalendarView.#formatDateToLocalISO(endDate);
}
events.push(eventData);
}
} }
return events.flat();
}
static async #buildEvent(note: FNote, startDate: string, endDate?: string | null) {
const customTitle = note.getLabelValue("calendar:title");
const titles = await CalendarView.#parseCustomTitle(customTitle, note);
const color = note.getLabelValue("calendar:color") ?? note.getLabelValue("color");
const events: EventInput[] = [];
for (const title of titles) {
const eventData: EventInput = {
title: title,
start: startDate,
url: `#${note.noteId}`,
noteId: note.noteId,
color: color ?? undefined,
iconClass: note.getLabelValue("iconClass")
};
const endDateOffset = CalendarView.#offsetDate(endDate ?? startDate, 1);
if (endDateOffset) {
eventData.end = CalendarView.#formatDateToLocalISO(endDateOffset);
}
events.push(eventData);
}
return events; return events;
} }

View File

@@ -17,8 +17,19 @@ export default abstract class ViewMode {
abstract renderList(): Promise<JQuery<HTMLElement> | undefined>; abstract renderList(): Promise<JQuery<HTMLElement> | undefined>;
entitiesReloadedEvents(e: EventData<"entitiesReloaded">) { /**
* Called whenever an "entitiesReloaded" event has been received by the parent component.
*
* @param e the event data.
* @return {@code true} if the view should be re-rendered, a falsy value otherwise.
*/
onEntitiesReloaded(e: EventData<"entitiesReloaded">): boolean | void {
// Do nothing by default. // Do nothing by default.
} }
get isFullHeight() {
// Override to change its value.
return false;
}
} }

View File

@@ -144,6 +144,9 @@
--alert-bar-background: #6b6b6b3b; --alert-bar-background: #6b6b6b3b;
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #000000b3;
--right-pane-item-hover-background: #ffffff26; --right-pane-item-hover-background: #ffffff26;
--right-pane-item-hover-color: white; --right-pane-item-hover-color: white;

View File

@@ -138,6 +138,9 @@
--alert-bar-background: #32637b29; --alert-bar-background: #32637b29;
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #00000033;
--new-tab-button-background: #d8d8d8; --new-tab-button-background: #d8d8d8;
--new-tab-button-color: #3a3a3a; --new-tab-button-color: #3a3a3a;
--new-tab-button-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); --new-tab-button-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);

View File

@@ -140,6 +140,8 @@ input:not([type]),
input[type="text"], input[type="text"],
input[type="number"], input[type="number"],
input[type="password"], input[type="password"],
input[type="date"],
input[type="time"],
input[type="datetime-local"], input[type="datetime-local"],
textarea.form-control, textarea.form-control,
textarea, textarea,
@@ -156,6 +158,8 @@ input:not([type]):hover,
input[type="text"]:hover, input[type="text"]:hover,
input[type="number"]:hover, input[type="number"]:hover,
input[type="password"]:hover, input[type="password"]:hover,
input[type="date"]:hover,
input[type="time"]:hover,
input[type="datetime-local"]:hover, input[type="datetime-local"]:hover,
textarea.form-control:hover, textarea.form-control:hover,
textarea:hover, textarea:hover,
@@ -168,6 +172,8 @@ input:not([type]):focus,
input[type="text"]:focus, input[type="text"]:focus,
input[type="number"]:focus, input[type="number"]:focus,
input[type="password"]:focus, input[type="password"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="datetime-local"]:focus, input[type="datetime-local"]:focus,
textarea.form-control:focus, textarea.form-control:focus,
textarea:focus, textarea:focus,
@@ -193,6 +199,11 @@ input::selection,
color: var(--input-selection-text-color); color: var(--input-selection-text-color);
} }
.form-text {
color: var(--main-text-color);
opacity: 0.8;
}
/* Input groups */ /* Input groups */
.input-group { .input-group {
@@ -270,17 +281,20 @@ input::selection,
background: transparent !important; background: transparent !important;
} }
.input-group .input-group-text:not(button):not(a) { .input-group .input-group-text {
/* Background color hijack */ /* Background color hijack */
--accented-background-color: transparent; --accented-background-color: transparent;
border: none; border: none;
font-style: italic;
color: var(--input-placeholder-color); color: var(--input-placeholder-color);
user-select: none; user-select: none;
cursor: default; cursor: default;
} }
.input-group .input-group-text:not(button):not(a):not(.bx) {
font-style: italic;
}
/* Combo box-like dropdown buttons */ /* Combo box-like dropdown buttons */
.select-button.dropdown-toggle::after { .select-button.dropdown-toggle::after {

View File

@@ -1836,3 +1836,110 @@ body.background-effects.zen #root-widget {
animation: alert-show 300ms ease-in; animation: alert-show 300ms ease-in;
border-bottom: 2px solid #0000001c !important; border-bottom: 2px solid #0000001c !important;
} }
/*
* Promoted attributes
*/
/* The promoted attributes section */
div.promoted-attributes-container {
display: flex;
margin-right: 10%;
padding: 6px 0;
gap: 8px;
align-items: stretch;
container-type: inline-size;
}
div.promoted-attributes-container,
div.promoted-attributes-container input {
font-size: .9rem;
}
/* A promoted attribute card */
div.promoted-attribute-cell {
--pa-card-padding-left: 16px;
--pa-card-padding-right: 2px;
--input-background-color: transparent;
box-shadow: 1px 1px 2px var(--promoted-attribute-card-shadow-color);
display: inline-flex;
margin: 0;
border-radius: 8px;
padding: 2px var(--pa-card-padding-right) 2px var(--pa-card-padding-left);
background: var(--promoted-attribute-card-background-color);
overflow-y: visible;
}
@container (max-width: 500px) {
/* Narrow promoted attributes section */
div.promoted-attribute-cell {
flex-grow: 1;
}
}
/* A promoted attribute card (boolean attribute) */
div.promoted-attribute-cell:has(input[type="checkbox"]):not(:has(.multiplicity > span)) {
/* Checbox attribute, without multiplicity */
padding-right: var(--pa-card-padding-left);
}
div.promoted-attribute-cell > * {
display: inline-block;
}
div.promoted-attribute-cell > label {
font-weight: normal;
white-space: nowrap;
opacity: .75;
}
div.promoted-attribute-cell:not(:has(input[type="checkbox"])) > label::after {
content: ":";
}
div.promoted-attribute-cell div.input-group {
min-height: auto;
padding: 1px 6px;
}
div.promoted-attribute-cell input {
padding: 2px 8px;
font-weight: 500;
}
div.promoted-attribute-cell input[type="text"] {
width: 10em !important;
}
div.promoted-attribute-cell input[type="number"] {
width: 6em !important;
}
div.promoted-attribute-cell span.open-external-link-button {
display: flex;
padding: 0;
font-size: 1.2em;
}
div.promoted-attribute-cell .tn-checkbox {
--box-label-gap: 0;
height: 1cap;
}
/* The <div> containing the checkbox for a promoted boolean attribute */
div.promoted-attribute-cell div:has(input[type="checkbox"]) {
order: -1; /* Relocate the checkbox before the label */
margin-right: 1.5em;
}
/* The element containing the "new attribute" and "remove this attribute button" */
div.promoted-attribute-cell .multiplicity:has(span) {
--icon-button-size: 24px;
margin-left: 8px;
margin-right: calc(var(--pa-card-padding-left) - var(--pa-card-padding-right));
font-size: 0; /* Prevent whitespaces creating a gap between buttons */
}

View File

@@ -988,9 +988,7 @@
"web_view": { "web_view": {
"web_view": "网页视图", "web_view": "网页视图",
"embed_websites": "网页视图类型的笔记允许您将网站嵌入到 Trilium 中。", "embed_websites": "网页视图类型的笔记允许您将网站嵌入到 Trilium 中。",
"create_label": "首先,请创建一个带有您要嵌入的 URL 地址的标签,例如 #webViewSrc=\"https://www.bing.com\"", "create_label": "首先,请创建一个带有您要嵌入的 URL 地址的标签,例如 #webViewSrc=\"https://www.bing.com\""
"disclaimer": "实验性功能免责声明",
"experimental_note": "网页视图是一种实验性的笔记类型,将来可能会被移除或大幅更改。网页视图只在桌面端有效。"
}, },
"backend_log": { "backend_log": {
"refresh": "刷新" "refresh": "刷新"
@@ -1122,7 +1120,7 @@
"attachment_erasure_timeout": { "attachment_erasure_timeout": {
"attachment_erasure_timeout": "附件清理超时", "attachment_erasure_timeout": "附件清理超时",
"attachment_auto_deletion_description": "如果附件在一段时间后不再被笔记引用,它们将自动被删除(并被清理)。", "attachment_auto_deletion_description": "如果附件在一段时间后不再被笔记引用,它们将自动被删除(并被清理)。",
"erase_attachments_after_x_seconds": "在附件在笔记中未被使用 X 秒后清理", "erase_attachments_after": "",
"manual_erasing_description": "您还可以手动触发清理(而不考虑上述定义的超时时间):", "manual_erasing_description": "您还可以手动触发清理(而不考虑上述定义的超时时间):",
"erase_unused_attachments_now": "立即清理未使用的附件笔记", "erase_unused_attachments_now": "立即清理未使用的附件笔记",
"unused_attachments_erased": "未使用的附件已被删除。" "unused_attachments_erased": "未使用的附件已被删除。"
@@ -1134,15 +1132,15 @@
"note_erasure_timeout": { "note_erasure_timeout": {
"note_erasure_timeout_title": "笔记清理超时", "note_erasure_timeout_title": "笔记清理超时",
"note_erasure_description": "被删除的笔记(以及属性、历史版本等)最初仅被标记为“删除”,可以从“最近修改”对话框中恢复它们。经过一段时间后,已删除的笔记会被“清理”,这意味着它们的内容将无法恢复。此设置允许您配置从删除到清除笔记之间的时间长度。", "note_erasure_description": "被删除的笔记(以及属性、历史版本等)最初仅被标记为“删除”,可以从“最近修改”对话框中恢复它们。经过一段时间后,已删除的笔记会被“清理”,这意味着它们的内容将无法恢复。此设置允许您配置从删除到清除笔记之间的时间长度。",
"erase_notes_after": "Erase notes after", "erase_notes_after": "",
"manual_erasing_description": "您还可以手动触发清理(不考虑上述定义的超时):", "manual_erasing_description": "您还可以手动触发清理(不考虑上述定义的超时):",
"erase_deleted_notes_now": "立即清理已删除的笔记", "erase_deleted_notes_now": "立即清理已删除的笔记",
"deleted_notes_erased": "已删除的笔记已被清理。" "deleted_notes_erased": "已删除的笔记已被清理。"
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "笔记修改快照间隔", "note_revisions_snapshot_interval_title": "",
"note_revisions_snapshot_description": "笔记修改快照时间间隔是指经过多少秒后会为笔记创建新的修改历史。更多信息请参见<a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a>。", "note_revisions_snapshot_description": "",
"snapshot_time_interval_label": "笔记修改快照时间间隔(单位:秒)" "snapshot_time_interval_label": ""
}, },
"revisions_snapshot_limit": { "revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "笔记历史快照限制", "note_revisions_snapshot_limit_title": "笔记历史快照限制",
@@ -1261,7 +1259,7 @@
"protected_session_timeout_description": "保护会话超时是一个时间段,超时后保护会话会从浏览器内存中清除。这是从最后一次与保护笔记的交互开始计时的。更多信息请见", "protected_session_timeout_description": "保护会话超时是一个时间段,超时后保护会话会从浏览器内存中清除。这是从最后一次与保护笔记的交互开始计时的。更多信息请见",
"wiki": "维基", "wiki": "维基",
"for_more_info": "更多信息。", "for_more_info": "更多信息。",
"protected_session_timeout_label": "保护会话超时(秒)", "protected_session_timeout_label": "",
"reset_confirmation": "重置密码将永久丧失对所有现受保护笔记的访问。您真的要重置密码吗?", "reset_confirmation": "重置密码将永久丧失对所有现受保护笔记的访问。您真的要重置密码吗?",
"reset_success_message": "密码已重置。请设置新密码", "reset_success_message": "密码已重置。请设置新密码",
"change_password_heading": "更改密码", "change_password_heading": "更改密码",

View File

@@ -1,6 +1,7 @@
{ {
"about": { "about": {
"title": "Über TriliumNext Notes", "title": "Über TriliumNext Notes",
"close": "Schließen",
"homepage": "Startseite:", "homepage": "Startseite:",
"app_version": "App-Version:", "app_version": "App-Version:",
"db_version": "DB-Version:", "db_version": "DB-Version:",
@@ -52,10 +53,15 @@
"chosen_actions": "Ausgewählte Aktionen", "chosen_actions": "Ausgewählte Aktionen",
"execute_bulk_actions": "Massenaktionen ausführen", "execute_bulk_actions": "Massenaktionen ausführen",
"bulk_actions_executed": "Massenaktionen wurden erfolgreich ausgeführt.", "bulk_actions_executed": "Massenaktionen wurden erfolgreich ausgeführt.",
"none_yet": "Noch keine ... Füge eine Aktion hinzu, indem du oben auf eine der verfügbaren Aktionen klicken." "none_yet": "Noch keine ... Füge eine Aktion hinzu, indem du oben auf eine der verfügbaren Aktionen klicken.",
"labels": "Labels",
"relations": "Beziehungen",
"notes": "Notizen",
"other": "Andere"
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Notizen klonen nach...", "clone_notes_to": "Notizen klonen nach...",
"close":"Schließen",
"help_on_links": "Hilfe zu Links", "help_on_links": "Hilfe zu Links",
"notes_to_clone": "Notizen zum Klonen", "notes_to_clone": "Notizen zum Klonen",
"target_parent_note": "Ziel-Übergeordnetenotiz", "target_parent_note": "Ziel-Übergeordnetenotiz",
@@ -68,6 +74,7 @@
}, },
"confirm": { "confirm": {
"confirmation": "Bestätigung", "confirmation": "Bestätigung",
"close":"Schließen",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"ok": "OK", "ok": "OK",
"are_you_sure_remove_note": "Bist du sicher, dass du \"{{title}}\" von der Beziehungskarte entfernen möchten? ", "are_you_sure_remove_note": "Bist du sicher, dass du \"{{title}}\" von der Beziehungskarte entfernen möchten? ",
@@ -76,6 +83,7 @@
}, },
"delete_notes": { "delete_notes": {
"delete_notes_preview": "Vorschau der Notizen löschen", "delete_notes_preview": "Vorschau der Notizen löschen",
"close": "Schließen",
"delete_all_clones_description": "auch alle Klone löschen (kann bei letzte Änderungen rückgängig gemacht werden)", "delete_all_clones_description": "auch alle Klone löschen (kann bei letzte Änderungen rückgängig gemacht werden)",
"erase_notes_description": "Beim normalen (vorläufigen) Löschen werden die Notizen nur als gelöscht markiert und sie können innerhalb eines bestimmten Zeitraums (im Dialogfeld „Letzte Änderungen“) wiederhergestellt werden. Wenn du diese Option aktivierst, werden die Notizen sofort gelöscht und es ist nicht möglich, die Notizen wiederherzustellen.", "erase_notes_description": "Beim normalen (vorläufigen) Löschen werden die Notizen nur als gelöscht markiert und sie können innerhalb eines bestimmten Zeitraums (im Dialogfeld „Letzte Änderungen“) wiederhergestellt werden. Wenn du diese Option aktivierst, werden die Notizen sofort gelöscht und es ist nicht möglich, die Notizen wiederherzustellen.",
"erase_notes_warning": "Notizen dauerhaft löschen (kann nicht rückgängig gemacht werden), einschließlich aller Klone. Dadurch wird ein Neuladen der Anwendung erzwungen.", "erase_notes_warning": "Notizen dauerhaft löschen (kann nicht rückgängig gemacht werden), einschließlich aller Klone. Dadurch wird ein Neuladen der Anwendung erzwungen.",
@@ -83,12 +91,15 @@
"no_note_to_delete": "Es werden keine Notizen gelöscht (nur Klone).", "no_note_to_delete": "Es werden keine Notizen gelöscht (nur Klone).",
"broken_relations_to_be_deleted": "Folgende Beziehungen werden gelöst und gelöscht (<span class=\"broke-relations-count\"></span>)", "broken_relations_to_be_deleted": "Folgende Beziehungen werden gelöst und gelöscht (<span class=\"broke-relations-count\"></span>)",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"ok": "OK" "ok": "OK",
"deleted_relation_text": "Notiz {{- note}} (soll gelöscht werden) wird von Beziehung {{- relation}} ausgehend von {{- source}} referenziert."
}, },
"export": { "export": {
"export_note_title": "Notiz exportieren", "export_note_title": "Notiz exportieren",
"close": "Schließen", "close": "Schließen",
"export_type_subtree": "Diese Notiz und alle ihre Unternotizen", "export_type_subtree": "Diese Notiz und alle ihre Unternotizen",
"format_html": "HTML - empfohlen, da dadurch alle Formatierungen erhalten bleiben",
"format_html_zip": "HTML im ZIP-Archiv dies wird empfohlen, da dadurch die gesamte Formatierung erhalten bleibt.", "format_html_zip": "HTML im ZIP-Archiv dies wird empfohlen, da dadurch die gesamte Formatierung erhalten bleibt.",
"format_markdown": "Markdown dadurch bleiben die meisten Formatierungen erhalten.", "format_markdown": "Markdown dadurch bleiben die meisten Formatierungen erhalten.",
"format_opml": "OPML Outliner-Austauschformat nur für Text. Formatierungen, Bilder und Dateien sind nicht enthalten.", "format_opml": "OPML Outliner-Austauschformat nur für Text. Formatierungen, Bilder und Dateien sind nicht enthalten.",
@@ -99,7 +110,8 @@
"choose_export_type": "Bitte wähle zuerst den Exporttypen aus", "choose_export_type": "Bitte wähle zuerst den Exporttypen aus",
"export_status": "Exportstatus", "export_status": "Exportstatus",
"export_in_progress": "Export läuft: {{progressCount}}", "export_in_progress": "Export läuft: {{progressCount}}",
"export_finished_successfully": "Der Export wurde erfolgreich abgeschlossen." "export_finished_successfully": "Der Export wurde erfolgreich abgeschlossen.",
"format_pdf": "PDF - für Ausdrucke oder Teilen."
}, },
"help": { "help": {
"fullDocumentation": "Hilfe (gesamte Dokumentation ist <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a> verfügbar)", "fullDocumentation": "Hilfe (gesamte Dokumentation ist <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a> verfügbar)",
@@ -171,10 +183,20 @@
"codeImportedAsCode": "Importiere erkannte Codedateien (z. B. <code>.json</code>) als Codenotizen, wenn die Metadaten unklar sind", "codeImportedAsCode": "Importiere erkannte Codedateien (z. B. <code>.json</code>) als Codenotizen, wenn die Metadaten unklar sind",
"replaceUnderscoresWithSpaces": "Ersetze Unterstriche in importierten Notiznamen durch Leerzeichen", "replaceUnderscoresWithSpaces": "Ersetze Unterstriche in importierten Notiznamen durch Leerzeichen",
"import": "Import", "import": "Import",
"failed": "Import fehlgeschlagen: {{message}}." "failed": "Import fehlgeschlagen: {{message}}.",
"html_import_tags": {
"title": "HTML Tag Import",
"description": "Festlegen, welche HTML tags beim Import von Notizen beibehalten werden sollen. Tags, die nicht in dieser Liste stehen, werden beim Import entfernt. Einige tags (wie bspw. 'script') werden aus Sicherheitsgründen immer entfernt.",
"placeholder": "HTML tags eintragen, pro Zeile nur einer pro Zeile",
"reset_button": "Zur Standardliste zurücksetzen"
},
"import-status": "Importstatus",
"in-progress": "Import läuft: {{progress}}",
"successful": "Import erfolgreich abgeschlossen."
}, },
"include_note": { "include_note": {
"dialog_title": "Notiz beifügen", "dialog_title": "Notiz beifügen",
"close": "Schließen",
"label_note": "Notiz", "label_note": "Notiz",
"placeholder_search": "Suche nach einer Notiz anhand ihres Namens", "placeholder_search": "Suche nach einer Notiz anhand ihres Namens",
"box_size_prompt": "Kartongröße des beigelegten Zettels:", "box_size_prompt": "Kartongröße des beigelegten Zettels:",
@@ -190,16 +212,19 @@
}, },
"jump_to_note": { "jump_to_note": {
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens", "search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
"close": "Schließen",
"search_button": "Suche im Volltext: <kbd>Strg+Eingabetaste</kbd>" "search_button": "Suche im Volltext: <kbd>Strg+Eingabetaste</kbd>"
}, },
"markdown_import": { "markdown_import": {
"dialog_title": "Markdown-Import", "dialog_title": "Markdown-Import",
"close": "Schließen",
"modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“.", "modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“.",
"import_button": "Importieren Strg+Eingabe", "import_button": "Importieren Strg+Eingabe",
"import_success": "Markdown-Inhalt wurde in das Dokument importiert." "import_success": "Markdown-Inhalt wurde in das Dokument importiert."
}, },
"move_to": { "move_to": {
"dialog_title": "Notizen verschieben nach ...", "dialog_title": "Notizen verschieben nach ...",
"close": "Schließen",
"notes_to_move": "Notizen zum Verschieben", "notes_to_move": "Notizen zum Verschieben",
"target_parent_note": "Ziel-Elternnotiz", "target_parent_note": "Ziel-Elternnotiz",
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens", "search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
@@ -209,16 +234,19 @@
}, },
"note_type_chooser": { "note_type_chooser": {
"modal_title": "Wähle den Notiztyp aus", "modal_title": "Wähle den Notiztyp aus",
"close": "Schließen",
"modal_body": "Wähle den Notiztyp / die Vorlage der neuen Notiz:", "modal_body": "Wähle den Notiztyp / die Vorlage der neuen Notiz:",
"templates": "Vorlagen:" "templates": "Vorlagen:"
}, },
"password_not_set": { "password_not_set": {
"title": "Das Passwort ist nicht festgelegt", "title": "Das Passwort ist nicht festgelegt",
"close": "Schließen",
"body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt.", "body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt.",
"body2": "Um Notizen verschlüsseln zu können, klicke <a class=\"open-password-options-button\" href=\"javascript:\">hier</a> um das Optionsmenu zu öffnen und ein Passwort zu setzen." "body2": "Um Notizen verschlüsseln zu können, klicke <a class=\"open-password-options-button\" href=\"javascript:\">hier</a> um das Optionsmenu zu öffnen und ein Passwort zu setzen."
}, },
"prompt": { "prompt": {
"title": "Prompt", "title": "Prompt",
"close": "Schließen",
"ok": "OK <kbd>Eingabe</kbd>", "ok": "OK <kbd>Eingabe</kbd>",
"defaultTitle": "Prompt" "defaultTitle": "Prompt"
}, },
@@ -232,6 +260,7 @@
"recent_changes": { "recent_changes": {
"title": "Aktuelle Änderungen", "title": "Aktuelle Änderungen",
"erase_notes_button": "Jetzt gelöschte Notizen löschen", "erase_notes_button": "Jetzt gelöschte Notizen löschen",
"close": "Schließen",
"deleted_notes_message": "Gelöschte Notizen wurden gelöscht.", "deleted_notes_message": "Gelöschte Notizen wurden gelöscht.",
"no_changes_message": "Noch keine Änderungen...", "no_changes_message": "Noch keine Änderungen...",
"undelete_link": "Wiederherstellen", "undelete_link": "Wiederherstellen",
@@ -242,6 +271,7 @@
"delete_all_revisions": "Lösche alle Revisionen dieser Notiz", "delete_all_revisions": "Lösche alle Revisionen dieser Notiz",
"delete_all_button": "Alle Revisionen löschen", "delete_all_button": "Alle Revisionen löschen",
"help_title": "Hilfe zu Notizrevisionen", "help_title": "Hilfe zu Notizrevisionen",
"close": "Schließen",
"revision_last_edited": "Diese Revision wurde zuletzt am {{date}} bearbeitet", "revision_last_edited": "Diese Revision wurde zuletzt am {{date}} bearbeitet",
"confirm_delete_all": "Möchtest du alle Revisionen dieser Notiz löschen? Durch diese Aktion werden der Titel und der Inhalt der Revision gelöscht, die Metadaten der Revision bleiben jedoch erhalten.", "confirm_delete_all": "Möchtest du alle Revisionen dieser Notiz löschen? Durch diese Aktion werden der Titel und der Inhalt der Revision gelöscht, die Metadaten der Revision bleiben jedoch erhalten.",
"no_revisions": "Für diese Notiz gibt es noch keine Revisionen...", "no_revisions": "Für diese Notiz gibt es noch keine Revisionen...",
@@ -249,9 +279,9 @@
"confirm_restore": "Möchtest du diese Revision wiederherstellen? Dadurch werden der aktuelle Titel und Inhalt der Notiz mit dieser Revision überschrieben.", "confirm_restore": "Möchtest du diese Revision wiederherstellen? Dadurch werden der aktuelle Titel und Inhalt der Notiz mit dieser Revision überschrieben.",
"delete_button": "Lösche diese Revision", "delete_button": "Lösche diese Revision",
"confirm_delete": "Möchtest du diese Revision löschen? Durch diese Aktion werden der Titel und der Inhalt der Revision gelöscht, die Metadaten der Revision bleiben jedoch erhalten.", "confirm_delete": "Möchtest du diese Revision löschen? Durch diese Aktion werden der Titel und der Inhalt der Revision gelöscht, die Metadaten der Revision bleiben jedoch erhalten.",
"revisions_deleted": "Hinweisrevisionen wurden gelöscht.", "revisions_deleted": "Notizrevisionen wurden gelöscht.",
"revision_restored": "Die Notizrevision wurde wiederhergestellt.", "revision_restored": "Die Notizrevision wurde wiederhergestellt.",
"revision_deleted": "Hinweisrevision wurde gelöscht.", "revision_deleted": "Notizrevision wurde gelöscht.",
"snapshot_interval": "Notizrevisionen-Snapshot Intervall: {{seconds}}s.", "snapshot_interval": "Notizrevisionen-Snapshot Intervall: {{seconds}}s.",
"maximum_revisions": "Maximale Revisionen für aktuelle Notiz: {{number}}.", "maximum_revisions": "Maximale Revisionen für aktuelle Notiz: {{number}}.",
"settings": "Einstellungen für Notizrevisionen", "settings": "Einstellungen für Notizrevisionen",
@@ -263,6 +293,7 @@
}, },
"sort_child_notes": { "sort_child_notes": {
"sort_children_by": "Unternotizen sortieren nach...", "sort_children_by": "Unternotizen sortieren nach...",
"close": "Schließen",
"sorting_criteria": "Sortierkriterien", "sorting_criteria": "Sortierkriterien",
"title": "Titel", "title": "Titel",
"date_created": "Erstellungsdatum", "date_created": "Erstellungsdatum",
@@ -280,6 +311,7 @@
}, },
"upload_attachments": { "upload_attachments": {
"upload_attachments_to_note": "Lade Anhänge zur Notiz hoch", "upload_attachments_to_note": "Lade Anhänge zur Notiz hoch",
"close": "Schließen",
"choose_files": "Wähle Dateien aus", "choose_files": "Wähle Dateien aus",
"files_will_be_uploaded": "Dateien werden als Anhänge in hochgeladen", "files_will_be_uploaded": "Dateien werden als Anhänge in hochgeladen",
"options": "Optionen", "options": "Optionen",
@@ -338,13 +370,14 @@
"disable_inclusion": "Skripte mit dieser Bezeichnung werden nicht in die Ausführung des übergeordneten Skripts einbezogen.", "disable_inclusion": "Skripte mit dieser Bezeichnung werden nicht in die Ausführung des übergeordneten Skripts einbezogen.",
"sorted": "Hält untergeordnete Notizen alphabetisch nach Titel sortiert", "sorted": "Hält untergeordnete Notizen alphabetisch nach Titel sortiert",
"sort_direction": "ASC (Standard) oder DESC", "sort_direction": "ASC (Standard) oder DESC",
"sort_folders_first": "Ordner (Notizen mit Kindern) sollten oben sortiert werden", "sort_folders_first": "Ordner (Notizen mit Unternotizen) sollten oben sortiert werden",
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen).", "top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen).",
"hide_promoted_attributes": "Heraufgestufte Attribute für diese Notiz ausblenden", "hide_promoted_attributes": "Heraufgestufte Attribute für diese Notiz ausblenden",
"read_only": "Der Editor befindet sich im schreibgeschützten Modus. Funktioniert nur für Text- und Codenotizen.", "read_only": "Der Editor befindet sich im schreibgeschützten Modus. Funktioniert nur für Text- und Codenotizen.",
"auto_read_only_disabled": "Text-/Codenotizen können automatisch in den Lesemodus versetzt werden, wenn sie zu groß sind. Du kannst dieses Verhalten für jede einzelne Notiz deaktivieren, indem du diese Beschriftung zur Notiz hinzufügst", "auto_read_only_disabled": "Text-/Codenotizen können automatisch in den Lesemodus versetzt werden, wenn sie zu groß sind. Du kannst dieses Verhalten für jede einzelne Notiz deaktivieren, indem du diese Beschriftung zur Notiz hinzufügst",
"app_css": "markiert CSS-Notizen, die in die Trilium-Anwendung geladen werden und somit zur Änderung des Aussehens von Trilium verwendet werden können.", "app_css": "markiert CSS-Notizen, die in die Trilium-Anwendung geladen werden und somit zur Änderung des Aussehens von Trilium verwendet werden können.",
"app_theme": "markiert CSS-Notizen, die vollständige Trilium-Themen sind und daher in den Trilium-Optionen verfügbar sind.", "app_theme": "markiert CSS-Notizen, die vollständige Trilium-Themen sind und daher in den Trilium-Optionen verfügbar sind.",
"app_theme_base": "markiert Notiz als \"nächste\" in der Reihe für ein Trilium-Theme als Grundlage für ein Custom-Theme. Ersetzt damit das Standard Theme.",
"css_class": "Der Wert dieser Bezeichnung wird dann als CSS-Klasse dem Knoten hinzugefügt, der die angegebene Notiz im Baum darstellt. Dies kann für fortgeschrittene Themen nützlich sein. Kann in Vorlagennotizen verwendet werden.", "css_class": "Der Wert dieser Bezeichnung wird dann als CSS-Klasse dem Knoten hinzugefügt, der die angegebene Notiz im Baum darstellt. Dies kann für fortgeschrittene Themen nützlich sein. Kann in Vorlagennotizen verwendet werden.",
"icon_class": "Der Wert dieser Bezeichnung wird als CSS-Klasse zum Symbol im Baum hinzugefügt, was dabei helfen kann, die Notizen im Baum visuell zu unterscheiden. Beispiel könnte bx bx-home sein Symbole werden von Boxicons übernommen. Kann in Vorlagennotizen verwendet werden.", "icon_class": "Der Wert dieser Bezeichnung wird als CSS-Klasse zum Symbol im Baum hinzugefügt, was dabei helfen kann, die Notizen im Baum visuell zu unterscheiden. Beispiel könnte bx bx-home sein Symbole werden von Boxicons übernommen. Kann in Vorlagennotizen verwendet werden.",
"page_size": "Anzahl der Elemente pro Seite in der Notizliste", "page_size": "Anzahl der Elemente pro Seite in der Notizliste",
@@ -406,7 +439,9 @@
"share_favicon": "Favicon-Notiz, die auf der freigegebenen Seite festgelegt werden soll. Normalerweise möchtest du es so einstellen, dass es Root teilt und es vererbbar macht. Die Favicon-Notiz muss sich ebenfalls im freigegebenen Unterbaum befinden. Erwäge die Verwendung von „share_hidden_from_tree“.", "share_favicon": "Favicon-Notiz, die auf der freigegebenen Seite festgelegt werden soll. Normalerweise möchtest du es so einstellen, dass es Root teilt und es vererbbar macht. Die Favicon-Notiz muss sich ebenfalls im freigegebenen Unterbaum befinden. Erwäge die Verwendung von „share_hidden_from_tree“.",
"is_owned_by_note": "ist Eigentum von Note", "is_owned_by_note": "ist Eigentum von Note",
"other_notes_with_name": "Other notes with {{attributeType}} name \"{{attributeName}}\"", "other_notes_with_name": "Other notes with {{attributeType}} name \"{{attributeName}}\"",
"and_more": "... und {{count}} mehr." "and_more": "... und {{count}} mehr.",
"print_landscape": "Beim Export als PDF, wird die Seitenausrichtung Querformat anstatt Hochformat verwendet.",
"print_page_size": "Beim Export als PDF, wird die Größe der Seite angepasst. Unterstützte Größen: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>."
}, },
"attribute_editor": { "attribute_editor": {
"help_text_body1": "Um ein Label hinzuzufügen, gebe einfach z.B. ein. <code>#rock</code> oder wenn du auch einen Wert hinzufügen möchten, dann z.B. <code>#year = 2024</code>", "help_text_body1": "Um ein Label hinzuzufügen, gebe einfach z.B. ein. <code>#rock</code> oder wenn du auch einen Wert hinzufügen möchten, dann z.B. <code>#year = 2024</code>",
@@ -491,7 +526,7 @@
"example_note": "<code>Notiz</code> alle übereinstimmenden Notizen werden in „Notiz“ umbenannt.", "example_note": "<code>Notiz</code> alle übereinstimmenden Notizen werden in „Notiz“ umbenannt.",
"example_new_title": "<code>NEU: ${note.title}</code> Übereinstimmende Notiztitel erhalten das Präfix „NEU:“", "example_new_title": "<code>NEU: ${note.title}</code> Übereinstimmende Notiztitel erhalten das Präfix „NEU:“",
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> übereinstimmende Notizen werden mit dem Erstellungsmonat und -datum der Notiz vorangestellt", "example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> übereinstimmende Notizen werden mit dem Erstellungsmonat und -datum der Notiz vorangestellt",
"api_docs": "Siehe API-Dokumente für <a hrefu003d'https://zadam.github.io/trilium/backend_api/Note.html'>Hinweis</a> und seinen <a hrefu003d'https://day.js.org/ docs/en/display/format'>dateCreatedObj / utcDateCreatedObj-Eigenschaften</a> für Details." "api_docs": "Siehe API-Dokumente für <a hrefu003d'https://zadam.github.io/trilium/backend_api/Note.html'>Notiz</a> und seinen <a hrefu003d'https://day.js.org/ docs/en/display/format'>dateCreatedObj / utcDateCreatedObj-Eigenschaften</a> für Details."
}, },
"add_relation": { "add_relation": {
"add_relation": "Beziehung hinzufügen", "add_relation": "Beziehung hinzufügen",
@@ -596,7 +631,7 @@
"zoom_in": "Hineinzoomen", "zoom_in": "Hineinzoomen",
"configure_launchbar": "Konfiguriere die Launchbar", "configure_launchbar": "Konfiguriere die Launchbar",
"show_shared_notes_subtree": "Unterbaum „Freigegebene Notizen“ anzeigen", "show_shared_notes_subtree": "Unterbaum „Freigegebene Notizen“ anzeigen",
"advanced": "Fortschrittlich", "advanced": "Erweitert",
"open_dev_tools": "Öffne die Entwicklungstools", "open_dev_tools": "Öffne die Entwicklungstools",
"open_sql_console": "Öffne die SQL-Konsole", "open_sql_console": "Öffne die SQL-Konsole",
"open_sql_console_history": "Öffne den SQL-Konsolenverlauf", "open_sql_console_history": "Öffne den SQL-Konsolenverlauf",
@@ -607,7 +642,9 @@
"show_hidden_subtree": "Versteckten Teilbaum anzeigen", "show_hidden_subtree": "Versteckten Teilbaum anzeigen",
"show_help": "Hilfe anzeigen", "show_help": "Hilfe anzeigen",
"about": "Über TriliumNext Notes", "about": "Über TriliumNext Notes",
"logout": "Abmelden" "logout": "Abmelden",
"show-cheatsheet": "Cheatsheet anzeigen",
"toggle-zen-mode": "Zen Modus"
}, },
"sync_status": { "sync_status": {
"unknown": "<p>Der Synchronisations-Status wird bekannt, sobald der nächste Synchronisierungsversuch gestartet wird.</p><p>Klicke, um eine Synchronisierung jetzt auszulösen.</p>", "unknown": "<p>Der Synchronisations-Status wird bekannt, sobald der nächste Synchronisierungsversuch gestartet wird.</p><p>Klicke, um eine Synchronisierung jetzt auszulösen.</p>",
@@ -641,7 +678,8 @@
"save_revision": "Revision speichern", "save_revision": "Revision speichern",
"convert_into_attachment_failed": "Konvertierung der Notiz '{{title}}' fehlgeschlagen.", "convert_into_attachment_failed": "Konvertierung der Notiz '{{title}}' fehlgeschlagen.",
"convert_into_attachment_successful": "Notiz '{{title}}' wurde als Anhang konvertiert.", "convert_into_attachment_successful": "Notiz '{{title}}' wurde als Anhang konvertiert.",
"convert_into_attachment_prompt": "Bist du dir sicher, dass du die Notiz '{{title}}' in ein Anhang der übergeordneten Notiz konvertieren möchtest?" "convert_into_attachment_prompt": "Bist du dir sicher, dass du die Notiz '{{title}}' in ein Anhang der übergeordneten Notiz konvertieren möchtest?",
"print_pdf": "Export als PDF..."
}, },
"onclick_button": { "onclick_button": {
"no_click_handler": "Das Schaltflächen-Widget „{{componentId}}“ hat keinen definierten Klick-Handler" "no_click_handler": "Das Schaltflächen-Widget „{{componentId}}“ hat keinen definierten Klick-Handler"
@@ -715,7 +753,8 @@
"collapse": "Einklappen", "collapse": "Einklappen",
"expand": "Ausklappen", "expand": "Ausklappen",
"book_properties": "Bucheigenschaften", "book_properties": "Bucheigenschaften",
"invalid_view_type": "Ungültiger Ansichtstyp „{{type}}“" "invalid_view_type": "Ungültiger Ansichtstyp „{{type}}“",
"calendar": "Kalender"
}, },
"edited_notes": { "edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...", "no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
@@ -759,15 +798,17 @@
"note_size_info": "Die Notizgröße bietet eine grobe Schätzung des Speicherbedarfs für diese Notiz. Es berücksichtigt den Inhalt der Notiz und den Inhalt ihrer Notizrevisionen.", "note_size_info": "Die Notizgröße bietet eine grobe Schätzung des Speicherbedarfs für diese Notiz. Es berücksichtigt den Inhalt der Notiz und den Inhalt ihrer Notizrevisionen.",
"calculate": "berechnen", "calculate": "berechnen",
"subtree_size": "(Teilbaumgröße: {{size}} in {{count}} Notizen)", "subtree_size": "(Teilbaumgröße: {{size}} in {{count}} Notizen)",
"title": "Hinweisinfo" "title": "Notizinfo"
}, },
"note_map": { "note_map": {
"open_full": "Vollständig erweitern", "open_full": "Vollständig erweitern",
"collapse": "Auf normale Größe reduzieren", "collapse": "Auf normale Größe reduzieren",
"title": "Hinweiskarte" "title": "Notizkarte",
"fix-nodes": "Knoten fixieren",
"link-distance": "Verbindungslänge"
}, },
"note_paths": { "note_paths": {
"title": "Hinweispfade", "title": "Notizpfade",
"clone_button": "Notiz an neuen Speicherort klonen...", "clone_button": "Notiz an neuen Speicherort klonen...",
"intro_placed": "Diese Notiz wird in den folgenden Pfaden abgelegt:", "intro_placed": "Diese Notiz wird in den folgenden Pfaden abgelegt:",
"intro_not_placed": "Diese Notiz ist noch nicht im Notizbaum platziert.", "intro_not_placed": "Diese Notiz ist noch nicht im Notizbaum platziert.",
@@ -864,7 +905,7 @@
"content_and_attachments_size": "Beachte die Inhaltsgröße einschließlich der Anhänge", "content_and_attachments_size": "Beachte die Inhaltsgröße einschließlich der Anhänge",
"content_and_attachments_and_revisions_size": "Beachte die Inhaltsgröße einschließlich Anhängen und Revisionen", "content_and_attachments_and_revisions_size": "Beachte die Inhaltsgröße einschließlich Anhängen und Revisionen",
"revision_count": "Anzahl der Revisionen", "revision_count": "Anzahl der Revisionen",
"children_count": "Anzahl der Kindernotizen", "children_count": "Anzahl der Unternotizen",
"parent_count": "Anzahl der Klone", "parent_count": "Anzahl der Klone",
"owned_label_count": "Anzahl der Etiketten", "owned_label_count": "Anzahl der Etiketten",
"owned_relation_count": "Anzahl der Beziehungen", "owned_relation_count": "Anzahl der Beziehungen",
@@ -900,14 +941,14 @@
}, },
"attachment_detail": { "attachment_detail": {
"open_help_page": "Hilfeseite zu Anhängen öffnen", "open_help_page": "Hilfeseite zu Anhängen öffnen",
"owning_note": "Hinweis zum Eigentümer:", "owning_note": "Eigentümernotiz: ",
"you_can_also_open": ", Du kannst auch das öffnen", "you_can_also_open": ", Du kannst auch das öffnen",
"list_of_all_attachments": "Liste aller Anhänge", "list_of_all_attachments": "Liste aller Anhänge",
"attachment_deleted": "Dieser Anhang wurde gelöscht." "attachment_deleted": "Dieser Anhang wurde gelöscht."
}, },
"attachment_list": { "attachment_list": {
"open_help_page": "Hilfeseite zu Anhängen öffnen", "open_help_page": "Hilfeseite zu Anhängen öffnen",
"owning_note": "Hinweis zum Eigentümer:", "owning_note": "Eigentümernotiz: ",
"upload_attachments": "Anhänge hochladen", "upload_attachments": "Anhänge hochladen",
"no_attachments": "Diese Notiz enthält keine Anhänge." "no_attachments": "Diese Notiz enthält keine Anhänge."
}, },
@@ -951,7 +992,7 @@
"specify_new_relation_name": "Gebe den neuen Beziehungsnamen an (erlaubte Zeichen: alphanumerisch, Doppelpunkt und Unterstrich):", "specify_new_relation_name": "Gebe den neuen Beziehungsnamen an (erlaubte Zeichen: alphanumerisch, Doppelpunkt und Unterstrich):",
"connection_exists": "Die Verbindung „{{name}}“ zwischen diesen Notizen besteht bereits.", "connection_exists": "Die Verbindung „{{name}}“ zwischen diesen Notizen besteht bereits.",
"start_dragging_relations": "Beginne hier mit dem Ziehen von Beziehungen und lege sie auf einer anderen Notiz ab.", "start_dragging_relations": "Beginne hier mit dem Ziehen von Beziehungen und lege sie auf einer anderen Notiz ab.",
"note_not_found": "Hinweis {{noteId}} nicht gefunden!", "note_not_found": "Notiz {{noteId}} nicht gefunden!",
"cannot_match_transform": "Transformation kann nicht übereinstimmen: {{transform}}", "cannot_match_transform": "Transformation kann nicht übereinstimmen: {{transform}}",
"note_already_in_diagram": "Die Notiz \"{{title}}\" ist schon im Diagram.", "note_already_in_diagram": "Die Notiz \"{{title}}\" ist schon im Diagram.",
"enter_title_of_new_note": "Gebe den Titel der neuen Notiz ein", "enter_title_of_new_note": "Gebe den Titel der neuen Notiz ein",
@@ -959,15 +1000,13 @@
"click_on_canvas_to_place_new_note": "Klicke auf den Canvas, um eine neue Notiz zu platzieren" "click_on_canvas_to_place_new_note": "Klicke auf den Canvas, um eine neue Notiz zu platzieren"
}, },
"render": { "render": {
"note_detail_render_help_1": "Dieser Hilfehinweis wird angezeigt, da dieser Hinweis vom Typ „HTML rendern“ nicht über die erforderliche Beziehung verfügt, um ordnungsgemäß zu funktionieren.", "note_detail_render_help_1": "Diese Hilfesnotiz wird angezeigt, da diese Notiz vom Typ „HTML rendern“ nicht über die erforderliche Beziehung verfügt, um ordnungsgemäß zu funktionieren.",
"note_detail_render_help_2": "Render-HTML-Notiztyp wird benutzt für <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. Kurzgesagt, du hast ein HTML-Code-Notiz (optional mit JavaScript) und diese Notiz rendert es. Damit es funktioniert, musst du eine a <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">Beziehung</a> namens \"renderNote\" zeigend auf die HTML-Notiz zum rendern definieren." "note_detail_render_help_2": "Render-HTML-Notiztyp wird benutzt für <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. Kurzgesagt, du hast ein HTML-Code-Notiz (optional mit JavaScript) und diese Notiz rendert es. Damit es funktioniert, musst du eine a <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">Beziehung</a> namens \"renderNote\" zeigend auf die HTML-Notiz zum rendern definieren."
}, },
"web_view": { "web_view": {
"web_view": "Webansicht", "web_view": "Webansicht",
"embed_websites": "Hinweis vom Typ Web View ermöglicht das Einbetten von Websites in Trilium.", "embed_websites": "Notiz vom Typ Web View ermöglicht das Einbetten von Websites in Trilium.",
"create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"", "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\""
"disclaimer": "Haftungsausschluss zum experimentellen Status",
"experimental_note": "Web View ist ein experimenteller Notiztyp und könnte in Zukunft entfernt oder grundlegend geändert werden. Web View funktioniert auch nur im Desktop-Build."
}, },
"backend_log": { "backend_log": {
"refresh": "Aktualisieren" "refresh": "Aktualisieren"
@@ -1026,13 +1065,22 @@
"main_font": "Handschrift", "main_font": "Handschrift",
"font_family": "Schriftfamilie", "font_family": "Schriftfamilie",
"size": "Größe", "size": "Größe",
"note_tree_font": "Hinweisbaum-Schriftart", "note_tree_font": "Notizbaum-Schriftart",
"note_detail_font": "Hinweis-Detail-Schriftart", "note_detail_font": "Notiz-Detail-Schriftart",
"monospace_font": "Minivan (Code) Schriftart", "monospace_font": "Minivan (Code) Schriftart",
"note_tree_and_detail_font_sizing": "Beachte, dass die Größe der Baum- und Detailschriftarten relativ zur Hauptschriftgrößeneinstellung ist.", "note_tree_and_detail_font_sizing": "Beachte, dass die Größe der Baum- und Detailschriftarten relativ zur Hauptschriftgrößeneinstellung ist.",
"not_all_fonts_available": "Möglicherweise sind nicht alle aufgelisteten Schriftarten auf Ihrem System verfügbar.", "not_all_fonts_available": "Möglicherweise sind nicht alle aufgelisteten Schriftarten auf Ihrem System verfügbar.",
"apply_font_changes": "Um Schriftartänderungen zu übernehmen, klicke auf", "apply_font_changes": "Um Schriftartänderungen zu übernehmen, klicke auf",
"reload_frontend": "Frontend neu laden" "reload_frontend": "Frontend neu laden",
"generic-fonts": "Generische Schriftarten",
"sans-serif-system-fonts": "Sans-serif Systemschriftarten",
"serif-system-fonts": "Serif Systemschriftarten",
"monospace-system-fonts": "Monospace Systemschriftarten",
"handwriting-system-fonts": "Handschrift Systemschriftarten",
"serif": "Serif",
"sans-serif": "Sans Serif",
"monospace": "Monospace",
"system-default": "System Standard"
}, },
"max_content_width": { "max_content_width": {
"title": "Inhaltsbreite", "title": "Inhaltsbreite",
@@ -1056,8 +1104,17 @@
"title": "Thema", "title": "Thema",
"theme_label": "Thema", "theme_label": "Thema",
"override_theme_fonts_label": "Theme-Schriftarten überschreiben", "override_theme_fonts_label": "Theme-Schriftarten überschreiben",
"light_theme": "Licht", "auto_theme": "Auto",
"dark_theme": "Dunkel" "light_theme": "Hell",
"dark_theme": "Dunkel",
"triliumnext": "TriliumNext Beta (Systemfarbschema folgend)",
"triliumnext-light": "TriliumNext Beta (Hell)",
"triliumnext-dark": "TriliumNext Beta (Dunkel)",
"layout": "Layout",
"layout-vertical-title": "Vertikal",
"layout-horizontal-title": "Horizontal",
"layout-vertical-description": "Startleiste ist auf der linken Seite (standard)",
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert."
}, },
"zoom_factor": { "zoom_factor": {
"title": "Zoomfaktor (nur Desktop-Build)", "title": "Zoomfaktor (nur Desktop-Build)",
@@ -1090,7 +1147,7 @@
"attachment_erasure_timeout": { "attachment_erasure_timeout": {
"attachment_erasure_timeout": "Zeitüberschreitung beim Löschen von Anhängen", "attachment_erasure_timeout": "Zeitüberschreitung beim Löschen von Anhängen",
"attachment_auto_deletion_description": "Anhänge werden automatisch gelöscht (und gelöscht), wenn sie nach einer definierten Zeitspanne nicht mehr in ihrer Notiz referenziert werden.", "attachment_auto_deletion_description": "Anhänge werden automatisch gelöscht (und gelöscht), wenn sie nach einer definierten Zeitspanne nicht mehr in ihrer Notiz referenziert werden.",
"erase_attachments_after_x_seconds": "Anhänge nach X Sekunden löschen, nachdem sie nicht in der Notiz verwendet wurden", "erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):", "manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
"erase_unused_attachments_now": "Lösche jetzt nicht verwendete Anhangnotizen", "erase_unused_attachments_now": "Lösche jetzt nicht verwendete Anhangnotizen",
"unused_attachments_erased": "Nicht verwendete Anhänge wurden gelöscht." "unused_attachments_erased": "Nicht verwendete Anhänge wurden gelöscht."
@@ -1102,15 +1159,15 @@
"note_erasure_timeout": { "note_erasure_timeout": {
"note_erasure_timeout_title": "Beachte das Zeitlimit für die Löschung", "note_erasure_timeout_title": "Beachte das Zeitlimit für die Löschung",
"note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.", "note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.",
"erase_notes_after": "Notizen löschen nach", "erase_notes_after": "Notizen löschen nach:",
"manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):", "manual_erasing_description": "Du kannst das Löschen auch manuell auslösen (ohne Berücksichtigung des oben definierten Timeouts):",
"erase_deleted_notes_now": "Jetzt gelöschte Notizen löschen", "erase_deleted_notes_now": "Jetzt gelöschte Notizen löschen",
"deleted_notes_erased": "Gelöschte Notizen wurden gelöscht." "deleted_notes_erased": "Gelöschte Notizen wurden gelöscht."
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "Snapshot-Intervall für Notizrevisionen", "note_revisions_snapshot_interval_title": "Snapshot-Intervall für Notizrevisionen",
"note_revisions_snapshot_description": "Das Snapshot-Zeitintervall für Notizrevisionen ist die Zeit in Sekunden, nach der eine neue Notizrevision erstellt wird. Weitere Informationen findest du im <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">Wiki</a>.", "note_revisions_snapshot_description": "Das Snapshot-Zeitintervall für Notizrevisionen ist die Zeit, nach der eine neue Notizrevision erstellt wird. Weitere Informationen findest du im <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">Wiki</a>.",
"snapshot_time_interval_label": "Zeitintervall für Notiz-Revisions-Snapshot (in Sekunden)" "snapshot_time_interval_label": "Zeitintervall für Notiz-Revisions-Snapshot:"
}, },
"revisions_snapshot_limit": { "revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "Limit für Notizrevision-Snapshots", "note_revisions_snapshot_limit_title": "Limit für Notizrevision-Snapshots",
@@ -1183,6 +1240,8 @@
"backup_now": "Jetzt sichern", "backup_now": "Jetzt sichern",
"backup_database_now": "Jetzt Datenbank sichern", "backup_database_now": "Jetzt Datenbank sichern",
"existing_backups": "Vorhandene Backups", "existing_backups": "Vorhandene Backups",
"date-and-time": "Datum & Uhrzeit",
"path": "Pfad",
"database_backed_up_to": "Die Datenbank wurde gesichert unter {{backupFilePath}}", "database_backed_up_to": "Die Datenbank wurde gesichert unter {{backupFilePath}}",
"no_backup_yet": "noch kein Backup" "no_backup_yet": "noch kein Backup"
}, },
@@ -1190,7 +1249,7 @@
"title": "ETAPI", "title": "ETAPI",
"description": "ETAPI ist eine REST-API, die für den programmgesteuerten Zugriff auf die Trilium-Instanz ohne Benutzeroberfläche verwendet wird.", "description": "ETAPI ist eine REST-API, die für den programmgesteuerten Zugriff auf die Trilium-Instanz ohne Benutzeroberfläche verwendet wird.",
"see_more": "Weitere Details findest du unter", "see_more": "Weitere Details findest du unter",
"wiki": "Woche", "wiki": "Wiki",
"and": "und", "and": "und",
"openapi_spec": "ETAPI OpenAPI-Spezifikation", "openapi_spec": "ETAPI OpenAPI-Spezifikation",
"create_token": "Erstelle ein neues ETAPI-Token", "create_token": "Erstelle ein neues ETAPI-Token",
@@ -1227,7 +1286,7 @@
"protected_session_timeout_description": "Das Zeitlimit für geschützte Sitzungen ist ein Zeitraum, nach dem die geschützte Sitzung aus dem Speicher des Browsers gelöscht wird. Dies wird ab der letzten Interaktion mit geschützten Notizen gemessen. Sehen", "protected_session_timeout_description": "Das Zeitlimit für geschützte Sitzungen ist ein Zeitraum, nach dem die geschützte Sitzung aus dem Speicher des Browsers gelöscht wird. Dies wird ab der letzten Interaktion mit geschützten Notizen gemessen. Sehen",
"wiki": "Wiki", "wiki": "Wiki",
"for_more_info": "für weitere Informationen.", "for_more_info": "für weitere Informationen.",
"protected_session_timeout_label": "Zeitüberschreitung der geschützten Sitzung (in Sekunden)", "protected_session_timeout_label": "Zeitüberschreitung der geschützten Sitzung:",
"reset_confirmation": "Durch das Zurücksetzen des Passworts verlierst du für immer den Zugriff auf alle Ihre bestehenden geschützten Notizen. Möchtest du das Passwort wirklich zurücksetzen?", "reset_confirmation": "Durch das Zurücksetzen des Passworts verlierst du für immer den Zugriff auf alle Ihre bestehenden geschützten Notizen. Möchtest du das Passwort wirklich zurücksetzen?",
"reset_success_message": "Das Passwort wurde zurückgesetzt. Bitte lege ein neues Passwort fest", "reset_success_message": "Das Passwort wurde zurückgesetzt. Bitte lege ein neues Passwort fest",
"change_password_heading": "Kennwort ändern", "change_password_heading": "Kennwort ändern",
@@ -1356,7 +1415,9 @@
"launcher": "Launcher", "launcher": "Launcher",
"doc": "Dokument", "doc": "Dokument",
"widget": "Widget", "widget": "Widget",
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?" "confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
"geo-map": "Geo Map",
"beta-feature": "Beta"
}, },
"protect_note": { "protect_note": {
"toggle-on": "Notiz schützen", "toggle-on": "Notiz schützen",
@@ -1379,7 +1440,11 @@
"open-help-page": "Hilfeseite öffnen", "open-help-page": "Hilfeseite öffnen",
"find": { "find": {
"case_sensitive": "Groß-/Kleinschreibung beachten", "case_sensitive": "Groß-/Kleinschreibung beachten",
"match_words": "Wörter genau übereinstimmen" "match_words": "Wörter genau übereinstimmen",
"find_placeholder": "Finde in Text...",
"replace_placeholder": "Ersetze mit...",
"replace": "Ersetzen",
"replace_all": "Alle Ersetzen"
}, },
"highlights_list_2": { "highlights_list_2": {
"title": "Hervorhebungs-Liste", "title": "Hervorhebungs-Liste",
@@ -1404,7 +1469,11 @@
"automatically-collapse-notes-title": "Notizen werden nach einer Inaktivitätsperiode automatisch zusammengeklappt, um den Baum zu entlasten.", "automatically-collapse-notes-title": "Notizen werden nach einer Inaktivitätsperiode automatisch zusammengeklappt, um den Baum zu entlasten.",
"save-changes": "Änderungen speichern und anwenden", "save-changes": "Änderungen speichern und anwenden",
"auto-collapsing-notes-after-inactivity": "Automatisches Zusammenklappen von Notizen nach Inaktivität…", "auto-collapsing-notes-after-inactivity": "Automatisches Zusammenklappen von Notizen nach Inaktivität…",
"saved-search-note-refreshed": "Gespeicherte Such-Notiz wurde aktualisiert." "saved-search-note-refreshed": "Gespeicherte Such-Notiz wurde aktualisiert.",
"hoist-this-note-workspace": "Diese Notiz fokussieren (Arbeitsbereich)",
"refresh-saved-search-results": "Gespeicherte Suchergebnisse aktualisieren",
"create-child-note": "Unternotiz anlegen",
"unhoist": "Entfokussieren"
}, },
"title_bar_buttons": { "title_bar_buttons": {
"window-on-top": "Dieses Fenster immer oben halten" "window-on-top": "Dieses Fenster immer oben halten"
@@ -1435,7 +1504,9 @@
"close_other_tabs": "Andere Tabs schließen", "close_other_tabs": "Andere Tabs schließen",
"close_right_tabs": "Tabs rechts schließen", "close_right_tabs": "Tabs rechts schließen",
"close_all_tabs": "Alle Tabs schließen", "close_all_tabs": "Alle Tabs schließen",
"reopen_last_tab": "Zuletzt geschlossenen Tab erneut öffnen",
"move_tab_to_new_window": "Tab in neues Fenster verschieben", "move_tab_to_new_window": "Tab in neues Fenster verschieben",
"copy_tab_to_new_window": "Tab in neues Fenster kopieren",
"new_tab": "Neuer Tab" "new_tab": "Neuer Tab"
}, },
"toc": { "toc": {
@@ -1487,7 +1558,7 @@
"confirm_unhoisting": "Die angeforderte Notiz {{requestedNote}} befindet sich außerhalb des hoisted Bereichs der Notiz {{hoistedNote}}. Du musst sie unhoisten, um auf die Notiz zuzugreifen. Möchtest du mit dem Unhoisting fortfahren?" "confirm_unhoisting": "Die angeforderte Notiz {{requestedNote}} befindet sich außerhalb des hoisted Bereichs der Notiz {{hoistedNote}}. Du musst sie unhoisten, um auf die Notiz zuzugreifen. Möchtest du mit dem Unhoisting fortfahren?"
}, },
"launcher_context_menu": { "launcher_context_menu": {
"reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Kindern) gehen verloren und der Launcher wird an seinen ursprünglichen Standort zurückgesetzt.", "reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Unternotizen) gehen verloren und der Launcher wird an seinen ursprünglichen Standort zurückgesetzt.",
"add-note-launcher": "Launcher für Notiz hinzufügen", "add-note-launcher": "Launcher für Notiz hinzufügen",
"add-script-launcher": "Launcher für Skript hinzufügen", "add-script-launcher": "Launcher für Skript hinzufügen",
"add-custom-widget": "Benutzerdefiniertes Widget hinzufügen", "add-custom-widget": "Benutzerdefiniertes Widget hinzufügen",
@@ -1508,5 +1579,84 @@
}, },
"code_block": { "code_block": {
"word_wrapping": "Wortumbruch" "word_wrapping": "Wortumbruch"
},
"classic_editor_toolbar": {
"title": "Format"
},
"editor": {
"title": "Editor"
},
"editing": {
"editor_type": {
"label": "Format Toolbar",
"floating": {
"title": "Schwebend",
"description": "Werkzeuge erscheinen in Cursornähe"
},
"fixed": {
"title": "Fixiert",
"description": "Werkzeuge erscheinen im \"Format\" Tab"
},
"multiline-toolbar": "Toolbar wenn nötig in mehreren Zeilen darstellen."
}
},
"electron_context_menu": {
"add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen",
"cut": "Ausschneiden",
"copy": "Kopieren",
"copy-link": "Link opieren",
"paste": "Einfügen",
"paste-as-plain-text": "Als unformatierten Text einfügen",
"search_online": "Suche nach \"{{term}}\" mit {{searchEngine}} starten"
},
"image_context_menu": {
"copy_reference_to_clipboard": "Referenz in Zwischenablage kopieren",
"copy_image_to_clipboard": "Bild in die Zwischenablage kopieren"
},
"link_context_menu": {
"open_note_in_new_tab": "Notiz in neuen Tab öffnen",
"open_note_in_new_split": "Notiz in neuen geteilten Tab öffnen",
"open_note_in_new_window": "Notiz in neuen Fenster öffnen"
},
"electron_integration": {
"desktop-application": "Desktop Anwendung",
"native-title-bar": "Native Anwendungsleiste",
"native-title-bar-description": "In Windows und macOS, sorgt das Deaktivieren der nativen Anwendungsleiste für ein kompakteres Aussehen. Unter Linux, sorgt das Aktivieren der nativen Anwendungsleiste für eine bessere Integration mit anderen Teilen des Systems.",
"background-effects": "Hintergrundeffekte aktivieren (nur Windows 11)",
"background-effects-description": "Der Mica Effekt fügt einen unscharfen, stylischen Hintergrund in Anwendungsfenstern ein. Dieser erzeugt Tiefe und ein modernes Auftreten.",
"restart-app-button": "Anwendung neustarten um Änderungen anzuwenden",
"zoom-factor": "Zoomfaktor"
},
"note_autocomplete": {
"search-for": "Suche nach \"{{term}}\"",
"create-note": "Erstelle und verlinke Unternotiz \"{{term}}\"",
"insert-external-link": "Einfügen von Externen Link zu \"{{term}}\"",
"clear-text-field": "Textfeldinhalt löschen",
"show-recent-notes": "Aktuelle Notizen anzeigen",
"full-text-search": "Volltextsuche"
},
"note_tooltip": {
"note-has-been-deleted": "Notiz wurde gelöscht."
},
"geo-map": {
"create-child-note-title": "Neue Unternotiz anlegen und zur Karte hinzufügen",
"create-child-note-instruction": "Auf die Karte klicken, um eine neue Notiz an der Stelle zu erstellen oder Escape drücken um abzubrechen.",
"unable-to-load-map": "Karte konnte nicht geladen werden."
},
"geo-map-context": {
"open-location": "Ort öffnen",
"remove-from-map": "Von Karte entfernen"
},
"help-button": {
"title": "Relevante Hilfeseite öffnen"
},
"duration": {
"seconds": "Sekunden",
"minutes": "Minuten",
"hours": "Stunden",
"days": "Tage"
},
"time_selector": {
"invalid_input": "Die eingegebene Zeit ist keine valide Zahl."
} }
} }

View File

@@ -824,6 +824,7 @@
}, },
"promoted_attributes": { "promoted_attributes": {
"promoted_attributes": "Promoted Attributes", "promoted_attributes": "Promoted Attributes",
"unset-field-placeholder": "not set",
"url_placeholder": "http://website...", "url_placeholder": "http://website...",
"open_external_link": "Open external link", "open_external_link": "Open external link",
"unknown_label_type": "Unknown label type '{{type}}'", "unknown_label_type": "Unknown label type '{{type}}'",
@@ -966,7 +967,8 @@
"enter_workspace": "Enter workspace {{title}}" "enter_workspace": "Enter workspace {{title}}"
}, },
"file": { "file": {
"file_preview_not_available": "File preview is not available for this file format." "file_preview_not_available": "File preview is not available for this file format.",
"too_big": "The preview only shows the first {{maxNumChars}} characters of the file for performance reasons. Download the file and open it externally to be able to see the entire content."
}, },
"protected_session": { "protected_session": {
"enter_password_instruction": "Showing protected note requires entering your password:", "enter_password_instruction": "Showing protected note requires entering your password:",
@@ -1005,9 +1007,7 @@
"web_view": { "web_view": {
"web_view": "Web View", "web_view": "Web View",
"embed_websites": "Note of type Web View allows you to embed websites into Trilium.", "embed_websites": "Note of type Web View allows you to embed websites into Trilium.",
"create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"", "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\""
"disclaimer": "Disclaimer on the experimental status",
"experimental_note": "Web View is an experimental note type, and it might be removed or substantially changed in the future. Web View works also only in the desktop build."
}, },
"backend_log": { "backend_log": {
"refresh": "Refresh" "refresh": "Refresh"
@@ -1148,7 +1148,7 @@
"attachment_erasure_timeout": { "attachment_erasure_timeout": {
"attachment_erasure_timeout": "Attachment Erasure Timeout", "attachment_erasure_timeout": "Attachment Erasure Timeout",
"attachment_auto_deletion_description": "Attachments get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.", "attachment_auto_deletion_description": "Attachments get automatically deleted (and erased) if they are not referenced by their note anymore after a defined time out.",
"erase_attachments_after_x_seconds": "Erase attachments after X seconds of not being used in its note", "erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):", "manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):",
"erase_unused_attachments_now": "Erase unused attachment notes now", "erase_unused_attachments_now": "Erase unused attachment notes now",
"unused_attachments_erased": "Unused attachments have been erased." "unused_attachments_erased": "Unused attachments have been erased."
@@ -1160,15 +1160,15 @@
"note_erasure_timeout": { "note_erasure_timeout": {
"note_erasure_timeout_title": "Note Erasure Timeout", "note_erasure_timeout_title": "Note Erasure Timeout",
"note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.", "note_erasure_description": "Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them from Recent Notes dialog. After a period of time, deleted notes are \"erased\" which means their content is not recoverable anymore. This setting allows you to configure the length of the period between deleting and erasing the note.",
"erase_notes_after": "Erase notes after", "erase_notes_after": "Erase notes after:",
"manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):", "manual_erasing_description": "You can also trigger erasing manually (without considering the timeout defined above):",
"erase_deleted_notes_now": "Erase deleted notes now", "erase_deleted_notes_now": "Erase deleted notes now",
"deleted_notes_erased": "Deleted notes have been erased." "deleted_notes_erased": "Deleted notes have been erased."
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "Note Revision Snapshot Interval", "note_revisions_snapshot_interval_title": "Note Revision Snapshot Interval",
"note_revisions_snapshot_description": "The Note revision snapshot interval is the time in seconds after which a new note revision will be created for the note. See <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> for more info.", "note_revisions_snapshot_description": "The Note revision snapshot interval is the time after which a new note revision will be created for the note. See <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> for more info.",
"snapshot_time_interval_label": "Note revision snapshot time interval (in seconds):" "snapshot_time_interval_label": "Note revision snapshot time interval:"
}, },
"revisions_snapshot_limit": { "revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "Note Revision Snapshot Limit", "note_revisions_snapshot_limit_title": "Note Revision Snapshot Limit",
@@ -1287,7 +1287,7 @@
"protected_session_timeout_description": "Protected session timeout is a time period after which the protected session is wiped from the browser's memory. This is measured from the last interaction with protected notes. See", "protected_session_timeout_description": "Protected session timeout is a time period after which the protected session is wiped from the browser's memory. This is measured from the last interaction with protected notes. See",
"wiki": "wiki", "wiki": "wiki",
"for_more_info": "for more info.", "for_more_info": "for more info.",
"protected_session_timeout_label": "Protected session timeout (in seconds)", "protected_session_timeout_label": "Protected session timeout:",
"reset_confirmation": "By resetting the password you will forever lose access to all your existing protected notes. Do you really want to reset the password?", "reset_confirmation": "By resetting the password you will forever lose access to all your existing protected notes. Do you really want to reset the password?",
"reset_success_message": "Password has been reset. Please set new password", "reset_success_message": "Password has been reset. Please set new password",
"change_password_heading": "Change Password", "change_password_heading": "Change Password",
@@ -1656,5 +1656,20 @@
"minutes": "Minutes", "minutes": "Minutes",
"hours": "Hours", "hours": "Hours",
"days": "Days" "days": "Days"
},
"share": {
"title": "Share Settings",
"redirect_bare_domain": "Redirect bare domain to Share page",
"redirect_bare_domain_description": "Redirect anonymous users to the Share page instead of showing Login",
"show_login_link": "Show Login link in Share theme",
"show_login_link_description": "Add a login link to the Share page footer",
"check_share_root": "Check Share Root Status",
"share_root_found": "Share root note '{{noteTitle}}' is ready",
"share_root_not_found": "No note with #shareRoot label found",
"share_root_not_shared": "Note '{{noteTitle}}' has #shareRoot label but is not Shared"
},
"time_selector": {
"invalid_input": "The entered time value is not a valid number.",
"minimum_input": "The entered time value needs to be at least {{minimumSeconds}} seconds."
} }
} }

View File

@@ -1003,9 +1003,7 @@
"web_view": { "web_view": {
"web_view": "Vista web", "web_view": "Vista web",
"embed_websites": "La nota de tipo Web View le permite insertar sitios web en Trilium.", "embed_websites": "La nota de tipo Web View le permite insertar sitios web en Trilium.",
"create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\"", "create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\""
"disclaimer": "Descargo de responsabilidad sobre el estado experimental",
"experimental_note": "Web View es un tipo de nota experimental y es posible que se elimine o cambie sustancialmente en el futuro. Web View también funciona solo en la versión de escritorio."
}, },
"backend_log": { "backend_log": {
"refresh": "Refrescar" "refresh": "Refrescar"
@@ -1146,7 +1144,7 @@
"attachment_erasure_timeout": { "attachment_erasure_timeout": {
"attachment_erasure_timeout": "Tiempo de espera para borrar archivos adjuntos", "attachment_erasure_timeout": "Tiempo de espera para borrar archivos adjuntos",
"attachment_auto_deletion_description": "Los archivos adjuntos se eliminan (y borran) automáticamente si ya no se hace referencia a ellos en su nota después de un tiempo de espera definido.", "attachment_auto_deletion_description": "Los archivos adjuntos se eliminan (y borran) automáticamente si ya no se hace referencia a ellos en su nota después de un tiempo de espera definido.",
"erase_attachments_after_x_seconds": "Borrar archivos adjuntos después de X segundos de no usarse en su nota", "erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):", "manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):",
"erase_unused_attachments_now": "Borrar ahora los archivos adjuntos no utilizados en la nota", "erase_unused_attachments_now": "Borrar ahora los archivos adjuntos no utilizados en la nota",
"unused_attachments_erased": "Los archivos adjuntos no utilizados se han eliminado." "unused_attachments_erased": "Los archivos adjuntos no utilizados se han eliminado."
@@ -1158,15 +1156,15 @@
"note_erasure_timeout": { "note_erasure_timeout": {
"note_erasure_timeout_title": "Tiempo de espera de borrado de notas", "note_erasure_timeout_title": "Tiempo de espera de borrado de notas",
"note_erasure_description": "Las notas eliminadas (y los atributos, las revisiones ...) en principio solo están marcadas como eliminadas y es posible recuperarlas del diálogo de Notas recientes. Después de un período de tiempo, las notas eliminadas son \" borradas\", lo que significa que su contenido ya no es recuperable. Esta configuración le permite configurar la longitud del período entre eliminar y borrar la nota.", "note_erasure_description": "Las notas eliminadas (y los atributos, las revisiones ...) en principio solo están marcadas como eliminadas y es posible recuperarlas del diálogo de Notas recientes. Después de un período de tiempo, las notas eliminadas son \" borradas\", lo que significa que su contenido ya no es recuperable. Esta configuración le permite configurar la longitud del período entre eliminar y borrar la nota.",
"erase_notes_after": "Borrar notas después de", "erase_notes_after": "Borrar notas después de:",
"manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):", "manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):",
"erase_deleted_notes_now": "Borrar notas eliminadas ahora", "erase_deleted_notes_now": "Borrar notas eliminadas ahora",
"deleted_notes_erased": "Las notas eliminadas han sido borradas." "deleted_notes_erased": "Las notas eliminadas han sido borradas."
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "Intervalo de instantáneas de revisiones de notas", "note_revisions_snapshot_interval_title": "Intervalo de instantáneas de revisiones de notas",
"note_revisions_snapshot_description": "El intervalo de tiempo de la instantánea de revisión de nota es el tiempo en segundos después de lo cual se creará una nueva revisión para la nota. Ver <a href=\"https://triliumnext.github.io/docs/wiki/note-revisions.html\" class=\"external\"> wiki </a> para obtener más información.", "note_revisions_snapshot_description": "El intervalo de tiempo de la instantánea de revisión de nota es el tiempo después de lo cual se creará una nueva revisión para la nota. Ver <a href=\"https://triliumnext.github.io/docs/wiki/note-revisions.html\" class=\"external\"> wiki </a> para obtener más información.",
"snapshot_time_interval_label": "Intervalo de tiempo de la instantánea de revisión de notas (en segundos)" "snapshot_time_interval_label": "Intervalo de tiempo de la instantánea de revisión de notas:"
}, },
"revisions_snapshot_limit": { "revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "Límite de respaldos de revisiones de nota", "note_revisions_snapshot_limit_title": "Límite de respaldos de revisiones de nota",
@@ -1285,7 +1283,7 @@
"protected_session_timeout_description": "El tiempo de espera de la sesión protegida es el período de tiempo después del cual la sesión protegida se borra de la memoria del navegador. Esto se mide desde la última interacción con notas protegidas. Ver", "protected_session_timeout_description": "El tiempo de espera de la sesión protegida es el período de tiempo después del cual la sesión protegida se borra de la memoria del navegador. Esto se mide desde la última interacción con notas protegidas. Ver",
"wiki": "wiki", "wiki": "wiki",
"for_more_info": "para más información.", "for_more_info": "para más información.",
"protected_session_timeout_label": "Tiempo de espera de sesión protegida (en segundos)", "protected_session_timeout_label": "Tiempo de espera de sesión protegida:",
"reset_confirmation": "Al restablecer la contraseña, perderá para siempre el acceso a todas sus notas protegidas existentes. ¿Realmente quieres restablecer la contraseña?", "reset_confirmation": "Al restablecer la contraseña, perderá para siempre el acceso a todas sus notas protegidas existentes. ¿Realmente quieres restablecer la contraseña?",
"reset_success_message": "La contraseña ha sido restablecida. Por favor establezca una nueva contraseña", "reset_success_message": "La contraseña ha sido restablecida. Por favor establezca una nueva contraseña",
"change_password_heading": "Cambiar contraseña", "change_password_heading": "Cambiar contraseña",

View File

@@ -966,9 +966,7 @@
"web_view": { "web_view": {
"web_view": "Affichage Web", "web_view": "Affichage Web",
"embed_websites": "Les notes de type Affichage Web vous permet d'intégrer des sites Web dans Trilium.", "embed_websites": "Les notes de type Affichage Web vous permet d'intégrer des sites Web dans Trilium.",
"create_label": "Pour commencer, veuillez créer un label avec l'adresse URL que vous souhaitez intégrer, par ex. #webViewSrc=\"https://www.google.com\"", "create_label": "Pour commencer, veuillez créer un label avec l'adresse URL que vous souhaitez intégrer, par ex. #webViewSrc=\"https://www.google.com\""
"disclaimer": "Avertissement sur le statut expérimental",
"experimental_note": "Affichage Web est un type de note expérimental et il pourrait être supprimé ou considérablement modifié à l'avenir. Affichage Web ne fonctionne que dans la version de bureau."
}, },
"backend_log": { "backend_log": {
"refresh": "Rafraîchir" "refresh": "Rafraîchir"
@@ -1091,7 +1089,7 @@
"attachment_erasure_timeout": { "attachment_erasure_timeout": {
"attachment_erasure_timeout": "Délai d'effacement des pièces jointes", "attachment_erasure_timeout": "Délai d'effacement des pièces jointes",
"attachment_auto_deletion_description": "Les pièces jointes sont automatiquement supprimées (et effacées) si elles ne sont plus référencées par leur note après un certain délai.", "attachment_auto_deletion_description": "Les pièces jointes sont automatiquement supprimées (et effacées) si elles ne sont plus référencées par leur note après un certain délai.",
"erase_attachments_after_x_seconds": "Effacer les pièces jointes après X secondes sans utilisation dans sa note", "erase_attachments_after": "Erase unused attachments after:",
"manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte du délai défini ci-dessus) :", "manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte du délai défini ci-dessus) :",
"erase_unused_attachments_now": "Effacez maintenant les pièces jointes inutilisées", "erase_unused_attachments_now": "Effacez maintenant les pièces jointes inutilisées",
"unused_attachments_erased": "Les pièces jointes inutilisées ont été effacées." "unused_attachments_erased": "Les pièces jointes inutilisées ont été effacées."
@@ -1103,15 +1101,15 @@
"note_erasure_timeout": { "note_erasure_timeout": {
"note_erasure_timeout_title": "Délai d'effacement des notes", "note_erasure_timeout_title": "Délai d'effacement des notes",
"note_erasure_description": "Les notes supprimées (et les attributs, versions...) sont seulement marquées comme supprimées et il est possible de les récupérer à partir de la boîte de dialogue Notes récentes. Après un certain temps, les notes supprimées sont « effacées », ce qui signifie que leur contenu n'est plus récupérable. Ce paramètre vous permet de configurer la durée entre la suppression et l'effacement de la note.", "note_erasure_description": "Les notes supprimées (et les attributs, versions...) sont seulement marquées comme supprimées et il est possible de les récupérer à partir de la boîte de dialogue Notes récentes. Après un certain temps, les notes supprimées sont « effacées », ce qui signifie que leur contenu n'est plus récupérable. Ce paramètre vous permet de configurer la durée entre la suppression et l'effacement de la note.",
"erase_notes_after": "Effacer les notes après", "erase_notes_after": "Effacer les notes après:",
"manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte de la durée définie ci-dessus) :", "manual_erasing_description": "Vous pouvez également déclencher l'effacement manuellement (sans tenir compte de la durée définie ci-dessus) :",
"erase_deleted_notes_now": "Effacer les notes supprimées maintenant", "erase_deleted_notes_now": "Effacer les notes supprimées maintenant",
"deleted_notes_erased": "Les notes supprimées ont été effacées." "deleted_notes_erased": "Les notes supprimées ont été effacées."
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "Intervalle d'enregistrement automatique des versions des notes", "note_revisions_snapshot_interval_title": "Intervalle d'enregistrement automatique des versions des notes",
"note_revisions_snapshot_description": "L'intervalle d'enregistrement automatique des versions de note est le temps en secondes après lequel une nouvelle version de note est créée pour une note. Consultez le <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> pour plus d'informations.", "note_revisions_snapshot_description": "L'intervalle d'enregistrement automatique des versions de note est le temps après lequel une nouvelle version de note est créée pour une note. Consultez le <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> pour plus d'informations.",
"snapshot_time_interval_label": "Intervalle de temps entre deux enregistrements de version de note (en secondes) :" "snapshot_time_interval_label": "Intervalle de temps entre deux enregistrements de version de note :"
}, },
"revisions_snapshot_limit": { "revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "Limite des enregistrements de version de note", "note_revisions_snapshot_limit_title": "Limite des enregistrements de version de note",
@@ -1228,7 +1226,7 @@
"protected_session_timeout_description": "Le délai d'expiration de la session protégée est une période de temps après laquelle la session protégée est effacée de la mémoire du navigateur. Il est mesuré à partir de la dernière interaction avec des notes protégées. Voir", "protected_session_timeout_description": "Le délai d'expiration de la session protégée est une période de temps après laquelle la session protégée est effacée de la mémoire du navigateur. Il est mesuré à partir de la dernière interaction avec des notes protégées. Voir",
"wiki": "wiki", "wiki": "wiki",
"for_more_info": "pour plus d'informations.", "for_more_info": "pour plus d'informations.",
"protected_session_timeout_label": "Délai d'expiration de la session protégée (en secondes)", "protected_session_timeout_label": "Délai d'expiration de la session protégée :",
"reset_confirmation": "En réinitialisant le mot de passe, vous perdrez à jamais l'accès à toutes vos notes protégées existantes. Voulez-vous vraiment réinitialiser le mot de passe ?", "reset_confirmation": "En réinitialisant le mot de passe, vous perdrez à jamais l'accès à toutes vos notes protégées existantes. Voulez-vous vraiment réinitialiser le mot de passe ?",
"reset_success_message": "Le mot de passe a été réinitialisé. Veuillez définir un nouveau mot de passe", "reset_success_message": "Le mot de passe a été réinitialisé. Veuillez définir un nouveau mot de passe",
"change_password_heading": "Changer le mot de passe", "change_password_heading": "Changer le mot de passe",

View File

@@ -78,7 +78,7 @@
"attachment_erasure_timeout": { "attachment_erasure_timeout": {
"attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.", "attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.",
"attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor", "attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor",
"erase_attachments_after_x_seconds": "Șterge atașamentele după X secunde după ce acestea n-au mai fost folosite într-o notiță", "erase_attachments_after": "Erase unused attachments after:",
"erase_unused_attachments_now": "Elimină atașamentele șterse acum", "erase_unused_attachments_now": "Elimină atașamentele șterse acum",
"manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe", "manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe",
"unused_attachments_erased": "Atașamentele nefolosite au fost șterse." "unused_attachments_erased": "Atașamentele nefolosite au fost șterse."
@@ -836,7 +836,7 @@
"note_erasure_timeout": { "note_erasure_timeout": {
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.", "deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
"erase_deleted_notes_now": "Elimină notițele șterse acum", "erase_deleted_notes_now": "Elimină notițele șterse acum",
"erase_notes_after": "Elimină notițele șterse după", "erase_notes_after": "Elimină notițele șterse după:",
"manual_erasing_description": "Se poate rula o eliminare manuală (fără a lua în considerare timpul definit mai sus):", "manual_erasing_description": "Se poate rula o eliminare manuală (fără a lua în considerare timpul definit mai sus):",
"note_erasure_description": "Notițele șterse (precum și atributele, reviziile) sunt prima oară doar marcate drept șterse și este posibil să fie recuperate din ecranul Notițe recente. După o perioadă de timp, notițele șterse vor fi „eliminate”, caz în care conținutul lor nu se poate recupera. Această setare permite configurarea duratei de timp dintre ștergerea și eliminarea notițelor.", "note_erasure_description": "Notițele șterse (precum și atributele, reviziile) sunt prima oară doar marcate drept șterse și este posibil să fie recuperate din ecranul Notițe recente. După o perioadă de timp, notițele șterse vor fi „eliminate”, caz în care conținutul lor nu se poate recupera. Această setare permite configurarea duratei de timp dintre ștergerea și eliminarea notițelor.",
"note_erasure_timeout_title": "Timpul de eliminare automată a notițelor șterse" "note_erasure_timeout_title": "Timpul de eliminare automată a notițelor șterse"
@@ -923,7 +923,7 @@
"password_mismatch": "Noile parole nu coincid.", "password_mismatch": "Noile parole nu coincid.",
"protected_session_timeout": "Timpul de expirare a sesiunii protejate", "protected_session_timeout": "Timpul de expirare a sesiunii protejate",
"protected_session_timeout_description": "Timpul de expirare a sesiunii protejate este o perioadă de timp după care sesiunea protejată este ștearsă din memoria navigatorului. Aceasta este măsurată de la timpul ultimei interacțiuni cu notițele protejate. Vezi", "protected_session_timeout_description": "Timpul de expirare a sesiunii protejate este o perioadă de timp după care sesiunea protejată este ștearsă din memoria navigatorului. Aceasta este măsurată de la timpul ultimei interacțiuni cu notițele protejate. Vezi",
"protected_session_timeout_label": "Timpul de expirare a sesiunii protejate (în secunde)", "protected_session_timeout_label": "Timpul de expirare a sesiunii protejate:",
"reset_confirmation": "Prin resetarea parolei se va pierde pentru totdeauna accesul la notițele protejate existente. Sigur doriți resetarea parolei?", "reset_confirmation": "Prin resetarea parolei se va pierde pentru totdeauna accesul la notițele protejate existente. Sigur doriți resetarea parolei?",
"reset_link": "click aici pentru a o reseta.", "reset_link": "click aici pentru a o reseta.",
"reset_success_message": "Parola a fost resetată. Setați o nouă parolă", "reset_success_message": "Parola a fost resetată. Setați o nouă parolă",
@@ -944,7 +944,8 @@
"remove_this_attribute": "Elimină acest atribut", "remove_this_attribute": "Elimină acest atribut",
"unknown_attribute_type": "Tip de atribut necunoscut „{{type}}”", "unknown_attribute_type": "Tip de atribut necunoscut „{{type}}”",
"unknown_label_type": "Tip de etichetă necunoscut „{{type}}”", "unknown_label_type": "Tip de etichetă necunoscut „{{type}}”",
"url_placeholder": "http://siteweb..." "url_placeholder": "http://siteweb...",
"unset-field-placeholder": "nesetat"
}, },
"prompt": { "prompt": {
"defaultTitle": "Aviz", "defaultTitle": "Aviz",
@@ -1068,9 +1069,9 @@
"note_revisions": "Revizii ale notiței" "note_revisions": "Revizii ale notiței"
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_description": "Intervalul de salvare a reviziilor este timpul în secunde după care se crează o nouă revizie a unei notițe. Vedeți <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki-ul</a> pentru mai multe informații.", "note_revisions_snapshot_description": "Intervalul de salvare a reviziilor este timpul după care se crează o nouă revizie a unei notițe. Vedeți <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki-ul</a> pentru mai multe informații.",
"note_revisions_snapshot_interval_title": "Intervalul de salvare a reviziilor", "note_revisions_snapshot_interval_title": "Intervalul de salvare a reviziilor",
"snapshot_time_interval_label": "Intervalul de salvare a reviziilor (în secunde)" "snapshot_time_interval_label": "Intervalul de salvare a reviziilor:"
}, },
"ribbon": { "ribbon": {
"edited_notes_message": "Tab-ul panglicii „Notițe editate” se va deschide automat pentru notițele zilnice", "edited_notes_message": "Tab-ul panglicii „Notițe editate” se va deschide automat pentru notițele zilnice",
@@ -1307,9 +1308,7 @@
}, },
"web_view": { "web_view": {
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"", "create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
"disclaimer": "Avertisment despre statutul experimental",
"embed_websites": "Notițele de tip „Vizualizare web” permit încorporarea site-urilor web în Trilium.", "embed_websites": "Notițele de tip „Vizualizare web” permit încorporarea site-urilor web în Trilium.",
"experimental_note": "„Vizualizare web” este un tip experimental de notiță, și poate fi înlăturat sau schimbat substanțial în viitor. De asemenea, Web View funcționeză doar în aplicația desktop.",
"web_view": "Vizualizare web" "web_view": "Vizualizare web"
}, },
"wrap_lines": { "wrap_lines": {

View File

@@ -970,9 +970,7 @@
"web_view": { "web_view": {
"web_view": "網頁視圖", "web_view": "網頁視圖",
"embed_websites": "網頁視圖類型的筆記允許您將網站嵌入到 Trilium 中。", "embed_websites": "網頁視圖類型的筆記允許您將網站嵌入到 Trilium 中。",
"create_label": "首先,請新增一個帶有您要嵌入的 URL 地址的標籤,例如 #webViewSrc=\"https://www.bing.com\"", "create_label": "首先,請新增一個帶有您要嵌入的 URL 地址的標籤,例如 #webViewSrc=\"https://www.bing.com\""
"disclaimer": "實驗性功能免責聲明",
"experimental_note": "網頁視圖是一種實驗性的筆記類型,將來可能會被移除或大幅更改。網頁視圖只在桌面端有效。"
}, },
"backend_log": { "backend_log": {
"refresh": "刷新" "refresh": "刷新"
@@ -1100,7 +1098,7 @@
"attachment_erasure_timeout": { "attachment_erasure_timeout": {
"attachment_erasure_timeout": "附件清理超時", "attachment_erasure_timeout": "附件清理超時",
"attachment_auto_deletion_description": "如果附件在一段時間後不再被筆記引用,它們將自動被刪除(並被清理)。", "attachment_auto_deletion_description": "如果附件在一段時間後不再被筆記引用,它們將自動被刪除(並被清理)。",
"erase_attachments_after_x_seconds": "在附件在筆記中未被使用 X 秒後清理", "erase_attachments_after": "",
"manual_erasing_description": "您還可以手動觸發清理(而不考慮上述定義的超時時間):", "manual_erasing_description": "您還可以手動觸發清理(而不考慮上述定義的超時時間):",
"erase_unused_attachments_now": "立即清理未使用的附件筆記", "erase_unused_attachments_now": "立即清理未使用的附件筆記",
"unused_attachments_erased": "未使用的附件已被刪除。" "unused_attachments_erased": "未使用的附件已被刪除。"
@@ -1112,15 +1110,15 @@
"note_erasure_timeout": { "note_erasure_timeout": {
"note_erasure_timeout_title": "筆記清理超時", "note_erasure_timeout_title": "筆記清理超時",
"note_erasure_description": "被刪除的筆記(以及屬性、歷史版本等)最初僅被標記為「刪除」,可以從「最近修改」對話框中恢復它們。經過一段時間後,已刪除的筆記會被「清理」,這意味著它們的內容將無法恢復。此設定允許您設定從刪除到清除筆記之間的時間長度。", "note_erasure_description": "被刪除的筆記(以及屬性、歷史版本等)最初僅被標記為「刪除」,可以從「最近修改」對話框中恢復它們。經過一段時間後,已刪除的筆記會被「清理」,這意味著它們的內容將無法恢復。此設定允許您設定從刪除到清除筆記之間的時間長度。",
"erase_notes_after": "Erase notes after", "erase_notes_after": "",
"manual_erasing_description": "您還可以手動觸發清理(不考慮上述定義的超時):", "manual_erasing_description": "您還可以手動觸發清理(不考慮上述定義的超時):",
"erase_deleted_notes_now": "立即清理已刪除的筆記", "erase_deleted_notes_now": "立即清理已刪除的筆記",
"deleted_notes_erased": "已刪除的筆記已被清理。" "deleted_notes_erased": "已刪除的筆記已被清理。"
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "筆記修改快照間隔", "note_revisions_snapshot_interval_title": "",
"note_revisions_snapshot_description": "筆記修改快照時間間隔是指經過多少秒後會為筆記新增新的修改歷史。更多資訊請參見<a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a>。", "note_revisions_snapshot_description": "",
"snapshot_time_interval_label": "筆記修改快照時間間隔(單位:秒)" "snapshot_time_interval_label": ""
}, },
"revisions_snapshot_limit": { "revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "筆記歷史快照限制", "note_revisions_snapshot_limit_title": "筆記歷史快照限制",
@@ -1239,7 +1237,7 @@
"protected_session_timeout_description": "保護會話超時是一個時間段,超時後保護會話會從瀏覽器內存中清除。這是從最後一次與保護筆記的交互開始計時的。更多資訊請見", "protected_session_timeout_description": "保護會話超時是一個時間段,超時後保護會話會從瀏覽器內存中清除。這是從最後一次與保護筆記的交互開始計時的。更多資訊請見",
"wiki": "維基", "wiki": "維基",
"for_more_info": "更多資訊。", "for_more_info": "更多資訊。",
"protected_session_timeout_label": "保護會話超時(秒)", "protected_session_timeout_label": "",
"reset_confirmation": "重置密碼將永久喪失對所有現受保護筆記的訪問。您真的要重置密碼嗎?", "reset_confirmation": "重置密碼將永久喪失對所有現受保護筆記的訪問。您真的要重置密碼嗎?",
"reset_success_message": "密碼已重置。請設定新密碼", "reset_success_message": "密碼已重置。請設定新密碼",
"change_password_heading": "更改密碼", "change_password_heading": "更改密碼",

View File

@@ -5,7 +5,7 @@ import log from "../../services/log.js";
import searchService from "../../services/search/services/search.js"; import searchService from "../../services/search/services/search.js";
import ValidationError from "../../errors/validation_error.js"; import ValidationError from "../../errors/validation_error.js";
import type { Request } from "express"; import type { Request } from "express";
import { changeLanguage } from "../../services/i18n.js"; import { changeLanguage, getLocales } from "../../services/i18n.js";
import { listSyntaxHighlightingThemes } from "../../services/code_block_theme.js"; import { listSyntaxHighlightingThemes } from "../../services/code_block_theme.js";
import type { OptionNames } from "../../services/options_interface.js"; import type { OptionNames } from "../../services/options_interface.js";
@@ -14,7 +14,9 @@ const ALLOWED_OPTIONS = new Set([
"eraseEntitiesAfterTimeInSeconds", "eraseEntitiesAfterTimeInSeconds",
"eraseEntitiesAfterTimeScale", "eraseEntitiesAfterTimeScale",
"protectedSessionTimeout", "protectedSessionTimeout",
"protectedSessionTimeoutTimeScale",
"revisionSnapshotTimeInterval", "revisionSnapshotTimeInterval",
"revisionSnapshotTimeIntervalTimeScale",
"revisionSnapshotNumberLimit", "revisionSnapshotNumberLimit",
"zoomFactor", "zoomFactor",
"theme", "theme",
@@ -61,6 +63,7 @@ const ALLOWED_OPTIONS = new Set([
"checkForUpdates", "checkForUpdates",
"disableTray", "disableTray",
"eraseUnusedAttachmentsAfterSeconds", "eraseUnusedAttachmentsAfterSeconds",
"eraseUnusedAttachmentsAfterTimeScale",
"disableTray", "disableTray",
"customSearchEngineName", "customSearchEngineName",
"customSearchEngineUrl", "customSearchEngineUrl",
@@ -72,7 +75,9 @@ const ALLOWED_OPTIONS = new Set([
"textNoteEditorMultilineToolbar", "textNoteEditorMultilineToolbar",
"layoutOrientation", "layoutOrientation",
"backgroundEffects", "backgroundEffects",
"allowedHtmlTags" // Allow configuring HTML import tags "allowedHtmlTags",
"redirectBareDomain",
"showLoginInShareTheme"
]); ]);
function getOptions() { function getOptions() {
@@ -153,37 +158,7 @@ function getSyntaxHighlightingThemes() {
} }
function getSupportedLocales() { function getSupportedLocales() {
// TODO: Currently hardcoded, needs to read the list of available languages. return getLocales();
return [
{
id: "en",
name: "English"
},
{
id: "de",
name: "Deutsch"
},
{
id: "es",
name: "Español"
},
{
id: "fr",
name: "Français"
},
{
id: "cn",
name: "简体中文"
},
{
id: "tw",
name: "繁體中文"
},
{
id: "ro",
name: "Română"
}
];
} }
function isAllowed(name: string) { function isAllowed(name: string) {

View File

@@ -29,8 +29,8 @@ function getYearNote(req: Request) {
function getDayNotesForMonth(req: Request) { function getDayNotesForMonth(req: Request) {
const month = req.params.month; const month = req.params.month;
const calendarRoot = req.query.calendarRoot;
return sql.getMap(` const query = `\
SELECT SELECT
attr.value AS date, attr.value AS date,
notes.noteId notes.noteId
@@ -40,7 +40,22 @@ function getDayNotesForMonth(req: Request) {
AND attr.isDeleted = 0 AND attr.isDeleted = 0
AND attr.type = 'label' AND attr.type = 'label'
AND attr.name = 'dateNote' AND attr.name = 'dateNote'
AND attr.value LIKE '${month}%'`); AND attr.value LIKE '${month}%'`;
if (calendarRoot) {
const rows = sql.getRows<{ date: string, noteId: string }>(query);
const result: Record<string, string> = {};
for (const {date, noteId} of rows) {
const note = becca.getNote(noteId);
if (note?.hasAncestor(String(calendarRoot))) {
result[date] = noteId;
}
}
return result;
} else {
return sql.getMap(query);
}
} }
function saveSqlConsole(req: Request) { function saveSqlConsole(req: Request) {

View File

@@ -7,6 +7,8 @@ import { isElectron } from "./utils.js";
import passwordEncryptionService from "./encryption/password_encryption.js"; import passwordEncryptionService from "./encryption/password_encryption.js";
import config from "./config.js"; import config from "./config.js";
import passwordService from "./encryption/password.js"; import passwordService from "./encryption/password.js";
import options from "./options.js";
import attributes from "./attributes.js";
import type { NextFunction, Request, Response } from "express"; import type { NextFunction, Request, Response } from "express";
const noAuthentication = config.General && config.General.noAuthentication === true; const noAuthentication = config.General && config.General.noAuthentication === true;
@@ -15,7 +17,16 @@ function checkAuth(req: Request, res: Response, next: NextFunction) {
if (!sqlInit.isDbInitialized()) { if (!sqlInit.isDbInitialized()) {
res.redirect("setup"); res.redirect("setup");
} else if (!req.session.loggedIn && !isElectron && !noAuthentication) { } else if (!req.session.loggedIn && !isElectron && !noAuthentication) {
res.redirect("login"); const redirectToShare = options.getOptionBool("redirectBareDomain");
if (redirectToShare) {
// Check if any note has the #shareRoot label
const shareRootNotes = attributes.getNotesWithLabel("shareRoot");
if (shareRootNotes.length === 0) {
res.status(404).json({ message: "Share root not found. Please set up a note with #shareRoot label first." });
return;
}
}
res.redirect(redirectToShare ? "share" : "login");
} else { } else {
next(); next();
} }

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