Compare commits

...

94 Commits

Author SHA1 Message Date
zadam
15ed381f85 release 0.55.1 2022-09-21 22:43:34 +02:00
zadam
180051d252 added "scrolling-container" class, fixes #3147 2022-09-19 23:15:54 +02:00
zadam
a19c58703f Merge remote-tracking branch 'origin/master' 2022-09-19 23:12:20 +02:00
zadam
fc43d9222a fix erasing notes - becca should be reloaded afterwards, closes #3146 2022-09-19 23:12:12 +02:00
zadam
af6bf08243 Merge pull request #3143 from charlesdagenais/master
add file properties widget in mobile layout
2022-09-19 22:44:23 +02:00
zadam
fb6a0bc2a6 Merge pull request #3142 from spasche/zip-import-skip-bad-url
let import continue when malformed URLs are encountered
2022-09-19 22:42:31 +02:00
Charles Dagenais
1f61c1b3b6 add file properties info in mobile view 2022-09-18 12:40:00 -04:00
Sylvain Pasche
fc69f3b8f3 let import continue when malformed URLs are encountered
In case of malformed URLs in imported HTML, keep original URL instead
of having the import get stuck.
2022-09-18 18:18:42 +02:00
zadam
d4658b9c2a bring back the possibility to close the floating buttons again, closes #3116 2022-09-18 14:57:44 +02:00
zadam
84f72edf1d use button CSS variables in more places 2022-09-18 13:52:19 +02:00
zadam
552d872047 more standard window-all-closed handling 2022-09-18 10:05:49 +02:00
zadam
47235965d5 api log close button 2022-09-18 08:48:01 +02:00
zadam
24e4455e91 Merge remote-tracking branch 'origin/master' 2022-09-17 23:06:43 +02:00
zadam
ea35b0c800 Merge branch 'api-log-capture' 2022-09-17 23:06:42 +02:00
zadam
1a30087426 api log implementation 2022-09-17 23:06:17 +02:00
zadam
5e9d004ca2 Merge pull request #3139 from jph/add-methods-to-etapi-spec
Add another ETAPI method to the api spec file.
2022-09-17 14:13:31 +02:00
jph
513d1c020c Add another ETAPI method to the api spec file. 2022-09-17 10:47:44 +10:00
zadam
05231bd1c2 stop #workspaceTemplate from being inherited (should have the same special behavior as #template) 2022-09-16 23:03:02 +02:00
zadam
b816773d02 add #workspaceTemplate which works as workspace-scoped template, closes #3137 2022-09-16 22:44:52 +02:00
zadam
3c49ea6cb1 make context sub-menu scrollable, fix #3136 2022-09-16 22:12:09 +02:00
zadam
539eac4be7 Merge pull request #3134 from jph/add-note-content-to-api-spec
Add get note content to ETAPI spec
2022-09-16 21:08:53 +02:00
James Hynes
8a6ead6d86 fix a couple of api shortcomings
* add note content to openapi spec file
* add test for get note content api endpoint
2022-09-16 13:18:01 +10:00
zadam
01a7ed8311 fix zooming in server install 2022-09-15 23:10:54 +02:00
zadam
cf6330dee6 allow deleting notes from note actions button, closes #3131 2022-09-15 23:09:24 +02:00
zadam
6c39b6f548 fix "excludeFromNoteMap" on journal instead of "excludeFromTreeMap" 2022-09-15 00:04:26 +02:00
zadam
e7ef1b86cc zoom buttons in main menu, closes #2894 2022-09-14 23:28:29 +02:00
zadam
3663d56917 api log capture WIP 2022-09-14 16:50:52 +02:00
zadam
135064a18f drag & drop from tree will insert links to notes, closes #227 2022-09-13 23:36:59 +02:00
zadam
7233f58767 flatpak release script fixes 2022-09-12 23:14:00 +02:00
zadam
78238381a7 release 0.55.0-beta 2022-09-12 22:37:41 +02:00
zadam
ca03c41205 flatpak release script should support beta channel 2022-09-12 22:35:22 +02:00
zadam
9b8474a728 Merge branch 'stable'
# Conflicts:
#	package-lock.json
2022-09-12 21:44:49 +02:00
zadam
ecfce409d8 fix "ghost split", closes #3115 2022-09-07 23:39:35 +02:00
zadam
b091e2222e separated editable code buttons into separate widget which also fixes scrolling issue 2022-09-04 23:09:42 +02:00
zadam
e8b8198eb8 added Montserrat-Regular.ttf to fix boldness on mac, closes #3094 2022-09-04 16:50:45 +02:00
zadam
261f1f0bf2 fix electron 2022-09-04 14:01:34 +02:00
zadam
8588ed5eec remove port scanning for an available port #3036 2022-09-04 14:00:39 +02:00
zadam
f161488c13 focus existing window on port conflict, closes #3036 2022-09-04 14:00:34 +02:00
zadam
563808144e small fixes to tray and closing windows 2022-09-04 13:59:58 +02:00
zadam
3c1f826ead add support for the persistent #sortDirection and #sortFoldersFirst (one time UI action exists), closes #3046 2022-09-04 13:59:34 +02:00
zadam
b2a63afc28 limit max imported file size to 250 MiB, #3108 2022-09-03 15:11:03 +02:00
zadam
86d3a695ac fix handling of parenthesis without explicit AND operator, closes #3096 2022-08-28 14:40:24 +02:00
zadam
7f566178d3 Merge pull request #3095 from agentydragon/trilium-docs-noformat
Add some docs to note.js
2022-08-28 13:51:33 +02:00
Rai
4af55d055e revert 2022-08-27 13:46:16 -07:00
Rai
be000be960 Merge branch 'master' into trilium-docs-noformat 2022-08-27 13:45:22 -07:00
Rai
afb6180cce update docs 2022-08-27 13:42:44 -07:00
Rai
d63156a0d6 Add some docs to note.js (for backend scripting docs) 2022-08-27 13:40:01 -07:00
zadam
2556cd5aaa fix share.js in case there's no menu 2022-08-27 21:34:21 +02:00
zadam
7109a12afd Merge branch 'stable'
# Conflicts:
#	bin/release-flatpack.sh
2022-08-27 13:19:29 +02:00
zadam
700e9fd912 fix release-flatpack.sh 2022-08-26 23:52:09 +02:00
zadam
6547b66228 release 0.54.3 2022-08-26 22:16:49 +02:00
zadam
e9090b9154 added a release flatpack script
(cherry picked from commit e70a7bad1a)
2022-08-26 22:09:48 +02:00
zadam
5b73fe949e defensive programming, #3089 2022-08-26 22:08:05 +02:00
zadam
35fab7a23c fixes overwriting attributes after clicking on a relation in the attribute editor, closes #3090 2022-08-26 22:02:40 +02:00
zadam
bf6d79f4ad Merge pull request #3088 from DynamoFox/fix-update-button-if-update-checking-is-disabled
Fix the update to latest version button in the dropdown menu if update checking is disabled
2022-08-24 23:24:19 +02:00
zadam
24286c2a6c remove all alert() usages, fixes #3086 2022-08-24 23:20:05 +02:00
DynamoFox
09641d9465 Fix the update to latest version button in the dropdown menu, now it doesn't appear at all if update checking was disabled 2022-08-24 22:07:04 +02:00
zadam
e70a7bad1a added a release flatpack script 2022-08-23 23:48:13 +02:00
zadam
d386966342 Merge remote-tracking branch 'origin/master' 2022-08-23 22:32:43 +02:00
zadam
09cfaeb9c4 focus autocomplete on new tab even if there are workspaces, fixes #3083 2022-08-23 22:32:27 +02:00
zadam
934b1ad8c6 Merge pull request #3080 from DynamoFox/rate-limit-etapi-login-route
Rate limit the /auth/login route of ETAPI
2022-08-22 19:37:59 +02:00
zadam
80dcb45415 disable compression for electron / desktop builds 2022-08-22 19:29:58 +02:00
zadam
ad8b7f67b1 Merge remote-tracking branch 'origin/master' 2022-08-22 19:27:43 +02:00
zadam
67b8f3206c Merge pull request #3078 from sigaloid/master
Add compression middleware
2022-08-22 19:27:16 +02:00
DynamoFox
b965f77f4a Rate limit the /auth/login route of ETAPI 2022-08-22 11:50:58 +02:00
sigaloid
84fc00465e Fix minor typo 2022-08-20 20:33:10 -04:00
Matt
9b0c8a19f9 Merge branch 'zadam:master' into master 2022-08-20 21:30:54 +00:00
sigaloid
8e7d26bf4f Add compression 2022-08-20 17:30:35 -04:00
zadam
df351a8d09 trilium safe mode now disables GPU usage 2022-08-20 22:39:59 +02:00
zadam
9ce0421ae7 Merge pull request #3075 from sigaloid/master
Filter excludeFromNoteMap from backlinks
2022-08-20 22:31:37 +02:00
sigaloid
c85ec957ec Filter excludeFromNoteMap from backlinks 2022-08-18 23:12:58 -04:00
zadam
e93dd176c0 Merge pull request #3074 from sigaloid/master
Add shareDescription tag for HTML meta tags
2022-08-18 23:11:56 +02:00
sigaloid
10676a8d0c Add shareDescription 2022-08-17 15:18:34 -04:00
zadam
8abcf571e8 fixes for undefined parent note #3066 2022-08-16 23:46:56 +02:00
zadam
78bca2477d fix creating virtual "none" note in becca, closes #3066 2022-08-16 23:38:11 +02:00
zadam
f241bc564c Merge pull request #3065 from sigaloid/master
Add Flatpak to README
2022-08-15 23:46:40 +02:00
sigaloid
65942213de Add Flatpak to README 2022-08-15 12:01:55 -04:00
zadam
28f620d51d update demo document, closes #3061 2022-08-14 15:05:10 +02:00
zadam
aebdbdf8be fix falsy check in setting custom widget positions, closes #3060 2022-08-14 15:00:18 +02:00
zadam
bb3734052a Merge pull request #3058 from eliandoran/bugfix/upload_filename_encoding
Fix UTF-8 file name encoding for uploads (fixes #3013)
2022-08-14 14:53:44 +02:00
zadam
9a691c8227 Merge pull request #3057 from eliandoran/bugfix/max_content_width
Fix max content width not working (fixes #3056)
2022-08-14 14:52:49 +02:00
zadam
3bd2732cee Merge pull request #3059 from eliandoran/bugfix/relation_map_crash
Fix relation map crash on missing inverse pair (fixes #3055)
2022-08-14 14:50:52 +02:00
Elian Doran
ddb57e35f3 Fix relation map crash on missing inverse pair (fixes #3055) 2022-08-14 04:30:00 +03:00
Elian Doran
d1f3ce0957 Fix UTF-8 file name encoding for uploads (fixes #3013) 2022-08-14 04:14:34 +03:00
Elian Doran
d73da33b08 Fix max content width not working (fixes #3056) 2022-08-14 02:43:09 +03:00
zadam
201ef7fcd5 fix "show recent note" button, closes #3051 2022-08-09 21:49:37 +02:00
zadam
6dee1f38f7 Merge pull request #3048 from DaiYuANg/master
add manifest.webmanifest into desktop make pwa install on ipad
2022-08-08 20:28:29 +02:00
DaiYuANg
87e2f50c6d add manifest.webmanifest into desktop make pwa install on ipad 2022-08-08 21:51:04 +08:00
zadam
2335e40262 release 0.54.2 2022-08-07 23:20:59 +02:00
zadam
3a07c5fcc0 release 0.54.2 2022-08-07 23:18:03 +02:00
zadam
0681ec9057 Merge pull request #3038 from DynamoFox/support-reverse-proxy-trust
Add optional support to trust reverse proxies (via X-Forwarded-For)
2022-08-04 21:14:48 +02:00
DynamoFox
912f79f1af Add optional support to trust reverse proxies (via X-Forwarded-For) 2022-08-04 00:19:54 +02:00
zadam
b6f1611218 less accented backlinks background (in dark mode esp.) 2022-08-03 19:24:26 +02:00
zadam
869c910801 fix "bulk actions" dialog title 2022-08-03 19:17:13 +02:00
99 changed files with 2233 additions and 684 deletions

View File

@@ -41,6 +41,10 @@ Trilium is provided as either desktop application (Linux and Windows) or web app
* If you want to install Trilium on server, follow [this page](https://github.com/zadam/trilium/wiki/Server-installation).
* Currently only recent Chrome and Firefox are supported (tested) browsers.
Trilium is also provided as a Flatpak:
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
## Documentation
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)

48
bin/release-flatpack.sh Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
VERSION=$1
if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
then
echo "Version ${VERSION} isn't in format X.Y.Z"
exit 1
fi
VERSION_DATE=$(git log -1 --format=%aI "v${VERSION}" | cut -c -10)
VERSION_COMMIT=$(git rev-list -n 1 "v${VERSION}")
# expecting the directory at a specific path
cd ~/trilium-flathub
if ! git diff-index --quiet HEAD --; then
echo "There are uncommitted changes"
exit 1
fi
if [[ "$VERSION" == *"beta"* ]]; then
git switch beta
else
git switch master
fi
git pull
echo "Updating files with version ${VERSION}, date ${VERSION_DATE} and commit ${VERSION_COMMIT}"
flatpak-node-generator npm ../trilium/package-lock.json
xmlstarlet ed --inplace --update "/component/releases/release/@version" --value "${VERSION}" --update "/component/releases/release/@date" --value "${VERSION_DATE}" ./com.github.zadam.trilium.metainfo.xml
yq --inplace "(.modules[0].sources[0].tag = \"v${VERSION}\") | (.modules[0].sources[0].commit = \"${VERSION_COMMIT}\")" ./com.github.zadam.trilium.yml
git add ./generated-sources.json
git add ./com.github.zadam.trilium.metainfo.xml
git add ./com.github.zadam.trilium.yml
git commit -m "release $VERSION"
git push

View File

@@ -5,8 +5,8 @@ UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title';
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'hoistedCssClass', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'hoistedInbox', 'hoistedSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarked', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey',

View File

@@ -2,4 +2,4 @@ SET DIR=%~dp0
SET TRILIUM_SAFE_MODE=1
cd %DIR%
WHERE powershell.exe
IF %ERRORLEVEL% NEQ 0 (start trilium.exe) ELSE (powershell.exe ./trilium-safe-mode.ps1)
IF %ERRORLEVEL% NEQ 0 (start trilium.exe --disable-gpu) ELSE (powershell.exe ./trilium-safe-mode.ps1)

View File

@@ -1,2 +1,2 @@
Set-Item -Path Env:TRILIUM_SAFE_MODE -Value 1
./trilium.exe
./trilium.exe --disable-gpu

View File

@@ -3,5 +3,5 @@
DIR=`dirname "$0"`
export TRILIUM_SAFE_MODE=1
"$DIR/trilium"
"$DIR/trilium" --disable-gpu

View File

@@ -21,3 +21,9 @@ https=false
# path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
certPath=
keyPath=
# setting to give trust to reverse proxies, a comma-separated list of trusted rev. proxy IPs can be specified (CIDR notation is permitted),
# alternatively 'true' will make use of the leftmost IP in X-Forwarded-For, ultimately an integer can be used to tell about the number of hops between
# Trilium (which is hop 0) and the first trusted rev. proxy.
# once set, expressjs will use the X-Forwarded-For header set by the rev. proxy to determinate the real IPs of clients.
# expressjs shortcuts are supported: loopback(127.0.0.1/8, ::1/128), linklocal(169.254.0.0/16, fe80::/10), uniquelocal(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7)
trustedReverseProxy=false

Binary file not shown.

View File

@@ -326,7 +326,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_branch.js.html">becca/entities/branch.js</a>, <a href="becca_entities_branch.js.html#line94">line 94</a>
<a href="becca_entities_branch.js.html">becca/entities/branch.js</a>, <a href="becca_entities_branch.js.html#line95">line 95</a>
</li></ul></dd>
@@ -588,7 +588,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_branch.js.html">becca/entities/branch.js</a>, <a href="becca_entities_branch.js.html#line108">line 108</a>
<a href="becca_entities_branch.js.html">becca/entities/branch.js</a>, <a href="becca_entities_branch.js.html#line109">line 109</a>
</li></ul></dd>
@@ -1144,7 +1144,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_branch.js.html">becca/entities/branch.js</a>, <a href="becca_entities_branch.js.html#line129">line 129</a>
<a href="becca_entities_branch.js.html">becca/entities/branch.js</a>, <a href="becca_entities_branch.js.html#line130">line 130</a>
</li></ul></dd>

View File

@@ -978,13 +978,17 @@
<h4 class="name" id="addAttribute"><span class="type-signature"></span>addAttribute<span class="signature">()</span><span class="type-signature"> &rarr; {<a href="Attribute.html">Attribute</a>}</span></h4>
<h4 class="name" id="addAttribute"><span class="type-signature"></span>addAttribute<span class="signature">(type, name, value<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {<a href="Attribute.html">Attribute</a>}</span></h4>
<div class="description">
Adds a new attribute to this note. The attribute is saved and returned.
See addLabel, addRelation for more specific methods.
</div>
@@ -994,6 +998,130 @@
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Attributes</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>type</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
</td>
<td class="description last">attribute type (label / relation)</td>
</tr>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
</td>
<td class="description last">name of the attribute, not including the leading ~/#</td>
</tr>
<tr>
<td class="name"><code>value</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="description last">value of the attribute - text for labels, target note ID for relations; optional.</td>
</tr>
</tbody>
</table>
@@ -1026,7 +1154,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1077">line 1077</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1086">line 1086</a>
</li></ul></dd>
@@ -1163,6 +1291,383 @@
<h4 class="name" id="addLabel"><span class="type-signature"></span>addLabel<span class="signature">(name, value<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {<a href="Attribute.html">Attribute</a>}</span></h4>
<div class="description">
Adds a new label to this note. The label attribute is saved and returned.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Attributes</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
</td>
<td class="description last">name of the label, not including the leading #</td>
</tr>
<tr>
<td class="name"><code>value</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="description last">text value of the label; optional</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1107">line 1107</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type"><a href="Attribute.html">Attribute</a></span>
</dd>
</dl>
<h4 class="name" id="addRelation"><span class="type-signature"></span>addRelation<span class="signature">(name, value)</span><span class="type-signature"> &rarr; {<a href="Attribute.html">Attribute</a>}</span></h4>
<div class="description">
Adds a new relation to this note. The relation attribute is saved and
returned.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last">name of the relation, not including the leading ~</td>
</tr>
<tr>
<td class="name"><code>value</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last">ID of the target note of the relation</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1120">line 1120</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type"><a href="Attribute.html">Attribute</a></span>
</dd>
</dl>
@@ -1350,7 +1855,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1179">line 1179</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1205">line 1205</a>
</li></ul></dd>
@@ -1550,7 +2055,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1193">line 1193</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1219">line 1219</a>
</li></ul></dd>
@@ -1812,7 +2317,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line999">line 999</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1001">line 1001</a>
</li></ul></dd>
@@ -1918,7 +2423,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line919">line 919</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line921">line 921</a>
</li></ul></dd>
@@ -3097,7 +3602,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line714">line 714</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line716">line 716</a>
</li></ul></dd>
@@ -6495,7 +7000,7 @@ This method can be significantly faster than the getAttribute()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line862">line 862</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line864">line 864</a>
</li></ul></dd>
@@ -6597,7 +7102,7 @@ This method can be significantly faster than the getAttribute()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line841">line 841</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line843">line 843</a>
</li></ul></dd>
@@ -6699,7 +7204,7 @@ This method can be significantly faster than the getAttribute()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line810">line 810</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line812">line 812</a>
</li></ul></dd>
@@ -6801,7 +7306,7 @@ This method can be significantly faster than the getAttribute()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line961">line 961</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line963">line 963</a>
</li></ul></dd>
@@ -6997,7 +7502,7 @@ This method can be significantly faster than the getAttribute()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line945">line 945</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line947">line 947</a>
</li></ul></dd>
@@ -8476,7 +8981,7 @@ This method can be significantly faster than the getAttribute()
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1020">line 1020</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1022">line 1022</a>
</li></ul></dd>
@@ -9398,7 +9903,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1064">line 1064</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1066">line 1066</a>
</li></ul></dd>
@@ -9578,7 +10083,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1155">line 1155</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1181">line 1181</a>
</li></ul></dd>
@@ -9758,7 +10263,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1163">line 1163</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1189">line 1189</a>
</li></ul></dd>
@@ -9953,7 +10458,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1237">line 1237</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1263">line 1263</a>
</li></ul></dd>
@@ -10185,7 +10690,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1033">line 1033</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1035">line 1035</a>
</li></ul></dd>
@@ -10365,7 +10870,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1139">line 1139</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1165">line 1165</a>
</li></ul></dd>
@@ -10525,7 +11030,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1147">line 1147</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1173">line 1173</a>
</li></ul></dd>
@@ -10767,7 +11272,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1106">line 1106</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1132">line 1132</a>
</li></ul></dd>
@@ -10978,7 +11483,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1122">line 1122</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1148">line 1148</a>
</li></ul></dd>
@@ -11189,7 +11694,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1131">line 1131</a>
<a href="becca_entities_note.js.html">becca/entities/note.js</a>, <a href="becca_entities_note.js.html#line1157">line 1157</a>
</li></ul></dd>

View File

@@ -98,21 +98,22 @@ class Branch extends AbstractEntity {
this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this;
const childNote = this.childNote;
if (!childNote.parentBranches.includes(this)) {
childNote.parentBranches.push(this);
}
if (this.branchId === 'root') {
return;
}
const childNote = this.childNote;
const parentNote = this.parentNote;
if (!childNote.parents.includes(parentNote)) {
childNote.parents.push(parentNote);
}
if (!childNote.parentBranches.includes(this)) {
childNote.parentBranches.push(this);
}
if (!parentNote.children.includes(childNote)) {
parentNote.children.push(childNote);
}
@@ -132,9 +133,9 @@ class Branch extends AbstractEntity {
return this.childNote;
}
/** @returns {Note} */
/** @returns {Note|undefined} - root branch will have undefined parent, all other branches have to have a parent note */
get parentNote() {
if (!(this.parentNoteId in this.becca.notes)) {
if (!(this.parentNoteId in this.becca.notes) &amp;&amp; this.parentNoteId !== 'none') {
// entities can come out of order in sync/import, create skeleton which will be filled later
this.becca.addNote(this.parentNoteId, new Note({noteId: this.parentNoteId}));
}

View File

@@ -727,9 +727,11 @@ class Note extends AbstractEntity {
sortParents() {
this.parentBranches.sort((a, b) =>
a.branchId.startsWith('virt-')
|| a.parentNote.hasInheritableOwnedArchivedLabel() ? 1 : -1);
|| a.parentNote?.hasInheritableOwnedArchivedLabel() ? 1 : -1);
this.parents = this.parentBranches.map(branch => branch.parentNote);
this.parents = this.parentBranches
.map(branch => branch.parentNote)
.filter(note => !!note);
}
/**
@@ -1100,6 +1102,13 @@ class Note extends AbstractEntity {
}
/**
* Adds a new attribute to this note. The attribute is saved and returned.
* See addLabel, addRelation for more specific methods.
*
* @param {string} type - attribute type (label / relation)
* @param {string} name - name of the attribute, not including the leading ~/#
* @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional.
*
* @return {Attribute}
*/
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
@@ -1115,10 +1124,27 @@ class Note extends AbstractEntity {
}).save();
}
/**
* Adds a new label to this note. The label attribute is saved and returned.
*
* @param {string} name - name of the label, not including the leading #
* @param {string} [value] - text value of the label; optional
*
* @return {Attribute}
*/
addLabel(name, value = "", isInheritable = false) {
return this.addAttribute(LABEL, name, value, isInheritable);
}
/**
* Adds a new relation to this note. The relation attribute is saved and
* returned.
*
* @param {string} name - name of the relation, not including the leading ~
* @param {string} value - ID of the target note of the relation
*
* @return {Attribute}
*/
addRelation(name, targetNoteId, isInheritable = false) {
return this.addAttribute(RELATION, name, targetNoteId, isInheritable);
}

View File

@@ -167,7 +167,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line29">line 29</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line30">line 30</a>
</li></ul></dd>
@@ -267,7 +267,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line38">line 38</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line39">line 39</a>
</li></ul></dd>
@@ -335,7 +335,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line46">line 46</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line47">line 47</a>
</li></ul></dd>
@@ -403,7 +403,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line52">line 52</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line53">line 53</a>
</li></ul></dd>
@@ -471,7 +471,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line63">line 63</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line64">line 64</a>
</li></ul></dd>
@@ -543,7 +543,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line73">line 73</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line74">line 74</a>
</li></ul></dd>
@@ -611,7 +611,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line59">line 59</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line60">line 60</a>
</li></ul></dd>
@@ -679,7 +679,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line44">line 44</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line45">line 45</a>
</li></ul></dd>
@@ -747,7 +747,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line49">line 49</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line50">line 50</a>
</li></ul></dd>
@@ -815,7 +815,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line41">line 41</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line42">line 42</a>
</li></ul></dd>
@@ -883,7 +883,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line61">line 61</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line62">line 62</a>
</li></ul></dd>
@@ -955,7 +955,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line68">line 68</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line69">line 69</a>
</li></ul></dd>
@@ -1103,7 +1103,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line504">line 504</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line505">line 505</a>
</li></ul></dd>
@@ -1303,7 +1303,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line238">line 238</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line239">line 239</a>
</li></ul></dd>
@@ -1481,7 +1481,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line526">line 526</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line527">line 527</a>
</li></ul></dd>
@@ -1589,7 +1589,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line163">line 163</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line164">line 164</a>
</li></ul></dd>
@@ -1693,7 +1693,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line146">line 146</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line147">line 147</a>
</li></ul></dd>
@@ -1795,7 +1795,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line173">line 173</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line174">line 174</a>
</li></ul></dd>
@@ -1897,7 +1897,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line211">line 211</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line212">line 212</a>
</li></ul></dd>
@@ -1999,7 +1999,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line216">line 216</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line217">line 217</a>
</li></ul></dd>
@@ -2150,7 +2150,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line566">line 566</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line567">line 567</a>
</li></ul></dd>
@@ -2317,7 +2317,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line390">line 390</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line391">line 391</a>
</li></ul></dd>
@@ -2472,7 +2472,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line590">line 590</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line591">line 591</a>
</li></ul></dd>
@@ -2582,7 +2582,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line717">line 717</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line718">line 718</a>
</li></ul></dd>
@@ -2756,7 +2756,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line493">line 493</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line494">line 494</a>
</li></ul></dd>
@@ -2956,7 +2956,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line225">line 225</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line226">line 226</a>
</li></ul></dd>
@@ -3134,7 +3134,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line515">line 515</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line516">line 516</a>
</li></ul></dd>
@@ -3289,7 +3289,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line560">line 560</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line561">line 561</a>
</li></ul></dd>
@@ -3456,7 +3456,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line382">line 382</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line383">line 383</a>
</li></ul></dd>
@@ -3611,7 +3611,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line584">line 584</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line585">line 585</a>
</li></ul></dd>
@@ -3766,7 +3766,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line572">line 572</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line573">line 573</a>
</li></ul></dd>
@@ -3933,7 +3933,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line458">line 458</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line459">line 459</a>
</li></ul></dd>
@@ -4088,7 +4088,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line596">line 596</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line597">line 597</a>
</li></ul></dd>
@@ -4194,7 +4194,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line153">line 153</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line154">line 154</a>
</li></ul></dd>
@@ -4296,7 +4296,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line138">line 138</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line139">line 139</a>
</li></ul></dd>
@@ -4398,7 +4398,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line181">line 181</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line182">line 182</a>
</li></ul></dd>
@@ -4500,7 +4500,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line186">line 186</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line187">line 187</a>
</li></ul></dd>
@@ -4651,7 +4651,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line578">line 578</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line579">line 579</a>
</li></ul></dd>
@@ -4818,7 +4818,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line466">line 466</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line467">line 467</a>
</li></ul></dd>
@@ -4973,7 +4973,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line608">line 608</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line609">line 609</a>
</li></ul></dd>
@@ -5143,7 +5143,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line618">line 618</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line619">line 619</a>
</li></ul></dd>
@@ -5294,7 +5294,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line602">line 602</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line603">line 603</a>
</li></ul></dd>
@@ -5400,7 +5400,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line761">line 761</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line762">line 762</a>
</li></ul></dd>
@@ -5513,7 +5513,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line696">line 696</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line697">line 697</a>
</li></ul></dd>
@@ -5619,7 +5619,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line706">line 706</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line707">line 707</a>
</li></ul></dd>
@@ -5721,7 +5721,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line632">line 632</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line633">line 633</a>
</li></ul></dd>
@@ -5895,7 +5895,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line475">line 475</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line476">line 476</a>
</li></ul></dd>
@@ -6001,7 +6001,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line168">line 168</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line169">line 169</a>
</li></ul></dd>
@@ -6152,7 +6152,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line542">line 542</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line543">line 543</a>
</li></ul></dd>
@@ -6330,7 +6330,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line484">line 484</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line485">line 485</a>
</li></ul></dd>
@@ -6485,7 +6485,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line536">line 536</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line537">line 537</a>
</li></ul></dd>
@@ -6640,7 +6640,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line548">line 548</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line549">line 549</a>
</li></ul></dd>
@@ -6795,7 +6795,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line554">line 554</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line555">line 555</a>
</li></ul></dd>
@@ -6903,7 +6903,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line689">line 689</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line690">line 690</a>
</li></ul></dd>
@@ -6987,7 +6987,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line756">line 756</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line757">line 757</a>
</li></ul></dd>
@@ -7093,7 +7093,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line748">line 748</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line749">line 749</a>
</li></ul></dd>
@@ -7199,7 +7199,7 @@ This note's representation is used in note tree and is kept in Froca.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line111">line 111</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line112">line 112</a>
</li></ul></dd>

View File

@@ -31,6 +31,7 @@ import noteAttributeCache from "../services/note_attribute_cache.js";
import ws from "../services/ws.js";
import options from "../services/options.js";
import froca from "../services/froca.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
const LABEL = 'label';
const RELATION = 'relation';
@@ -840,6 +841,10 @@ class NoteShort {
return false;
}
isContentAvailable() {
return !this.isProtected || protectedSessionHolder.isProtectedSessionAvailable()
}
}
export default NoteShort;

View File

@@ -13,12 +13,12 @@ appIconService.installLocalAppIcon();
require('electron-dl')({ saveAs: true });
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
else if (process.platform === 'win32') {
app.exit(0); // attempt to fix the issue when app.quite() won't terminate processes on windows
app.quit()
}
});
@@ -30,7 +30,7 @@ app.on('ready', async () => {
if (await sqlInit.isDbInitialized()) {
await sqlInit.dbReady;
await windowService.createMainWindow();
await windowService.createMainWindow(app);
tray.createTray();
}

957
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.54.1-beta",
"version": "0.55.1",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -28,15 +28,16 @@
"@electron/remote": "2.0.8",
"@excalidraw/excalidraw": "0.12.0",
"archiver": "5.3.1",
"async-mutex": "0.3.2",
"async-mutex": "0.4.0",
"axios": "0.27.2",
"better-sqlite3": "7.4.5",
"chokidar": "3.5.3",
"cls-hooked": "4.2.2",
"commonmark": "0.30.0",
"compression": "^1.7.4",
"cookie-parser": "1.4.6",
"csurf": "1.11.0",
"dayjs": "1.11.4",
"dayjs": "1.11.5",
"dayjs-plugin-utc": "^0.1.2",
"ejs": "3.1.8",
"electron-debug": "3.2.0",
@@ -44,16 +45,16 @@
"electron-window-state": "5.0.3",
"express": "4.18.1",
"express-partial-content": "1.0.2",
"express-rate-limit": "6.5.1",
"express-rate-limit": "6.6.0",
"express-session": "1.17.3",
"fs-extra": "10.1.0",
"helmet": "5.1.1",
"helmet": "6.0.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "5.0.0",
"https-proxy-agent": "5.0.1",
"image-type": "4.1.0",
"ini": "3.0.0",
"ini": "3.0.1",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.16.1",
@@ -61,10 +62,9 @@
"jsdom": "20.0.0",
"mime-types": "2.1.35",
"multer": "1.4.5-lts.1",
"node-abi": "3.22.0",
"node-abi": "3.24.0",
"normalize-strings": "1.1.1",
"open": "8.4.0",
"portscanner": "2.2.0",
"rand-token": "1.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
@@ -88,15 +88,15 @@
"devDependencies": {
"cross-env": "7.0.3",
"electron": "16.2.8",
"electron-builder": "23.1.0",
"electron-packager": "15.5.1",
"electron-rebuild": "3.2.8",
"electron-builder": "23.3.3",
"electron-packager": "16.0.0",
"electron-rebuild": "3.2.9",
"esm": "3.2.25",
"jasmine": "4.3.0",
"jasmine": "4.4.0",
"jsdoc": "3.6.11",
"lorem-ipsum": "2.0.8",
"rcedit": "3.0.1",
"webpack": "5.73.0",
"webpack": "5.74.0",
"webpack-cli": "4.10.0"
},
"optionalDependencies": {

View File

@@ -211,6 +211,35 @@ describe("Parser", () => {
expect(secondSubSub.constructor.name).toEqual("LabelComparisonExp");
expect(secondSubSub.attributeName).toEqual("third");
});
it("label sub-expression without explicit operator", () => {
const rootExp = parse({
fulltextTokens: [],
expressionTokens: tokens(["#first", ["#second", "or", "#third"], "#fourth"]),
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("AndExp");
assertIsArchived(rootExp.subExpressions[0]);
expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp");
const [firstSub, secondSub, thirdSub] = rootExp.subExpressions[1].subExpressions;
expect(firstSub.constructor.name).toEqual("AttributeExistsExp");
expect(firstSub.attributeName).toEqual("first");
expect(secondSub.constructor.name).toEqual("OrExp");
const [firstSubSub, secondSubSub] = secondSub.subExpressions;
expect(firstSubSub.constructor.name).toEqual("AttributeExistsExp");
expect(firstSubSub.attributeName).toEqual("second");
expect(secondSubSub.constructor.name).toEqual("AttributeExistsExp");
expect(secondSubSub.attributeName).toEqual("third");
expect(thirdSub.constructor.name).toEqual("AttributeExistsExp");
expect(thirdSub.attributeName).toEqual("fourth");
});
});
describe("Invalid expressions", () => {

View File

@@ -5,6 +5,7 @@ const favicon = require('serve-favicon');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const session = require('express-session');
const compression = require('compression')
const FileStore = require('session-file-store')(session);
const sessionSecret = require('./services/session_secret');
const dataDir = require('./services/data_dir');
@@ -18,6 +19,10 @@ const app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
if (!utils.isElectron()) {
app.use(compression()); // HTTP compression
}
app.use(helmet({
hidePoweredBy: false, // errors out in electron
contentSecurityPolicy: false,

View File

@@ -27,6 +27,8 @@ class Becca {
/** @type {Object.<String, EtapiToken>} */
this.etapiTokens = {};
this.dirtyNoteSetCache();
this.loaded = false;
}

View File

@@ -105,9 +105,9 @@ class Branch extends AbstractEntity {
return this.childNote;
}
/** @returns {Note} */
/** @returns {Note|undefined} - root branch will have undefined parent, all other branches have to have a parent note */
get parentNote() {
if (!(this.parentNoteId in this.becca.notes)) {
if (!(this.parentNoteId in this.becca.notes) && this.parentNoteId !== 'none') {
// entities can come out of order in sync/import, create skeleton which will be filled later
this.becca.addNote(this.parentNoteId, new Note({noteId: this.parentNoteId}));
}

View File

@@ -403,7 +403,7 @@ class Note extends AbstractEntity {
templateAttributes.push(
...templateNote.__getAttributes(newPath)
// template attr is used as a marker for templates, but it's not meant to be inherited
.filter(attr => !(attr.type === 'label' && attr.name === 'template'))
.filter(attr => !(attr.type === 'label' && (attr.name === 'template' || attr.name === 'workspacetemplate')))
);
}
}
@@ -699,9 +699,11 @@ class Note extends AbstractEntity {
sortParents() {
this.parentBranches.sort((a, b) =>
a.branchId.startsWith('virt-')
|| a.parentNote.hasInheritableOwnedArchivedLabel() ? 1 : -1);
|| a.parentNote?.hasInheritableOwnedArchivedLabel() ? 1 : -1);
this.parents = this.parentBranches.map(branch => branch.parentNote);
this.parents = this.parentBranches
.map(branch => branch.parentNote)
.filter(note => !!note);
}
/**
@@ -1072,6 +1074,13 @@ class Note extends AbstractEntity {
}
/**
* Adds a new attribute to this note. The attribute is saved and returned.
* See addLabel, addRelation for more specific methods.
*
* @param {string} type - attribute type (label / relation)
* @param {string} name - name of the attribute, not including the leading ~/#
* @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional.
*
* @return {Attribute}
*/
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
@@ -1087,10 +1096,27 @@ class Note extends AbstractEntity {
}).save();
}
/**
* Adds a new label to this note. The label attribute is saved and returned.
*
* @param {string} name - name of the label, not including the leading #
* @param {string} [value] - text value of the label; optional
*
* @return {Attribute}
*/
addLabel(name, value = "", isInheritable = false) {
return this.addAttribute(LABEL, name, value, isInheritable);
}
/**
* Adds a new relation to this note. The relation attribute is saved and
* returned.
*
* @param {string} name - name of the relation, not including the leading ~
* @param {string} value - ID of the target note of the relation
*
* @return {Attribute}
*/
addRelation(name, targetNoteId, isInheritable = false) {
return this.addAttribute(RELATION, name, targetNoteId, isInheritable);
}

View File

@@ -281,7 +281,7 @@ async function findSimilarNotes(noteId) {
}
function gatherAncestorRewards(note) {
if (ancestorNoteIds.has(note.noteId)) {
if (!note || ancestorNoteIds.has(note.noteId)) {
return 0;
}

View File

@@ -3,8 +3,8 @@ const eu = require("./etapi_utils");
const passwordEncryptionService = require("../services/password_encryption");
const etapiTokenService = require("../services/etapi_tokens");
function register(router) {
eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', (req, res, next) => {
function register(router, loginMiddleware) {
eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', loginMiddleware, (req, res, next) => {
const {password, tokenName} = req.body;
if (!passwordEncryptionService.verifyPassword(password)) {

View File

@@ -228,6 +228,36 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}/content:
parameters:
- name: noteId
in: path
required: true
schema:
$ref: '#/components/schemas/EntityId'
get:
description: Returns note content idenfied by its ID
operationId: getNoteContent
responses:
'200':
description: note content response
content:
text/html:
schema:
type: string
put:
description: Updates note content idenfied by its ID
operationId: putNoteContentById
requestBody:
description: html content of note
required: true
content:
text/plain:
schema:
type: string
responses:
'204':
description: note content updated
/notes/{noteId}/export:
parameters:
- name: noteId
@@ -602,6 +632,8 @@ paths:
authToken:
type: string
example: Bc4bFn0Ffiok_4NpbVCDnFz7B2WU+pdhW8B5Ne3DiR5wXrEyqdjgRIsk=
'429':
description: Client IP has been blacklisted because too many requests (possibly failed authentications) were made within a short time frame, try again later
default:
description: unexpected error
content:

View File

@@ -66,8 +66,8 @@ function route(router, method, path, routeHandler) {
router[method](path, checkEtapiAuth, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
}
function NOT_AUTHENTICATED_ROUTE(router, method, path, routeHandler) {
router[method](path, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
function NOT_AUTHENTICATED_ROUTE(router, method, path, middleware, routeHandler) {
router[method](path, ...middleware, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
}
function getAndCheckNote(noteId) {

View File

@@ -267,7 +267,7 @@ class NoteShort {
attrArrs.push(
templateNote.__getCachedAttributes(newPath)
// template attr is used as a marker for templates, but it's not meant to be inherited
.filter(attr => !(attr.type === 'label' && attr.name === 'template'))
.filter(attr => !(attr.type === 'label' && (attr.name === 'template' || attr.name === 'workspacetemplate')))
);
}
}

View File

@@ -78,6 +78,9 @@ import OptionsDialog from "../widgets/dialogs/options.js";
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js";
import EditableCodeButtonsWidget from "../widgets/type_widgets/editable_code_buttons.js";
import ApiLogWidget from "../widgets/api_log.js";
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
export default class DesktopLayout {
constructor(customWidgets) {
@@ -184,6 +187,7 @@ export default class DesktopLayout {
.child(new RelationMapButtons())
.child(new MermaidExportButton())
.child(new BacklinksWidget())
.child(new HideFloatingButtonsButton())
)
.child(new MermaidWidget())
.child(
@@ -195,6 +199,8 @@ export default class DesktopLayout {
.child(new SearchResultWidget())
.child(new SqlResultWidget())
)
.child(new EditableCodeButtonsWidget())
.child(new ApiLogWidget())
.child(new FindWidget())
.child(
...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC

View File

@@ -10,6 +10,7 @@ import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
import ConfirmDialog from "../widgets/dialogs/confirm.js";
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
const MOBILE_CSS = `
<style>
@@ -128,7 +129,7 @@ export default class MobileLayout {
.child(
new NoteDetailWidget()
.css('padding', '5px 20px 10px 0')
)
).child(new FilePropertiesWidget().css('font-size','smaller'))
)
)
.child(new ProtectedSessionPasswordDialog())

View File

@@ -11,7 +11,7 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
branchIdsToMove = filterSearchBranches(branchIdsToMove);
if (beforeBranchId === 'root') {
alert('Cannot move notes before root note.');
toastService.showError('Cannot move notes before root note.');
return;
}
@@ -19,7 +19,7 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
const resp = await server.put(`branches/${branchIdToMove}/move-before/${beforeBranchId}`);
if (!resp.success) {
alert(resp.message);
toastService.showError(resp.message);
return;
}
}
@@ -32,7 +32,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
const afterNote = await froca.getBranch(afterBranchId).getNote();
if (afterNote.noteId === 'root' || afterNote.noteId === hoistedNoteService.getHoistedNoteId()) {
alert('Cannot move notes after root note.');
toastService.showError('Cannot move notes after root note.');
return;
}
@@ -42,7 +42,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
const resp = await server.put(`branches/${branchIdToMove}/move-after/${afterBranchId}`);
if (!resp.success) {
alert(resp.message);
toastService.showError(resp.message);
return;
}
}
@@ -62,13 +62,13 @@ async function moveToParentNote(branchIdsToMove, newParentBranchId) {
const resp = await server.put(`branches/${branchIdToMove}/move-to/${newParentBranchId}`);
if (!resp.success) {
alert(resp.message);
toastService.showError(resp.message);
return;
}
}
}
async function deleteNotes(branchIdsToDelete) {
async function deleteNotes(branchIdsToDelete, forceDeleteAllClones = false) {
branchIdsToDelete = filterRootNote(branchIdsToDelete);
if (branchIdsToDelete.length === 0) {
@@ -83,7 +83,7 @@ async function deleteNotes(branchIdsToDelete) {
}
else {
({proceed, deleteAllClones, eraseNotes} = await new Promise(res =>
appContext.triggerCommand('showDeleteNotesDialog', {branchIdsToDelete, callback: res})));
appContext.triggerCommand('showDeleteNotesDialog', {branchIdsToDelete, callback: res, forceDeleteAllClones})));
}
if (!proceed) {
@@ -127,7 +127,7 @@ async function moveNodeUpInHierarchy(node) {
const resp = await server.put('branches/' + node.data.branchId + '/move-after/' + node.getParent().data.branchId);
if (!resp.success) {
alert(resp.message);
toastService.showError(resp.message);
return;
}
@@ -203,7 +203,7 @@ async function cloneNoteToBranch(childNoteId, parentBranchId, prefix) {
});
if (!resp.success) {
alert(resp.message);
toastService.showError(resp.message);
}
}
@@ -213,7 +213,7 @@ async function cloneNoteToNote(childNoteId, parentNoteId, prefix) {
});
if (!resp.success) {
alert(resp.message);
toastService.showError(resp.message);
}
}
@@ -222,7 +222,7 @@ async function cloneNoteAfter(noteId, afterBranchId) {
const resp = await server.put('notes/' + noteId + '/clone-after/' + afterBranchId);
if (!resp.success) {
alert(resp.message);
toastService.showError(resp.message);
}
}

View File

@@ -179,7 +179,7 @@ export default class Entrypoints extends Component {
const resp = await server.post("sql/execute/" + note.noteId);
if (!resp.success) {
alert("Error occurred while executing SQL query: " + resp.message);
toastService.showError("Error occurred while executing SQL query: " + resp.message);
}
await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results});

View File

@@ -13,6 +13,7 @@ import appContext from "./app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import NoteContextCachingWidget from "../widgets/note_context_caching_widget.js";
import BasicWidget from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js";
/**
* This is the main frontend API interface for scripts. It's published in the local "api" object.
@@ -594,6 +595,33 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
* @returns {string} random string
*/
this.randomString = utils.randomString;
this.logMessages = {};
this.logSpacedUpdates = {};
/**
* Log given message to the log pane in UI
*
* @param message
*/
this.log = message => {
const {noteId} = this.startNote;
message = utils.now() + ": " + message;
console.log(`Script ${noteId}: ${message}`);
this.logMessages[noteId] = this.logMessages[noteId] || [];
this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
const messages = this.logMessages[noteId];
this.logMessages[noteId] = [];
appContext.triggerEvent("apiLogMessages", {noteId, messages});
}, 100);
this.logMessages[noteId].push(message);
this.logSpacedUpdates[noteId].scheduleUpdate();
};
}
export default FrontendScriptApi;

View File

@@ -32,6 +32,9 @@ export async function uploadFiles(parentNoteId, files, options) {
dataType: 'json',
type: 'POST',
timeout: 60 * 60 * 1000,
error: function(xhr) {
toastService.showError("Import failed: " + xhr.responseText);
},
contentType: false, // NEEDED, DON'T REMOVE THIS
processData: false, // NEEDED, DON'T REMOVE THIS
}));

View File

@@ -5,7 +5,7 @@ import noteCreateService from './note_create.js';
import treeService from './tree.js';
import froca from "./froca.js";
// this key needs to have this value so it's hit by the tooltip
// this key needs to have this value, so it's hit by the tooltip
const SELECTED_NOTE_PATH_KEY = "data-note-path";
const SELECTED_EXTERNAL_LINK_KEY = "data-external-link";
@@ -89,6 +89,11 @@ function showRecentNotes($el) {
$el.setSelectedNotePath("");
$el.autocomplete("val", "");
$el.trigger('focus');
// simulate pressing down arrow to trigger autocomplete
const e = $.Event('keydown');
e.which = 40; // arrow down
$el.trigger(e);
}
function initNoteAutocomplete($el, options) {

View File

@@ -135,10 +135,6 @@ function getSomeNotePath(note, hoistedNotePath = 'root') {
return notePath === null ? null : notePath.join('/');
}
async function sortAlphabetically(noteId) {
await server.put(`notes/${noteId}/sort`);
}
ws.subscribeToMessages(message => {
if (message.type === 'openNote') {
appContext.tabManager.activateOrOpenNote(message.noteId);
@@ -319,7 +315,6 @@ function parseNotePath(notePath) {
}
export default {
sortAlphabetically,
resolveNotePath,
resolveNotePathToSegments,
getSomeNotePath,

View File

@@ -3,6 +3,7 @@ import toastService from "./toast.js";
import server from "./server.js";
import options from "./options.js";
import frocaUpdater from "./froca_updater.js";
import appContext from "./app_context.js";
const messageHandlers = [];
@@ -118,6 +119,9 @@ async function handleMessage(event) {
else if (message.type === 'consistency-checks-failed') {
toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000);
}
else if (message.type === 'api-log-messages') {
appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages});
}
}
let entityChangeIdReachedListeners = [];
@@ -175,7 +179,7 @@ async function consumeFrontendUpdateData() {
else {
console.log("nonProcessedEntityChanges causing the timeout", nonProcessedEntityChanges);
alert(`Encountered error "${e.message}", check out the console.`);
toastService.showError(`Encountered error "${e.message}", check out the console.`);
}
}

View File

@@ -17,5 +17,7 @@ document.addEventListener('DOMContentLoaded', () => {
const toggleMenuButton = document.getElementById('toggleMenuButton');
const layout = document.getElementById('layout');
toggleMenuButton.addEventListener('click', () => layout.classList.toggle('showMenu'));
if (toggleMenuButton && layout) {
toggleMenuButton.addEventListener('click', () => layout.classList.toggle('showMenu'));
}
}, false);

View File

@@ -0,0 +1,75 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
const TPL = `
<div class="api-log-widget">
<style>
.api-log-widget {
padding: 15px;
flex-grow: 1;
max-height: 40%;
position: relative;
}
.hidden-api-log {
display: none;
}
.api-log-container {
overflow: auto;
height: 100%;
}
.close-api-log-button {
padding: 5px;
border: 1px solid var(--button-border-color);
background-color: var(--button-background-color);
border-radius: var(--button-border-radius);
color: var(--button-text-color);
position: absolute;
top: 10px;
right: 40px;
cursor: pointer;
}
</style>
<div class="bx bx-x close-api-log-button" title="Close"></div>
<div class="api-log-container"></div>
</div>`;
export default class ApiLogWidget extends NoteContextAwareWidget {
isEnabled() {
return this.note
&& this.note.mime.startsWith('application/javascript;env=')
&& super.isEnabled();
}
doRender() {
this.$widget = $(TPL);
this.toggle(false);
this.$logContainer = this.$widget.find('.api-log-container');
this.$closeButton = this.$widget.find(".close-api-log-button");
this.$closeButton.on("click", () => this.toggle(false));
}
async refreshWithNote(note) {
this.$logContainer.empty();
}
apiLogMessagesEvent({messages, noteId}) {
if (!this.isNote(noteId)) {
return;
}
this.toggle(true);
for (const message of messages) {
this.$logContainer.append(message).append($("<br>"));
}
}
toggle(show) {
this.$widget.toggleClass("hidden-api-log", !show);
}
}

View File

@@ -189,6 +189,8 @@ const ATTR_HELP = {
"runAtHour": "On which hour should this run. Should be used together with <code>#run=hourly</code>. Can be defined multiple times for more runs during the day.",
"disableInclusion": "scripts with this label won't be included into parent script execution.",
"sorted": "keeps child notes sorted by title alphabetically",
"sortDirection": "ASC (the default) or DESC",
"sortFoldersFirst": "Folders (notes with children) should be sorted on top",
"top": "keep given note on top in its parent (applies only on sorted parents)",
"hidePromotedAttributes": "Hide promoted attributes on this note",
"readOnly": "editor is in read only mode. Works only for text and code notes.",
@@ -205,6 +207,7 @@ const ATTR_HELP = {
"workspaceIconClass": "defines box icon CSS class which will be used in tab when hoisted to this note",
"workspaceTabBackgroundColor": "CSS color used in the note tab when hoisted to this note",
"workspaceCalendarRoot": "Defines per-workspace calendar root",
"workspaceTemplate": "This note will appear in the selection of available template when creating new note, but only when hoisted into a workspace containing this template",
"searchHome": "new search notes will be created as children of this note",
"hoistedSearchHome": "new search notes will be created as children of this note when hoisted to some ancestor of this note",
"inbox": "default inbox location for new notes",
@@ -216,6 +219,7 @@ const ATTR_HELP = {
"shareAlias": "define an alias using which the note will be available under https://your_trilium_host/share/[your_alias]",
"shareOmitDefaultCss": "default share page CSS will be omitted. Use when you make extensive styling changes.",
"shareRoot": "marks note which is served on /share root.",
"shareDescription": "define text to be added to the HTML meta tag for description",
"shareRaw": "note will be served in its raw format, without HTML wrapper",
"shareDisallowRobotIndexing": `will forbid robot indexing of this note via <code>X-Robots-Tag: noindex</code> header`,
"shareCredentials": "require credentials to access this shared note. Value is expected to be in format 'username:password'. Don't forget to make this inheritable to apply to child-notes/images.",

View File

@@ -57,8 +57,10 @@ const TPL = `
}
.add-new-attribute-button:hover, .save-attributes-button:hover {
border: 1px solid var(--main-border-color);
border-radius: 2px;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
background: var(--button-background-color);
color: var(--button-text-color);
}
.attribute-errors {
@@ -297,6 +299,12 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
}
async save() {
if (this.lastUpdatedNoteId !== this.noteId) {
// https://github.com/zadam/trilium/issues/3090
console.warn("Ignoring blur event because a different note is loaded.");
return;
}
const attributes = this.parseAttributes();
if (attributes) {
@@ -354,6 +362,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
}
dataChanged() {
this.lastUpdatedNoteId = this.noteId;
if (this.lastSavedContent === this.textEditor.getData()) {
this.$saveAttributesButton.fadeOut();
}

View File

@@ -8,6 +8,28 @@ class BasicWidget extends Component {
style: ''
};
this.classes = [];
this.children = [];
this.childPositionCounter = 10;
}
child(...components) {
if (!components) {
return this;
}
super.child(...components);
for (const component of components) {
if (component.position === undefined) {
component.position = this.childPositionCounter;
this.childPositionCounter += 10;
}
}
this.children.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
return this;
}
id(id) {

View File

@@ -4,6 +4,7 @@ import dateNoteService from "../../services/date_notes.js";
import server from "../../services/server.js";
import appContext from "../../services/app_context.js";
import RightDropdownButtonWidget from "./right_dropdown_button.js";
import toastService from "../../services/toast.js";
const DROPDOWN_TPL = `
<div class="calendar-dropdown-widget">
@@ -62,7 +63,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.hideDropdown();
}
else {
alert("Cannot find day note");
toastService.showError("Cannot find day note");
}
});
}

View File

@@ -35,9 +35,31 @@ const TPL = `
bottom: -30px;
width: 100%;
height: 100%;
pointer-events: none;
}
.update-to-latest-version-button {
display: none;
}
.global-menu .zoom-buttons a {
display: inline-block;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
color: var(--button-text-color);
background-color: var(--button-background-color);
padding: 3px;
margin-left: 3px;
}
.global-menu .zoom-buttons a:hover {
text-decoration: none;
}
.global-menu .zoom-state {
margin-left: 5px;
margin-right: 5px;
}
</style>
<button type="button" data-toggle="dropdown" data-placement="right"
@@ -87,11 +109,30 @@ const TPL = `
Reload frontend
<kbd data-command="reloadFrontendApp"></kbd>
</a>
<span class="zoom-container dropdown-item" style="display: flex; flex-direction: row; justify-content: space-between;">
<div>
<span class="bx bx-empty"></span>
Zoom
</div>
<div class="zoom-buttons">
<a data-trigger-command="toggleFullscreen" title="Toggle fullscreen" class="bx bx-expand-alt"></a>
&nbsp;
<a data-trigger-command="zoomOut" title="Zoom out" class="bx bx-minus"></a>
<span class="zoom-state"></span>
<a data-trigger-command="zoomIn" title="Zoom in" class="bx bx-plus"></a>
</div>
</span>
<a class="dropdown-item" data-trigger-command="toggleFullscreen">
<span class="bx bx-empty"></span>
Toggle fullscreen
<kbd data-command="toggleFullscreen"></kbd>
<kbd ></kbd>
</a>
<a class="dropdown-item" data-trigger-command="showHelp">
@@ -141,9 +182,13 @@ export default class GlobalMenuWidget extends BasicWidget {
this.$widget.find(".open-dev-tools-button").toggle(isElectron);
this.$widget.find(".switch-to-mobile-version-button").toggle(!isElectron);
this.$widget.on('click', '.dropdown-item', e => {
if ($(e.target).parent(".zoom-buttons")) {
return;
}
this.$widget.on('click', '.dropdown-item',
() => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.$widget.find("[data-toggle='dropdown']").dropdown('toggle');
});
this.$widget.find(".global-menu-button-update-available").append(
this.updateAvailableWidget.render()
@@ -151,11 +196,34 @@ export default class GlobalMenuWidget extends BasicWidget {
this.$updateToLatestVersionButton = this.$widget.find(".update-to-latest-version-button");
if (!utils.isElectron()) {
this.$widget.find(".zoom-container").hide();
}
this.$zoomState = this.$widget.find(".zoom-state");
this.$widget.on('show.bs.dropdown', () => this.updateZoomState());
this.$widget.find(".zoom-buttons").on("click",
// delay to wait for the actual zoom change
() => setTimeout(() => this.updateZoomState(), 300)
);
this.updateVersionStatus();
setInterval(() => this.updateVersionStatus(), 8 * 60 * 60 * 1000);
}
updateZoomState() {
if (!utils.isElectron()) {
return;
}
const zoomFactor = utils.dynamicRequire('electron').webFrame.getZoomFactor();
const zoomPercent = Math.round(zoomFactor * 100);
this.$zoomState.text(zoomPercent + "%");
}
async updateVersionStatus() {
if (options.get("checkForUpdates") !== 'true') {
return;

View File

@@ -1,5 +1,6 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import utils from "../../services/utils.js";
import branchService from "../../services/branches.js";
const TPL = `
<div class="dropdown note-actions">
@@ -30,6 +31,7 @@ const TPL = `
<a data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button"><kbd data-command="openNoteExternally"></kbd> Open note externally</a>
<a class="dropdown-item import-files-button">Import files</a>
<a class="dropdown-item export-note-button">Export note</a>
<a class="dropdown-item delete-note-button">Delete note</a>
<a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
</div>
</div>`;
@@ -65,6 +67,15 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
this.$deleteNoteButton = this.$widget.find(".delete-note-button");
this.$deleteNoteButton.on("click", () => {
if (this.note.noteId === 'root') {
return;
}
branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
});
}
refreshWithNote(note) {

View File

@@ -8,7 +8,7 @@ const TPL = `
height: 21px !important;
padding: 0 !important;
border-radius: 8px;
border-radius: var(--button-border-radius);
transform: scale(0.9);
border: none;
opacity: 0.8;

View File

@@ -1,33 +1,6 @@
import BasicWidget from "../basic_widget.js";
export default class Container extends BasicWidget {
constructor() {
super();
this.children = [];
this.positionCounter = 10;
}
child(...components) {
if (!components) {
return this;
}
super.child(...components);
for (const component of components) {
if (!component.position) {
component.position = this.positionCounter;
this.positionCounter += 10;
}
}
this.children.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
return this;
}
doRender() {
this.$widget = $(`<div>`);

View File

@@ -4,6 +4,7 @@ export default class ScrollingContainer extends Container {
constructor() {
super();
this.class("scrolling-container");
this.css('overflow', 'auto');
this.css('position', 'relative');
}

View File

@@ -37,7 +37,7 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">Bulk assign attributes</h5>
<h5 class="modal-title mr-auto">Bulk actions</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>

View File

@@ -3,7 +3,7 @@ import BasicWidget from "../basic_widget.js";
const DELETE_NOTE_BUTTON_CLASS = "confirm-dialog-delete-note";
const TPL = `
<div class="confirm-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="confirm-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">

View File

@@ -97,7 +97,7 @@ export default class DeleteNotesDialog extends BasicWidget {
this.resolve({
proceed: true,
deleteAllClones: this.isDeleteAllClonesChecked(),
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked(),
eraseNotes: this.isEraseNotesChecked()
});
});
@@ -108,7 +108,7 @@ export default class DeleteNotesDialog extends BasicWidget {
async renderDeletePreview() {
const response = await server.post('delete-notes-preview', {
branchIdsToDelete: this.branchIds,
deleteAllClones: this.isDeleteAllClonesChecked()
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked()
});
this.$deleteNotesList.empty();
@@ -143,14 +143,18 @@ export default class DeleteNotesDialog extends BasicWidget {
}
}
async showDeleteNotesDialogEvent({branchIdsToDelete, callback}) {
async showDeleteNotesDialogEvent({branchIdsToDelete, callback, forceDeleteAllClones}) {
this.branchIds = branchIdsToDelete;
this.forceDeleteAllClones = forceDeleteAllClones;
await this.renderDeletePreview();
utils.openDialog(this.$widget);
this.$deleteAllClones.prop("checked", false);
this.$deleteAllClones
.prop("checked", !!forceDeleteAllClones)
.prop("disabled", !!forceDeleteAllClones);
this.$eraseNotes.prop("checked", false);
this.resolve = callback;

View File

@@ -142,7 +142,7 @@ export default class ExportDialog extends BasicWidget {
if (!exportType) {
// this shouldn't happen as we always choose default export type
alert("Choose export type first please");
toastService.showError("Choose export type first please");
return;
}

View File

@@ -2,7 +2,7 @@ import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
const TPL = `
<div class="info-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="info-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">

View File

@@ -1,5 +1,6 @@
import server from "../../../services/server.js";
import dialogService from "../../dialog.js";
import toastService from "../../../services/toast.js";
const TPL = `
<h4>ETAPI</h4>
@@ -36,11 +37,11 @@ const TPL = `
margin-right: 20px;
font-size: large;
border: 1px solid transparent;
border-radius: 5px;
border-radius: var(--button-border-radius);
}
.token-table-button:hover {
border: 1px solid var(--main-border-color);
border: 1px solid var(--button-border-color);
}
</style>`;
@@ -56,7 +57,7 @@ export default class EtapiOptions {
});
if (!tokenName.trim()) {
alert("Token name can't be empty");
toastService.showError("Token name can't be empty");
return;
}

View File

@@ -49,7 +49,7 @@ export default class ChangePasswordOptions {
const options = await server.get('options');
this.optionsLoaded(options);
alert("Password has been reset. Please set new password");
toastService.showError("Password has been reset. Please set new password");
}
});
@@ -74,7 +74,7 @@ export default class ChangePasswordOptions {
this.$newPassword2.val('');
if (newPassword1 !== newPassword2) {
alert("New passwords are not the same.");
toastService.showError("New passwords are not the same.");
return false;
}
@@ -83,7 +83,7 @@ export default class ChangePasswordOptions {
'new_password': newPassword1
}).then(result => {
if (result.success) {
alert("Password has been changed. Trilium will be reloaded after you press OK.");
toastService.showError("Password has been changed. Trilium will be reloaded after you press OK.");
// password changed so current protected session is invalid and needs to be cleared
protectedSessionHolder.resetProtectedSession();

View File

@@ -2,10 +2,10 @@ import utils from "../../services/utils.js";
import BasicWidget from "../basic_widget.js";
const TPL = `
<div class="prompt-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="prompt-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<form id="prompt-dialog-form">
<form class="prompt-dialog-form">
<div class="modal-header">
<h5 class="prompt-title modal-title mr-auto">Prompt</h5>
@@ -34,7 +34,7 @@ export default class PromptDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$dialogBody = this.$widget.find(".modal-body");
this.$form = this.$widget.find("#prompt-dialog-form");
this.$form = this.$widget.find(".prompt-dialog-form");
this.$question = null;
this.$answer = null;

View File

@@ -1,4 +1,4 @@
import Container from "../containers/container.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `
<div class="floating-buttons">
@@ -16,7 +16,7 @@ const TPL = `
z-index: 100;
}
.floating-buttons-children > * {
.floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) {
margin-left: 10px;
}
@@ -24,12 +24,16 @@ const TPL = `
font-size: 130%;
padding: 5px 10px 4px 10px;
}
.floating-buttons.temporarily-hidden {
display: none;
}
</style>
<div class="floating-buttons-children"></div>
</div>`;
export default class FloatingButtons extends Container {
export default class FloatingButtons extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.$children = this.$widget.find(".floating-buttons-children");
@@ -38,4 +42,16 @@ export default class FloatingButtons extends Container {
this.$children.append(widget.render());
}
}
async refreshWithNote(note) {
this.toggle(true);
}
toggle(show) {
this.$widget.toggleClass("temporarily-hidden", !show);
}
hideFloatingButtonsCommand() {
this.toggle(false);
}
}

View File

@@ -0,0 +1,42 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `
<div class="close-floating-buttons">
<style>
.close-floating-buttons {
display: none;
margin-left: 5px !important;
}
/* conditionally display close button if there's some other button visible */
.floating-buttons *:not(.hidden-int):not(.hidden-no-content) ~ .close-floating-buttons {
display: block;
}
.close-floating-buttons-button {
border: 1px solid transparent;
color: var(--button-text-color);
padding: 6px;
border-radius: 100px;
}
.close-floating-buttons-button:hover {
border: 1px solid var(--button-border-color);
}
</style>
<button type="button"
class="close-floating-buttons-button btn bx bx-x no-print"
title="Hide buttons"></button>
</div>
`;
export default class HideFloatingButtonsButton extends NoteContextAwareWidget {
doRender() {
super.doRender();
this.$widget = $(TPL);
this.$widget.on('click', () => this.triggerCommand('hideFloatingButtons'));
this.contentSized();
}
}

View File

@@ -1,7 +1,4 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import dialogService from "../dialog.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
const TPL = `
<button type="button"

View File

@@ -1,7 +1,4 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import dialogService from "../dialog.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
const TPL = `
<div>

View File

@@ -36,7 +36,7 @@ const TPL = `
right: 10px;
width: 400px;
border-radius: 10px;
background-color: var(--more-accented-background-color);
background-color: var(--accented-background-color);
color: var(--main-text-color);
padding: 20px;
overflow-y: auto;
@@ -89,17 +89,21 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
const resp = await server.get(`notes/${this.noteId}/backlink-count`);
if (!resp || !resp.count) {
this.$ticker.hide();
this.toggle(false);
return;
}
this.$ticker.show();
this.$ticker.toggle(true);
this.$count.text(
`${resp.count} backlink`
+ (resp.count === 1 ? '' : 's')
);
}
toggle(show) {
this.$widget.toggleClass("hidden-no-content", !show);
}
clearItems() {
this.$items.empty().hide();
}

View File

@@ -13,6 +13,7 @@ import appContext from "../services/app_context.js";
import keyboardActionsService from "../services/keyboard_actions.js";
import clipboard from "../services/clipboard.js";
import protectedSessionService from "../services/protected_session.js";
import linkService from "../services/link.js";
import syncService from "../services/sync.js";
import options from "../services/options.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
@@ -393,6 +394,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
title: node.title
}));
if (notes.length === 1) {
linkService.createNoteLink(notes[0].noteId, {referenceLink: true})
.then($link => data.dataTransfer.setData("text/html", $link[0].outerHTML));
}
else {
Promise.all(notes.map(note => linkService.createNoteLink(note.noteId, {referenceLink: true}))).then(links => {
const $list = $("<ul>").append(...links.map($link => $("<li>").append($link)));
data.dataTransfer.setData("text/html", $list[0].outerHTML);
});
}
data.dataTransfer.setData("text", JSON.stringify(notes));
return true; // allow dragging to start
},
@@ -513,8 +526,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
if (isHoistedNote) {
const $unhoistButton = $('<span class="tree-item-button unhoist-button bx bx-door-open" title="Unhoist"></span>');
$unhoistButton.on('click', () => alert("bebe"));
$span.append($unhoistButton);
}
@@ -935,7 +946,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
if (this.noteContext
&& this.noteContext.notePath
&& !this.noteContext.note.isDeleted
&& !this.noteContext.note?.isDeleted
&& !this.noteContext.notePath.includes("root/hidden")
) {
const newActiveNode = await this.getNodeFromPath(this.noteContext.notePath);

View File

@@ -10,12 +10,6 @@ export default class NoteWrapperWidget extends FlexContainer {
.collapsible();
}
doRender() {
super.doRender();
this.$widget.addClass("note-split");
}
setNoteContextEvent({noteContext}) {
this.noteContext = noteContext;
@@ -35,8 +29,14 @@ export default class NoteWrapperWidget extends FlexContainer {
}
refresh() {
const isHiddenExt = this.isHiddenExt(); // preserve through class reset
this.$widget.removeClass();
this.toggleExt(!isHiddenExt);
this.$widget.addClass("component note-split");
const note = this.noteContext?.note;
if (!note) {
return;

View File

@@ -13,7 +13,7 @@ const TPL = `
<div style="margin-left: 10px; margin-right: 10px">depth:</div>
<select name="depth" class="form-control d-inline ancestor-depth" style="flex-shrink: 3">
<option value="">doesn't mattter</option>
<option value="">doesn't matter</option>
<option value="eq1">is exactly 1 (direct children)</option>
<option value="eq2">is exactly 2</option>
<option value="eq3">is exactly 3</option>

View File

@@ -16,7 +16,10 @@ const TPL = `
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
line-height: 0.5;
border-radius: 0.2rem;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
background: var(--button-background-color);
color: var(--button-text-color);
}
.sql-console-result-container {

View File

@@ -93,7 +93,7 @@ const TAB_ROW_TPL = `
.note-new-tab:hover {
background-color: var(--accented-background-color);
border-radius: 5px;
border-radius: var(--button-border-radius);
}
.tab-row-filler {

View File

@@ -23,26 +23,6 @@ const TPL = `
</style>
<div class="note-detail-code-editor"></div>
<div style="display: flex; justify-content: space-evenly;">
<button data-trigger-command="runActiveNote"
class="no-print execute-button btn btn-sm">
Execute <kbd data-command="runActiveNote"></kbd>
</button>
<button class="no-print trilium-api-docs-button btn btn-sm"
title="Open Trilium API docs">
<span class="bx bx-help-circle"></span>
API docs
</button>
<button class="no-print save-to-note-button btn btn-sm">
<span class="bx bx-save"></span>
Save to note</kbd>
</button>
</div>
</div>`;
export default class EditableCodeTypeWidget extends TypeWidget {
@@ -50,28 +30,7 @@ export default class EditableCodeTypeWidget extends TypeWidget {
doRender() {
this.$widget = $(TPL);
this.$openTriliumApiDocsButton = this.$widget.find(".trilium-api-docs-button");
this.$openTriliumApiDocsButton.on("click", () => {
if (this.note.mime.endsWith("frontend")) {
window.open("https://zadam.github.io/trilium/frontend_api/FrontendScriptApi.html", "_blank");
}
else {
window.open("https://zadam.github.io/trilium/backend_api/BackendScriptApi.html", "_blank");
}
});
this.$editor = this.$widget.find('.note-detail-code-editor');
this.$executeButton = this.$widget.find('.execute-button');
this.$saveToNoteButton = this.$widget.find('.save-to-note-button');
this.$saveToNoteButton.on('click', async () => {
const {notePath} = await server.post("special-notes/save-sql-console", {sqlConsoleNoteId: this.noteId});
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(notePath);
toastService.showMessage("SQL Console note has been saved into " + await treeService.getNotePathTitle(notePath));
});
keyboardActionService.setupActionsForElement('code-detail', this.$widget, this);
@@ -115,18 +74,6 @@ export default class EditableCodeTypeWidget extends TypeWidget {
}
async doRefresh(note) {
this.$executeButton.toggle(
note.mime.startsWith('application/javascript')
|| note.mime === 'text/x-sqlite;schema=trilium'
);
this.$saveToNoteButton.toggle(
note.mime === 'text/x-sqlite;schema=trilium'
&& !note.getAllNotePaths().find(notePathArr => !notePathArr.includes("hidden"))
);
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env='));
const noteComplement = await this.noteContext.getNoteComplement();
await this.spacedUpdate.allowUpdateWithoutChange(() => {

View File

@@ -0,0 +1,96 @@
import server from "../../services/server.js";
import ws from "../../services/ws.js";
import appContext from "../../services/app_context.js";
import toastService from "../../services/toast.js";
import treeService from "../../services/tree.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import keyboardActionService from "../../services/keyboard_actions.js";
const TPL = `
<div class="editable-code-buttons-widget">
<style>
.editable-code-buttons-widget {
display: flex;
justify-content: space-evenly;
padding: 5px;
height: 45px;
}
</style>
<button data-trigger-command="runActiveNote"
class="no-print execute-button btn btn-sm">
<span class="bx bx-run"></span>
Execute <kbd data-command="runActiveNote"></kbd>
</button>
<button class="no-print trilium-api-docs-button btn btn-sm"
title="Open Trilium API docs">
<span class="bx bx-help-circle"></span>
API docs
</button>
<button class="no-print save-to-note-button btn btn-sm">
<span class="bx bx-save"></span>
Save to note</kbd>
</button>
</div>`;
export default class EditableCodeButtonsWidget extends NoteContextAwareWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& (this.note.mime.startsWith('application/javascript') || this.note.mime === 'text/x-sqlite;schema=trilium');
}
doRender() {
this.$widget = $(TPL);
this.$openTriliumApiDocsButton = this.$widget.find(".trilium-api-docs-button");
this.$openTriliumApiDocsButton.on("click", () => {
if (this.note.mime.endsWith("frontend")) {
window.open("https://zadam.github.io/trilium/frontend_api/FrontendScriptApi.html", "_blank");
}
else {
window.open("https://zadam.github.io/trilium/backend_api/BackendScriptApi.html", "_blank");
}
});
this.$executeButton = this.$widget.find('.execute-button');
this.$saveToNoteButton = this.$widget.find('.save-to-note-button');
this.$saveToNoteButton.on('click', async () => {
const {notePath} = await server.post("special-notes/save-sql-console", {sqlConsoleNoteId: this.noteId});
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(notePath);
toastService.showMessage("SQL Console note has been saved into " + await treeService.getNotePathTitle(notePath));
});
keyboardActionService.setupActionsForElement('code-detail', this.$widget, this);
keyboardActionService.updateDisplayedShortcuts(this.$widget);
super.doRender();
}
refreshWithNote(note) {
this.$executeButton.toggle(
note.mime.startsWith('application/javascript')
|| note.mime === 'text/x-sqlite;schema=trilium'
);
this.$saveToNoteButton.toggle(
note.mime === 'text/x-sqlite;schema=trilium'
&& !note.getAllNotePaths().find(notePathArr => !notePathArr.includes("hidden"))
);
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env='));
}
async noteTypeMimeChangedEvent({noteId}) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@@ -63,8 +63,6 @@ export default class EmptyTypeWidget extends TypeWidget {
appContext.tabManager.getActiveContext().setNote(suggestion.notePath);
});
noteAutocompleteService.showRecentNotes(this.$autoComplete);
this.$workspaceNotes = this.$widget.find('.workspace-notes');
super.doRender();
@@ -85,10 +83,8 @@ export default class EmptyTypeWidget extends TypeWidget {
);
}
if (workspaceNotes.length === 0) {
this.$autoComplete
.trigger('focus')
.trigger('select');
}
this.$autoComplete
.trigger('focus')
.trigger('select');
}
}

View File

@@ -53,11 +53,12 @@ const TPL = `
padding: 5px;
cursor: pointer;
border: 1px solid transparent;
border-radius: 5px;
border-radius: var(--button-border-radius);
color: var(--button-text-color);
}
.edit-text-note-button:hover {
border-color: var(--main-border-color);
border-color: var(--button-border-color);
}
</style>

Binary file not shown.

View File

@@ -1,6 +1,13 @@
@font-face {
font-family: MontserratLight;
font-family: Montserrat;
src: url(../fonts/Montserrat-Light.ttf);
font-weight: normal;
}
@font-face {
font-family: Montserrat;
src: url(../fonts/Montserrat-Regular.ttf);
font-weight: bold;
}
@font-face {
@@ -102,7 +109,6 @@ button.close:hover {
.icon-action {
border: 1px solid transparent;
border-radius: 3px;
padding: 5px;
width: 35px;
height: 35px;
@@ -110,6 +116,7 @@ button.close:hover {
font-size: 1.5em;
color: var(--button-text-color);
background: var(--button-background-color);
border-radius: var(--button-border-radius);
}
.icon-action:hover:not(.disabled) {
@@ -740,7 +747,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
}
.dropdown-submenu {
position:relative;
position: relative;
}
.dropdown-submenu > .dropdown-menu {
@@ -748,6 +755,9 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
left: 100%;
margin-top: -6px;
min-width: 15rem;
/* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */
max-height: 600px;
overflow: auto;
}
/* rotate caret on hover */
@@ -947,3 +957,7 @@ button.close:hover {
text-shadow: none;
color: currentColor;
}
.hidden-no-content {
display: none;
}

View File

@@ -1,13 +1,13 @@
:root {
--theme-style: dark;
--main-font-family: MontserratLight;
--main-font-family: Montserrat;
--main-font-size: normal;
--tree-font-family: MontserratLight;
--tree-font-family: Montserrat;
--tree-font-size: normal;
--detail-font-family: MontserratLight;
--detail-font-family: Montserrat;
--detail-font-size: normal;
--monospace-font-family: JetBrainsLight;

View File

@@ -5,13 +5,13 @@ html {
/* either light or dark, colored theme with darker tones are also dark, used e.g. for note map node colors */
--theme-style: light;
--main-font-family: MontserratLight;
--main-font-family: Montserrat;
--main-font-size: normal;
--tree-font-family: MontserratLight;
--tree-font-family: Montserrat;
--tree-font-size: normal;
--detail-font-family: MontserratLight;
--detail-font-family: Montserrat;
--detail-font-size: normal;
--monospace-font-family: JetBrainsLight;
@@ -73,4 +73,4 @@ html {
--link-color: blue;
--mermaid-theme: default;
}
}

View File

@@ -299,7 +299,7 @@ function getBacklinks(req) {
let backlinksWithExcerptCount = 0;
return backlinks.map(backlink => {
return backlinks.filter(note => !note.getNote().hasLabel('excludeFromNoteMap')).map(backlink => {
const sourceNote = backlink.note;
if (sourceNote.type !== 'text' || backlinksWithExcerptCount > 50) {

View File

@@ -7,7 +7,6 @@ const utils = require('../../services/utils');
const log = require('../../services/log');
const TaskContext = require('../../services/task_context');
const fs = require('fs');
const noteRevisionService = require("../../services/note_revisions");
const becca = require("../../becca/becca");
function getNote(req) {
@@ -182,6 +181,7 @@ function getRelationMap(req) {
if (def.inverseRelation) {
resp.inverseRelations[relationDefinition.getDefinedName()] = def.inverseRelation;
resp.inverseRelations[def.inverseRelation] = relationDefinition.getDefinedName();
}
}
}
@@ -315,7 +315,7 @@ function getBacklinkCount(req) {
}
else {
return {
count: note.getTargetRelations().length
count: note.getTargetRelations().filter(note => !note.getNote().hasLabel('excludeFromNoteMap')).length
};
}
}

View File

@@ -6,6 +6,7 @@ const log = require('../../services/log');
const scriptService = require('../../services/script');
const searchService = require('../../services/search/services/search');
const bulkActionService = require("../../services/bulk_actions");
const cls = require("../../services/cls");
const {formatAttrForSearch} = require("../../services/attribute_formatter");
function searchFromNoteInt(note) {
@@ -189,7 +190,9 @@ function getRelatedNotes(req) {
}
function searchTemplates() {
const query = formatAttrForSearch({type: 'label', name: "template"}, false);
const query = cls.getHoistedNoteId() === 'root'
? '#template'
: '#template OR #workspaceTemplate';
return searchService.searchNotes(query, {
includeArchivedNotes: true,

View File

@@ -215,7 +215,7 @@ function update(req) {
setInterval(() => {
for (const key in partialRequests) {
if (Date.now() - partialRequests[key].createdAt > 5 * 60 * 1000) {
if (Date.now() - partialRequests[key].createdAt > 20 * 60 * 1000) {
log.info(`Cleaning up unfinished partial requests for ${key}`);
delete partialRequests[key];

View File

@@ -10,7 +10,7 @@ function getNotesAndBranchesAndAttributes(noteIds) {
const collectedBranchIds = new Set();
function collectEntityIds(note) {
if (collectedNoteIds.has(note.noteId)) {
if (!note || collectedNoteIds.has(note.noteId)) {
return;
}
@@ -97,6 +97,11 @@ function getNotesAndBranchesAndAttributes(noteIds) {
for (const attributeId of collectedAttributeIds) {
const attribute = becca.attributes[attributeId];
if (!attribute) {
log.error(`Could not find attribute for attributeId=${attributeId}`);
continue;
}
attributes.push({
attributeId: attribute.attributeId,
noteId: attribute.noteId,

View File

@@ -4,7 +4,7 @@ const setupRoute = require('./setup');
const loginRoute = require('./login');
const indexRoute = require('./index');
const utils = require('../services/utils');
const multer = require('multer')();
const multer = require('multer');
// API routes
const treeApiRoute = require('./api/tree');
@@ -194,8 +194,33 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
});
}
const MAX_ALLOWED_FILE_SIZE_MB = 250;
const GET = 'get', POST = 'post', PUT = 'put', PATCH = 'patch', DELETE = 'delete';
const uploadMiddleware = multer.single('upload');
const uploadMiddleware = multer({
fileFilter: (req, file, cb) => {
// UTF-8 file names are not well decoded by multer/busboy, so we handle the conversion on our side.
// See https://github.com/expressjs/multer/pull/1102.
file.originalname = Buffer.from(file.originalname, "latin1").toString("utf-8");
cb(null, true);
},
limits: {
fileSize: MAX_ALLOWED_FILE_SIZE_MB * 1024 * 1024
}
}).single('upload');
const uploadMiddlewareWithErrorHandling = function (req, res, next) {
uploadMiddleware(req, res, function (err) {
if (err?.code === 'LIMIT_FILE_SIZE') {
res.setHeader("Content-Type", "text/plain")
.status(400)
.send(`Cannot upload file because it excceeded max allowed file size of ${MAX_ALLOWED_FILE_SIZE_MB} MiB`);
}
else {
next();
}
});
};
function register(app) {
route(GET, '/', [auth.checkAuth, csrfMiddleware], indexRoute.index);
@@ -253,9 +278,9 @@ function register(app) {
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], importRoute.importToBranch, apiResultHandler);
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], importRoute.importToBranch, apiResultHandler);
route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware],
route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware],
filesRoute.updateFile, apiResultHandler);
route(GET, '/api/notes/:noteId/open', [auth.checkApiAuthOrElectron], filesRoute.openFile);
@@ -294,10 +319,10 @@ function register(app) {
apiRoute(POST, '/api/special-notes/search-note', specialNotesRoute.createSearchNote);
apiRoute(POST, '/api/special-notes/save-search-note', specialNotesRoute.saveSearchNote);
// :filename is not used by trilium, but instead used for "save as" to assign a human readable filename
// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], imageRoute.uploadImage, apiResultHandler);
route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.uploadImage, apiResultHandler);
route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
apiRoute(GET, '/api/recent-changes/:ancestorNoteId', recentChangesApiRoute.getRecentChanges);
@@ -359,7 +384,7 @@ function register(app) {
// no CSRF since this is called from android app
route(POST, '/api/sender/login', [], loginApiRoute.token, apiResultHandler);
route(POST, '/api/sender/image', [auth.checkEtapiToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler);
route(POST, '/api/sender/image', [auth.checkEtapiToken, uploadMiddlewareWithErrorHandling], senderRoute.uploadImage, apiResultHandler);
route(POST, '/api/sender/note', [auth.checkEtapiToken], senderRoute.saveNote, apiResultHandler);
apiRoute(GET, '/api/quick-search/:searchString', searchRoute.quickSearch);
@@ -409,7 +434,7 @@ function register(app) {
shareRoutes.register(router);
etapiAuthRoutes.register(router);
etapiAuthRoutes.register(router, [loginRateLimiter]);
etapiAppInfoRoutes.register(router);
etapiAttributeRoutes.register(router);
etapiBranchRoutes.register(router);

View File

@@ -14,6 +14,8 @@ const appInfo = require('./app_info');
const searchService = require('./search/services/search');
const SearchContext = require("./search/search_context");
const becca = require("../becca/becca");
const ws = require("./ws");
const SpacedUpdate = require("./spaced_update");
/**
* This is the main backend API interface for scripts. It's published in the local "api" object.
@@ -288,12 +290,34 @@ function BackendScriptApi(currentNote, apiParams) {
});
};
this.logMessages = {};
this.logSpacedUpdates = {};
/**
* Log given message to trilium logs.
* Log given message to trilium logs and log pane in UI
*
* @param message
*/
this.log = message => log.info(message);
this.log = message => {
log.info(message);
const {noteId} = this.startNote;
this.logMessages[noteId] = this.logMessages[noteId] || [];
this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
const messages = this.logMessages[noteId];
this.logMessages[noteId] = [];
ws.sendMessageToAllClients({
type: 'api-log-messages',
noteId,
messages
});
}, 100);
this.logMessages[noteId].push(message);
this.logSpacedUpdates[noteId].scheduleUpdate();
};
/**
* Returns root note of the calendar.

View File

@@ -1 +1 @@
module.exports = { buildDate:"2022-08-02T21:56:34+02:00", buildRevision: "12af3d05f04b9406af55ac301c0069eb385957c8" };
module.exports = { buildDate:"2022-09-21T22:43:34+02:00", buildRevision: "180051d252d254a2543f8192a28e87e134594a38" };

View File

@@ -30,6 +30,7 @@ module.exports = [
{ type: 'label', name: 'workspaceIconClass' },
{ type: 'label', name: 'workspaceTabBackgroundColor' },
{ type: 'label', name: 'workspaceCalendarRoot' },
{ type: 'label', name: 'workspaceTemplate' },
{ type: 'label', name: 'searchHome' },
{ type: 'label', name: 'hoistedInbox' },
{ type: 'label', name: 'hoistedSearchHome' },
@@ -41,12 +42,15 @@ module.exports = [
{ type: 'label', name: 'bookmarked' },
{ type: 'label', name: 'bookmarkFolder' },
{ type: 'label', name: 'sorted' },
{ type: 'label', name: 'sortDirection' },
{ type: 'label', name: 'sortFoldersFirst' },
{ type: 'label', name: 'top' },
{ type: 'label', name: 'fullContentWidth' },
{ type: 'label', name: 'shareHiddenFromTree' },
{ type: 'label', name: 'shareAlias' },
{ type: 'label', name: 'shareOmitDefaultCss' },
{ type: 'label', name: 'shareRoot' },
{ type: 'label', name: 'shareDescription' },
{ type: 'label', name: 'shareRaw' },
{ type: 'label', name: 'shareDisallowRobotIndexing' },
{ type: 'label', name: 'shareCredentials' },

View File

@@ -44,7 +44,7 @@ eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETE
if (entityName === 'attributes') {
runAttachedRelations(entity.getNote(), 'runOnAttributeChange', entity);
if (entity.type === 'label' && entity.name === 'sorted') {
if (entity.type === 'label' && ['sorted', 'sortDirection', 'sortFoldersFirst'].includes(entity.name)) {
handleSortedAttribute(entity);
}
}

View File

@@ -1,10 +1,3 @@
const config = require('./config');
const env = require('./env');
let environmentHost;
if (process.env.TRILIUM_HOST) {
environmentHost = process.env.TRILIUM_HOST;
}
module.exports = Promise.resolve(environmentHost || config['Network']['host'] || '0.0.0.0');
module.exports = process.env.TRILIUM_HOST || config['Network']['host'] || '0.0.0.0';

View File

@@ -292,7 +292,12 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
content = content.replace(/<\/body>.*<\/html>/gis, "");
content = content.replace(/src="([^"]*)"/g, (match, url) => {
url = decodeURIComponent(url);
try {
url = decodeURIComponent(url);
} catch (e) {
log.error(`Cannot parse image URL '${url}', keeping original (${e}).`);
return `src="${url}"`;
}
if (isUrlAbsolute(url) || url.startsWith("/")) {
return match;
@@ -304,7 +309,12 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
});
content = content.replace(/href="([^"]*)"/g, (match, url) => {
url = decodeURIComponent(url);
try {
url = decodeURIComponent(url);
} catch (e) {
log.error(`Cannot parse link URL '${url}', keeping original (${e}).`);
return `href="${url}"`;
}
if (isUrlAbsolute(url)) {
return match;

View File

@@ -735,6 +735,10 @@ function eraseDeletedEntities(eraseEntitiesAfterTimeInSeconds = null) {
const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateTimeStr(cutoffDate)]);
eraseAttributes(attributeIdsToErase);
if (noteIdsToErase.length > 0 || branchIdsToErase.length > 0 || attributeIdsToErase.length > 0) {
require('../becca/becca_loader').reload();
}
});
}
@@ -750,6 +754,10 @@ function eraseNotesWithDeleteId(deleteId) {
const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE deleteId = ?", [deleteId]);
eraseAttributes(attributeIdsToErase);
if (noteIdsToErase.length > 0 || branchIdsToErase.length > 0 || attributeIdsToErase.length > 0) {
require('../becca/becca_loader').reload();
}
}
function eraseDeletedNotesNow() {

View File

@@ -1,28 +1,15 @@
const config = require('./config');
const utils = require('./utils');
const env = require('./env');
const portscanner = require('portscanner');
let environmentPort;
if (process.env.TRILIUM_PORT) {
environmentPort = parseInt(process.env.TRILIUM_PORT);
module.exports = parseInt(process.env.TRILIUM_PORT);
return;
}
if (utils.isElectron()) {
module.exports = new Promise((resolve, reject) => {
const startingPort = environmentPort || (env.isDev() ? 37740 : 37840);
portscanner.findAPortNotInUse(startingPort, startingPort + 10, '127.0.0.1', function(error, port) {
if (error) {
reject(error);
}
else {
resolve(port);
}
})
});
module.exports = env.isDev() ? 37740 : 37840;
}
else {
module.exports = Promise.resolve(environmentPort || config['Network']['port'] || '3000');
}
module.exports = config['Network']['port'] || '3000';
}

View File

@@ -28,7 +28,7 @@ class AttributeExistsExp extends Expression {
}
else if (note.isTemplate() &&
// template attr is used as a marker for templates, but it's not meant to be inherited
!(this.attributeType === 'label' && this.attributeName === 'template')) {
!(this.attributeType === 'label' && (this.attributeName === 'template' || this.attributeName === 'workspacetemplate'))) {
resultNoteSet.addAll(note.getTemplatedNotes());
}
else {

View File

@@ -39,8 +39,12 @@ function getFulltext(tokens, searchContext) {
}
}
function isOperator(str) {
return str.match(/^[!=<>*%]+$/);
function isOperator(token) {
if (Array.isArray(token)) {
return false;
}
return token.token.match(/^[!=<>*%]+$/);
}
function getExpression(tokens, searchContext, level = 0) {
@@ -129,16 +133,16 @@ function getExpression(tokens, searchContext, level = 0) {
i += 1;
const operator = tokens[i].token;
const operator = tokens[i];
if (!isOperator(operator)) {
searchContext.addError(`After content expected operator, but got "${tokens[i].token}" in ${context(i)}`);
searchContext.addError(`After content expected operator, but got "${operator.token}" in ${context(i)}`);
return;
}
i++;
return new NoteContentFulltextExp(operator, {tokens: [tokens[i].token], raw });
return new NoteContentFulltextExp(operator.token, {tokens: [tokens[i].token], raw });
}
if (tokens[i].token === 'parents') {
@@ -228,7 +232,7 @@ function getExpression(tokens, searchContext, level = 0) {
function parseLabel(labelName) {
searchContext.highlightedTokens.push(labelName);
if (i < tokens.length - 2 && isOperator(tokens[i + 1].token)) {
if (i < tokens.length - 2 && isOperator(tokens[i + 1])) {
let operator = tokens[i + 1].token;
i += 2;
@@ -265,7 +269,7 @@ function getExpression(tokens, searchContext, level = 0) {
return new RelationWhereExp(relationName, parseNoteProperty());
}
else if (i < tokens.length - 2 && isOperator(tokens[i + 1].token)) {
else if (i < tokens.length - 2 && isOperator(tokens[i + 1])) {
searchContext.addError(`Relation can be compared only with property, e.g. ~relation.title=hello in ${context(i)}`);
return null;
@@ -384,7 +388,7 @@ function getExpression(tokens, searchContext, level = 0) {
searchContext.addError('Mixed usage of AND/OR - always use parenthesis to group AND/OR expressions.');
}
}
else if (isOperator(token)) {
else if (isOperator({token: token})) {
searchContext.addError(`Misplaced or incomplete expression "${token}"`);
}
else {

View File

@@ -0,0 +1,67 @@
class SpacedUpdate {
constructor(updater, updateInterval = 1000) {
this.updater = updater;
this.lastUpdated = Date.now();
this.changed = false;
this.updateInterval = updateInterval;
}
scheduleUpdate() {
if (!this.changeForbidden) {
this.changed = true;
setTimeout(() => this.triggerUpdate());
}
}
async updateNowIfNecessary() {
if (this.changed) {
this.changed = false; // optimistic...
try {
await this.updater();
}
catch (e) {
this.changed = true;
throw e;
}
}
}
isAllSavedAndTriggerUpdate() {
const allSaved = !this.changed;
this.updateNowIfNecessary();
return allSaved;
}
triggerUpdate() {
if (!this.changed) {
return;
}
if (Date.now() - this.lastUpdated > this.updateInterval) {
this.updater();
this.lastUpdated = Date.now();
this.changed = false;
}
else {
// update not triggered but changes are still pending so we need to schedule another check
this.scheduleUpdate();
}
}
async allowUpdateWithoutChange(callback) {
this.changeForbidden = true;
try {
await callback();
}
finally {
this.changeForbidden = false;
}
}
}
module.exports = SpacedUpdate;

View File

@@ -33,7 +33,7 @@ function isDbInitialized() {
async function initDbConnection() {
if (!isDbInitialized()) {
log.info(`DB not initialized, please visit setup page` +
(utils.isElectron() ? '' : ` - http://[your-server-host]:${await port} to see instructions on how to initialize Trilium.`));
(utils.isElectron() ? '' : ` - http://[your-server-host]:${port} to see instructions on how to initialize Trilium.`));
return;
}

View File

@@ -62,6 +62,7 @@ const updateTrayMenu = () => {
mainWindow.hide();
} else {
mainWindow.show();
mainWindow.focus();
}
}
},

View File

@@ -204,7 +204,11 @@ function sortNotesIfNeeded(parentNoteId) {
return;
}
sortNotes(parentNoteId, sortedLabel.value);
const sortReversed = parentNote.getLabelValue('sortDirection')?.toLowerCase() === "desc";
const sortFoldersFirstLabel = parentNote.getLabel('sortFoldersFirst');
const sortFoldersFirst = sortFoldersFirstLabel && sortFoldersFirstLabel.value.toLowerCase() !== "false";
sortNotes(parentNoteId, sortedLabel.value, sortReversed, sortFoldersFirst);
}
/**

View File

@@ -35,7 +35,7 @@ async function createExtraWindow(notePath, hoistedNoteId = 'root') {
});
win.setMenuBarVisibility(false);
win.loadURL('http://127.0.0.1:' + await port + '/?extra=1&extraHoistedNoteId=' + hoistedNoteId + '#' + notePath);
win.loadURL(`http://127.0.0.1:${port}/?extra=1&extraHoistedNoteId=${hoistedNoteId}#${notePath}`);
configureWebContents(win.webContents, spellcheckEnabled);
}
@@ -44,7 +44,7 @@ ipcMain.on('create-extra-window', (event, arg) => {
createExtraWindow(arg.notePath, arg.hoistedNoteId);
});
async function createMainWindow() {
async function createMainWindow(app) {
const windowStateKeeper = require('electron-window-state'); // should not be statically imported
const mainWindowState = windowStateKeeper({
@@ -77,10 +77,22 @@ async function createMainWindow() {
mainWindowState.manage(mainWindow);
mainWindow.setMenuBarVisibility(false);
mainWindow.loadURL('http://127.0.0.1:' + await port);
mainWindow.loadURL('http://127.0.0.1:' + port);
mainWindow.on('closed', () => mainWindow = null);
configureWebContents(mainWindow.webContents, spellcheckEnabled);
app.on('second-instance', () => {
// Someone tried to run a second instance, we should focus our window.
// see www.js "requestSingleInstanceLock" for the rest of this logic with explanation
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus();
}
});
}
function configureWebContents(webContents, spellcheckEnabled) {
@@ -131,7 +143,7 @@ async function createSetupWindow() {
});
setupWindow.setMenuBarVisibility(false);
setupWindow.loadURL('http://127.0.0.1:' + await port);
setupWindow.loadURL('http://127.0.0.1:' + port);
setupWindow.on('closed', () => setupWindow = null);
}

View File

@@ -67,7 +67,7 @@ function sendMessageToAllClients(message) {
const jsonStr = JSON.stringify(message);
if (webSocketServer) {
if (message.type !== 'sync-failed') {
if (message.type !== 'sync-failed' && message.type !== 'api-log-messages') {
log.info("Sending message to all clients: " + jsonStr);
}

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<link rel="manifest" href="manifest.webmanifest">
<title>Trilium Notes</title>
</head>
<body class="desktop heading-style-<%= headingStyle %>">

View File

@@ -2,6 +2,10 @@
<html lang="en">
<head>
<meta charset="utf-8">
<% if (note.hasLabel("shareDescription")) { %>
<meta name="description" content="<%= note.getLabelValue("shareDescription") %>">
<% } %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<% if (note.hasRelation("shareFavicon")) { %>
<link rel="shortcut icon" href="api/notes/<%= note.getRelation("shareFavicon").value %>/download">

40
src/www
View File

@@ -38,11 +38,33 @@ if (!semver.satisfies(process.version, ">=10.5.0")) {
let httpServer;
async function startTrilium() {
const usedPort = await port;
const usedHost = await host;
/**
* The intended behavior is to detect when a second instance is running, in that case open the old instance
* instead of the new one. This is complicated by the fact that it is possible to run multiple instances of Trilium
* if port and data dir is configured separately. This complication is the source of the following weird usage.
*
* The line below makes sure that the "second-instance" (process in window.js) is fired. Normally it returns a boolean
* indicating whether another instance is running or not, but we ignore that and kill the app only based on the port conflict.
*
* A bit weird is that "second-instance" is triggered also on the valid usecases (different port/data dir) and
* focuses the existing window. But the new process is start as well and will steal the focus too, it will win, because
* its startup is slower than focusing the existing process/window. So in the end it works out without having
* to do complex evaluation.
*/
if (utils.isElectron()) {
require("electron").app.requestSingleInstanceLock();
}
app.set('port', usedPort);
app.set('host', usedHost);
app.set('port', port);
app.set('host', host);
// Check from config whether to trust reverse proxies to supply user IPs, hostnames and protocols
if (config['Network']['trustedReverseProxy']) {
if (config['Network']['trustedReverseProxy'] === true || config['Network']['trustedReverseProxy'].trim().length) {
app.set('trust proxy', config['Network']['trustedReverseProxy'])
}
}
log.info('Trusted reverse proxy: ' + app.get('trust proxy'))
if (config['Network']['https']) {
if (!config['Network']['keyPath'] || !config['Network']['keyPath'].trim().length) {
@@ -60,12 +82,12 @@ async function startTrilium() {
httpServer = https.createServer(options, app);
log.info("App HTTPS server starting up at port " + usedPort);
log.info("App HTTPS server starting up at port " + port);
}
else {
httpServer = http.createServer(app);
log.info("App HTTP server starting up at port " + usedPort);
log.info("App HTTP server starting up at port " + port);
}
log.info(JSON.stringify(appInfo, null, 2));
@@ -78,7 +100,7 @@ async function startTrilium() {
*/
httpServer.keepAliveTimeout = 120000 * 5;
httpServer.listen(usedPort, usedHost);
httpServer.listen(port, host);
httpServer.on('error', error => {
if (error.syscall !== 'listen') {
throw error;
@@ -87,12 +109,12 @@ async function startTrilium() {
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(`Port ${usedPort} requires elevated privileges`);
console.error(`Port ${port} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`Port ${usedPort} is already in use`);
console.error(`Port ${port} is already in use`);
process.exit(1);
break;

View File

@@ -0,0 +1,25 @@
POST {{triliumHost}}/etapi/create-note
Authorization: {{authToken}}
Content-Type: application/json
{
"parentNoteId": "root",
"title": "Hello",
"type": "text",
"content": "Hi there!"
}
> {%
client.global.set("createdNoteId", response.body.note.noteId);
client.global.set("createdBranchId", response.body.branch.branchId);
%}
###
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
Authorization: {{authToken}}
> {%
client.assert(response.status === 200);
client.assert(response.body === "<p>Hi there!</p>");
%}