Compare commits

...

56 Commits

Author SHA1 Message Date
zadam
6f60cf1a86 release 0.48.5 2021-11-01 09:19:28 +01:00
zadam
930d29d64a add hack to produce debian compatible XZ compressed deb packages, fixes #2272 2021-11-01 09:11:04 +01:00
zadam
c345e7031b fix hoisting note from empty detail screen 2021-11-01 08:50:01 +01:00
zadam
fc46398a3c close last tab should behave similarly to 0.47, fixes #2298 2021-11-01 08:37:59 +01:00
zadam
7e41226549 fix "XSS" in the new empty tab, closes #2145 2021-10-31 23:12:24 +01:00
zadam
b0c0ed8512 add configurable max content width with default value of 1200px. 2021-10-31 21:55:11 +01:00
zadam
5978447185 add back .isDeleted property to becca's note, branch and attribute entities for BC, fixes #2268 2021-10-31 21:03:26 +01:00
zadam
ec4d445f97 make sure we're not using electron before enabling mobile version 2021-10-31 20:25:54 +01:00
zadam
e378435fbe added "switch to mobile version" button to web edition, closes #1889 2021-10-31 16:56:23 +01:00
zadam
e4dca4750f fix overriding monospace font size, closes #2296 2021-10-31 16:48:21 +01:00
zadam
311b98ffcb fix immediately closed attribute detail after clicking e.g. "add label" 2021-10-30 15:36:50 +02:00
zadam
126f41ae5e fix broken autocomplete in attribute editor, closes #2295 2021-10-30 15:22:20 +02:00
zadam
4b1678c416 fix sizing of the attribute detail in inherited attribute list 2021-10-29 21:47:04 +02:00
zadam
da74272f13 jsdoc @param => @type 2021-10-29 21:37:12 +02:00
zadam
9ce224d4c5 fix correct number of related attributes 2021-10-29 21:36:57 +02:00
zadam
ce57a13002 fix infinite recursion, closes #2292 2021-10-29 21:36:23 +02:00
zadam
e42357f1f8 release 0.48.4 2021-10-28 22:28:52 +02:00
zadam
ab7d121290 disallow removing last tab because of frequent race conditions when done too quickly 2021-10-28 22:27:21 +02:00
zadam
5209583a73 new note is in decrypted state even if protected, fixes #2291 2021-10-28 22:02:18 +02:00
zadam
35fc4ba9a4 fix wrapping of code blocks in note revisions, #2216 2021-10-28 20:38:35 +02:00
zadam
7f9019322b after creation of inverse relation we need to clear the cache, otherwise inverse relations will be created infinitely, fixes #2269 2021-10-27 22:36:33 +02:00
zadam
8a455e83f0 fix creating inverse relations 2021-10-27 22:28:33 +02:00
zadam
79b8d91025 validate/clean inverse relation name in promoted attr definition 2021-10-27 22:13:54 +02:00
zadam
bcabe5786f fix ordering of note contexts, fixes #2283 2021-10-27 21:32:03 +02:00
Abitofevrything
a7d3dafcf1 Remove logging line (#2284) 2021-10-27 19:45:59 +02:00
zadam
e6af84df39 fix updating "lastAcceptedEntityId" - can't take last entityChange since they might be reordered based on update order, closes #2277 2021-10-26 22:07:35 +02:00
zadam
674172f0b8 fix not updating attribute if not needed 2021-10-26 20:57:45 +02:00
zadam
7ec20f9384 fix refreshing ribbon titles upon entity change, closes #2274 2021-10-26 20:14:56 +02:00
zadam
6135de8507 Merge remote-tracking branch 'origin/master' 2021-10-25 21:59:34 +02:00
zadam
63b0d30e74 fix note status update, closes #2276 2021-10-25 21:59:21 +02:00
MeIchthys
9e29fba8da Update "Sync from Desktop" setup instructions. (#2271) 2021-10-25 21:15:17 +02:00
zadam
8910ae92c7 increasing amount of text rendered in note previous to 2000 (previously 1000) bytes, #2266 2021-10-25 20:01:41 +02:00
zadam
3413074235 when saving Saved Search or SQL console, move it to their home (i.e. remove from hidden subtree) 2021-10-24 14:53:45 +02:00
zadam
33aa72eb97 fix finding note paths of hidden notes, fixes #2262 2021-10-24 14:37:41 +02:00
zadam
6e0a65b59c release 0.48.3 2021-10-22 21:31:44 +02:00
zadam
56e49cfc19 set iconClass also on whole page clipping #2250 2021-10-21 23:02:22 +02:00
zadam
af40d73cee fix saving images from web clipper, closes #2249 2021-10-21 22:58:52 +02:00
zadam
50b7063811 display properly relations in note map even if they are not in the subtree, closes #2251 2021-10-21 22:52:52 +02:00
zadam
ee1b377bc2 deactivate some buttons based on note type 2021-10-21 21:28:44 +02:00
zadam
51dfe8bb14 avoid double rendering of note paths 2021-10-21 21:10:55 +02:00
zadam
e426ee3e4f disallow changing note type of note-map 2021-10-21 21:02:37 +02:00
zadam
a434aa113d fix repeated invocation of actions in note actions dropdown, closes #2255 2021-10-21 20:53:35 +02:00
Sathyam M Vellal
3b551e3e4d Add week notes to frontend_script_api (#2253)
Note: getWeekNote can take `startOfTheWeek` as options but is not passed to the api route.
2021-10-21 12:16:51 +02:00
zadam
3d98644bf6 release 0.48.2 2021-10-20 22:12:20 +02:00
zadam
a22e4d60b6 Merge remote-tracking branch 'origin/master' 2021-10-20 21:30:39 +02:00
zadam
069fbee3a6 allow async scripts in runOnBackend(), fixes #2243 2021-10-20 21:27:47 +02:00
Matt
8b21867c5c Set default icon for clipped notes (#2246)
* Set default icon for clipped notes

I like being able to differentiate between clipped notes and regular notes

* Globe
2021-10-20 08:57:05 +02:00
zadam
2cc4367b37 fix restoring note revision, closes #2232 2021-10-15 21:37:16 +02:00
zadam
241d1b1035 node 14.18.1 2021-10-14 22:22:27 +02:00
zadam
449081807e fix backend API's getNote, getBranch, getAttribute, closes #2230 2021-10-14 21:19:03 +02:00
zadam
dc0d6d24bd Merge remote-tracking branch 'origin/master' 2021-10-14 21:09:32 +02:00
zadam
4111a2f0e8 docker image build/push is now handled by github actions 2021-10-14 20:49:48 +02:00
TrAnn3l
8ef4b2bf50 Add tag based multi arch docker build. (#2229) 2021-10-14 20:42:27 +02:00
zadam
53875d26bc don't try to activate the pane being currently removed 2021-10-13 23:27:58 +02:00
zadam
f505f9d65a hide tooltip after clicking on a button, fixes #2228 2021-10-13 23:05:47 +02:00
zadam
6434889cd6 added SVG icon 2021-10-13 19:54:26 +02:00
79 changed files with 761 additions and 333 deletions

53
.github/workflows/docker.yaml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Publish Docker image
on:
push:
tags: [v*]
jobs:
push_to_registries:
name: Push Docker image to multiple registries
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: |
zadam/trilium
ghcr.io/zadam/trilium
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}-latest
type=match,pattern=(\d+.\d+).\d+\-beta,enable=${{ endsWith(github.ref, 'beta') }},group=1,suffix=-latest
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
install: true
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to GitHub Docker Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Build and Push
uses: docker/build-push-action@v2.7.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
push: true
cache-from: type=registry,ref=zadam/trilium:buildcache
cache-to: type=registry,ref=zadam/trilium:buildcache,mode=max
tags: ${{ steps.meta.outputs.tags }}

View File

@@ -2,10 +2,10 @@ image:
file: .gitpod.dockerfile
tasks:
- before: nvm install 14.17.6 && nvm use 14.17.6
- before: nvm install 14.18.1 && nvm use 14.18.1
init: npm install
command: npm run start-server
ports:
- port: 8080
onOpen: open-preview
onOpen: open-preview

View File

@@ -1,4 +1,4 @@
FROM node:14.17.6-alpine
FROM node:14.18.1-alpine
# Create app directory
WORKDIR /usr/src/app

View File

@@ -4,4 +4,19 @@ echo "Packaging debian x64 distribution..."
VERSION=`jq -r ".version" package.json`
./node_modules/.bin/electron-installer-debian --config bin/deb-options.json --options.version=${VERSION} --arch amd64
./node_modules/.bin/electron-installer-debian --config bin/deb-options.json --options.version=${VERSION} --arch amd64
# hacky stop-gag measure to produce debian compatible XZ compressed debs until this is fixed: https://github.com/electron-userland/electron-installer-debian/issues/272
cd dist
ar x trilium_${VERSION}_amd64.deb
rm trilium_${VERSION}_amd64.deb
# recompress
< control.tar.zst zstd -d | xz > control.tar.xz
< data.tar.zst zstd -d | xz > data.tar.xz
# create deb archive (I really do not know, what argument "sdsd" is for but something is required for ar to create the archive as desired)
ar -m -c -a sdsd trilium_${VERSION}_amd64.deb debian-binary control.tar.xz data.tar.xz
rm control* data* debian-binary
echo "Converted to XZ deb"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
PKG_DIR=dist/trilium-linux-x64-server
NODE_VERSION=14.17.6
NODE_VERSION=14.18.1
if [ "$1" != "DONTCOPY" ]
then

View File

@@ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then
exit 1
fi
n exec 14.17.6 npm run webpack
n exec 14.18.1 npm run webpack
DIR=$1
@@ -27,7 +27,7 @@ cp -r electron.js $DIR/
cp webpack-* $DIR/
# run in subshell (so we return to original dir)
(cd $DIR && n exec 14.17.6 npm install --only=prod)
(cd $DIR && n exec 14.18.1 npm install --only=prod)
# cleanup of useless files in dependencies
rm -r $DIR/node_modules/image-q/demo

View File

@@ -69,13 +69,3 @@ gh release create "$TAG" \
"dist/$WINDOWS_X64_BUILD" \
"dist/$MAC_X64_BUILD" \
"dist/$SERVER_BUILD"
echo "Building docker image"
bin/build-docker.sh $VERSION
echo "Pushing docker image to dockerhub"
bin/push-docker-image.sh $VERSION
echo "Release finished!"

67
images/icon.svg Normal file
View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg2163"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
sodipodi:docname="cropped.svg">
<defs
id="defs2157" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.5099157"
inkscape:cx="386.95033"
inkscape:cy="314.49555"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1245"
inkscape:window-height="846"
inkscape:window-x="60"
inkscape:window-y="0"
inkscape:window-maximized="0" />
<metadata
id="metadata2160">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1">
<path
d="m 139.19968,46.483949 c -5.40456,-0.16722 -8.20491,2.3622 -8.88118,2.5968 0.61524,-1.16523 1.02694,-1.89936 2.60032,-3.53766 -5.35164,-0.0561 -10.31381,1.10208 -14.09982,4.64397 -5.07788,4.75015 -4.68312,8.68574 -4.69335,8.66634 0.001,0.002 -0.80116,-2.56646 -1.04176,-5.31354 -5.52591,7.64928 -7.70784,17.78459 -3.51825,25.34532 2.60068,-6.2738 6.73029,-11.68083 12.0523,-15.9445 0.17533,-4.47393 1.9752,-8.25394 4.82,-11.04477 -2.56716,3.0727 -3.69993,6.57331 -3.58563,10.10144 6.19019,-4.61821 13.77738,-7.6962 22.11141,-8.7062 -6.03391,1.10878 -11.50091,3.51367 -16.27929,6.7938 2.99085,1.36595 6.40645,1.58009 9.99631,0.32984 -3.4417,1.6129 -7.26228,1.87678 -11.12731,0.47202 -3.43041,2.51213 -6.48124,5.4804 -9.10343,8.74254 4.65525,1.52188 9.74725,1.2072 14.81455,-1.37548 -4.65173,3.00461 -10.11661,4.13138 -15.95614,2.85185 -2.32798,3.1242 -4.2792,6.47983 -5.80426,9.93811 1.87855,1.385 10.03089,5.73052 24.01465,-4.58153 -1.9745,-0.58349 -3.5747,-1.40864 -3.57541,-1.41005 0.0445,0.0198 1.4411,0.14817 5.52556,-2.2987 3.61104,-2.11173 6.71724,-5.9369 9.0103,-8.91434 -1.95051,-0.71861 -3.46957,-1.47285 -3.47028,-1.47461 -0.001,-10e-4 2.5206,0.16968 6.4322,-2.8247 3.48897,-2.67088 7.38399,-6.54261 7.39599,-6.43325 0.0247,0.0233 -8.59226,-6.3433 -17.63748,-6.6227"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
id="path1983" />
<path
d="m 76.908921,60.394149 c 4.10149,-1.19228 6.7131,0.18422 7.27437,0.22948 -0.68748,-0.76706 -1.13907,-1.24548 -2.64709,-2.18406 4.08133,-1.09739 8.09089,-1.19268 11.64521,0.76111 4.7672,2.6204 5.19783,5.69818 5.20227,5.68151 -8.1e-4,0.002 0.13467,-2.11419 -0.19274,-4.25578 5.649429,4.74214 9.204969,12.03797 7.409049,18.6271 -3.15635,-4.27007 -7.320749,-7.57773 -12.183899,-9.77927 -0.96691,-3.37555 -3.04709,-5.90265 -5.74177,-7.46934 2.535,1.83627 4.0527,4.28158 4.62244,6.9933 -5.59297,-2.30039 -11.96698,-3.15163 -18.52727,-2.27895 4.82002,-0.34383 15.0298,0.35455 23.31423,7.22913 -3.27571,2.07767 -7.22761,2.84155 -11.58272,1.87121 4.116,1.37333 8.50418,1.15576 12.73066,-0.97074 2.361629,1.92282 4.478499,4.09616 6.288529,6.4319 -1.1786,1.42613 -6.602629,6.34494 -19.214749,1.23993 1.40117,-0.83377 2.47089,-1.77831 2.47129,-1.7795 -0.0302,0.0238 -1.07415,0.39703 -4.65308,-0.66304 -3.15394,-0.89808 -6.24094,-3.20205 -8.54894,-5.02006 1.35762,-0.93183 2.37896,-1.80609 2.37896,-1.80768 3.9e-4,-8.1e-4 -1.89551,0.62612 -5.44379,-0.88538 -3.16522,-1.34871 -6.86431,-3.53238 -6.85302,-3.44662 -0.0145,0.0226 5.38855,-6.52877 12.25206,-8.52425"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.040011"
id="path1985" />
<path
d="m 106.35028,116.78676 c -2.99089,-2.78316 -3.11891,-5.61706 -3.35465,-6.10128 -0.30328,0.94728 -0.48119,1.55657 -0.5271,3.26459 -2.902599,-2.81363 -4.935969,-6.06629 -5.042799,-9.9638 -0.14303,-5.226251 2.20774,-7.111581 2.19185,-7.106711 0.002,0 -1.82455,0.94068 -3.44692,2.27518 1.19194,-7.00825 5.529279,-13.56279 11.887189,-15.36164 -2.01438,4.72154 -2.73705,9.79389 -2.19847,14.89152 -2.33709,2.474751 -3.42573,5.443121 -3.41602,8.440321 0.29225,-2.99941 1.58705,-5.46485 3.56567,-7.282331 0.81185,5.747411 3.21074,11.417161 7.13399,16.382681 -2.63507,-3.79648 -7.03025,-12.54682 -5.34828,-22.784661 3.32507,1.66359 5.88686,4.53616 7.20241,8.603531 -0.86349,-4.070921 -3.18336,-7.574601 -7.01259,-9.996131 0.44853,-2.89963 1.2255,-5.72253 2.28632,-8.37424 1.76099,0.26123 8.49766,2.27917 10.39682,15.19844 -1.37647,-0.73844 -2.68363,-1.14912 -2.68496,-1.14868 0.0349,0.0129 0.85422,0.68654 1.7164,4.159181 0.78977,3.04292 0.37789,6.72937 -0.0102,9.53311 -1.43739,-0.65417 -2.66244,-1.05988 -2.66332,-1.059 -0.001,0 1.44445,1.24891 1.91637,4.92099 0.42071,3.27585 0.40659,7.40616 0.47236,7.35418 0.026,8.4e-4 -8.0584,-1.18846 -13.06411,-5.84525"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0442482"
id="path1987" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

100
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "trilium",
"version": "0.48.0-beta",
"version": "0.48.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1606,16 +1606,16 @@
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
},
"browserslist": {
"version": "4.17.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.3.tgz",
"integrity": "sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ==",
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.4.tgz",
"integrity": "sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001264",
"electron-to-chromium": "^1.3.857",
"caniuse-lite": "^1.0.30001265",
"electron-to-chromium": "^1.3.867",
"escalade": "^3.1.1",
"node-releases": "^1.1.77",
"picocolors": "^0.2.1"
"node-releases": "^2.0.0",
"picocolors": "^1.0.0"
}
},
"buffer": {
@@ -2188,11 +2188,6 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w=="
},
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
@@ -2855,9 +2850,9 @@
}
},
"electron": {
"version": "13.5.2",
"resolved": "https://registry.npmjs.org/electron/-/electron-13.5.2.tgz",
"integrity": "sha512-CPakwDpy5m8dL0383F5uJboQcVtn9bT/+6/wdDKo8LuTUO9aER1TF41v7feZgZW2c+UwoGPWa814ElSQ3qta2A==",
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-13.6.1.tgz",
"integrity": "sha512-rZ6Y7RberigruefQpWOiI4bA9ppyT88GQF8htY6N1MrAgal5RrBc+Mh92CcGU7zT9QO+XO3DarSgZafNTepffQ==",
"dev": true,
"requires": {
"@electron/get": "^1.0.1",
@@ -2866,9 +2861,9 @@
},
"dependencies": {
"@types/node": {
"version": "14.17.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.21.tgz",
"integrity": "sha512-zv8ukKci1mrILYiQOwGSV4FpkZhyxQtuFWGya2GujWg+zVAeRQ4qbaMmWp9vb9889CFA8JECH7lkwCL6Ygg8kA==",
"version": "14.17.32",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.32.tgz",
"integrity": "sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ==",
"dev": true
}
}
@@ -3551,9 +3546,9 @@
}
},
"electron-to-chromium": {
"version": "1.3.864",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.864.tgz",
"integrity": "sha512-v4rbad8GO6/yVI92WOeU9Wgxc4NA0n4f6P1FvZTY+jyY7JHEhw3bduYu60v3Q1h81Cg6eo4ApZrFPuycwd5hGw==",
"version": "1.3.867",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.867.tgz",
"integrity": "sha512-WbTXOv7hsLhjJyl7jBfDkioaY++iVVZomZ4dU6TMe/SzucV6mUAs2VZn/AehBwuZMiNEQDaPuTGn22YK5o+aDw==",
"dev": true
},
"electron-window-state": {
@@ -4944,19 +4939,19 @@
}
},
"jasmine": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.9.0.tgz",
"integrity": "sha512-JgtzteG7xnqZZ51fg7N2/wiQmXon09szkALcRMTgCMX4u/m17gVJFjObnvw5FXkZOWuweHPaPRVB6DI2uN0wVA==",
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.10.0.tgz",
"integrity": "sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==",
"dev": true,
"requires": {
"glob": "^7.1.6",
"jasmine-core": "~3.9.0"
"jasmine-core": "~3.10.0"
}
},
"jasmine-core": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.9.0.tgz",
"integrity": "sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==",
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.10.0.tgz",
"integrity": "sha512-XWGaJ25RUdOQnjGiLoQa9QG/R4u1e9Bk4uhLdn9F4JCBco84L4SKM52bxci4vWTSUzhmhuHNAkAHFN/6Cox9wQ==",
"dev": true
},
"jest-worker": {
@@ -5782,9 +5777,9 @@
}
},
"nanoid": {
"version": "3.1.25",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz",
"integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q=="
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
"integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ=="
},
"napi-build-utils": {
"version": "1.0.2",
@@ -5844,9 +5839,9 @@
"dev": true
},
"node-releases": {
"version": "1.1.77",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.77.tgz",
"integrity": "sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.0.tgz",
"integrity": "sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA==",
"dev": true
},
"nopt": {
@@ -6308,9 +6303,9 @@
"integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA=="
},
"picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"picomatch": {
@@ -6380,13 +6375,20 @@
}
},
"postcss": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz",
"integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==",
"version": "8.3.9",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz",
"integrity": "sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw==",
"requires": {
"colorette": "^1.2.2",
"nanoid": "^3.1.23",
"nanoid": "^3.1.28",
"picocolors": "^0.2.1",
"source-map-js": "^0.6.2"
},
"dependencies": {
"picocolors": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
}
}
},
"prebuild-install": {
@@ -6862,9 +6864,9 @@
}
},
"sanitize-html": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.5.1.tgz",
"integrity": "sha512-hUITPitQk+eFNLtr4dEkaaiAJndG2YE87IOpcfBSL1XdklWgwcNDJdr9Ppe8QKL/C3jFt1xH/Mbj20e0GZQOfg==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.5.2.tgz",
"integrity": "sha512-sJ1rO2YixFIqs2kIcEUb6PTrCjvz8DMq1XqWWuy0kjgjrn58GNLK1DKSIRybFZDO1WNgsEgD+WiEzTEYS8xEug==",
"requires": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
@@ -8030,9 +8032,9 @@
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
},
"webpack": {
"version": "5.58.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.58.1.tgz",
"integrity": "sha512-4Z/dmbTU+VmkCb2XNgW7wkE5TfEcSooclprn/UEuVeAkwHhn07OcgUsyaKHGtCY/VobjnsYBlyhKeMLiSoOqPg==",
"version": "5.58.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.58.2.tgz",
"integrity": "sha512-3S6e9Vo1W2ijk4F4PPWRIu6D/uGgqaPmqw+av3W3jLDujuNkdxX5h5c+RQ6GkjVR+WwIPOfgY8av+j5j4tMqJw==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.0",

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.48.1-beta",
"version": "0.48.5",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -66,7 +66,7 @@
"request": "^2.88.2",
"rimraf": "3.0.2",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.5.1",
"sanitize-html": "2.5.2",
"sax": "1.2.4",
"semver": "7.3.5",
"serve-favicon": "2.5.0",
@@ -81,16 +81,16 @@
},
"devDependencies": {
"cross-env": "7.0.3",
"electron": "13.5.2",
"electron": "13.6.1",
"electron-builder": "22.13.1",
"electron-packager": "15.4.0",
"electron-rebuild": "3.2.3",
"esm": "3.2.25",
"jasmine": "3.9.0",
"jasmine": "3.10.0",
"jsdoc": "3.6.7",
"lorem-ipsum": "2.0.4",
"rcedit": "3.0.1",
"webpack": "5.58.1",
"webpack": "5.58.2",
"webpack-cli": "4.9.0"
},
"optionalDependencies": {

View File

@@ -4,7 +4,7 @@ const Attribute = require('../../src/becca/entities/attribute.js');
const becca = require('../../src/becca/becca.js');
const randtoken = require('rand-token').generator({source: 'crypto'});
/** @return {Note} */
/** @returns {Note} */
function findNoteByTitle(searchResults, title) {
return searchResults
.map(sr => becca.notes[sr.noteId])

View File

@@ -1,8 +1,6 @@
"use strict";
const sql = require("../services/sql.js");
const NoteRevision = require("./entities/note_revision.js");
const RecentNote = require("./entities/recent_note.js");
const NoteSet = require("../services/search/note_set");
class Becca {
@@ -27,7 +25,7 @@ class Becca {
this.loaded = false;
}
/** @return {Attribute[]} */
/** @returns {Attribute[]} */
findAttributes(type, name) {
name = name.trim().toLowerCase();
@@ -38,7 +36,7 @@ class Becca {
return this.attributeIndex[`${type}-${name}`] || [];
}
/** @return {Attribute[]} */
/** @returns {Attribute[]} */
findAttributesWithPrefix(type, name) {
const resArr = [];
const key = `${type}-${name}`;
@@ -102,6 +100,7 @@ class Becca {
getNoteRevision(noteRevisionId) {
const row = sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]);
const NoteRevision = require("./entities/note_revision.js"); // avoiding circular dependency problems
return row ? new NoteRevision(row) : null;
}
@@ -131,12 +130,14 @@ class Becca {
getRecentNotesFromQuery(query, params = []) {
const rows = sql.getRows(query, params);
const RecentNote = require("./entities/recent_note.js"); // avoiding circular dependency problems
return rows.map(row => new RecentNote(row));
}
getNoteRevisionsFromQuery(query, params = []) {
const rows = sql.getRows(query, params);
const NoteRevision = require("./entities/note_revision.js"); // avoiding circular dependency problems
return rows.map(row => new NoteRevision(row));
}

View File

@@ -45,6 +45,10 @@ function load() {
new Option(row);
}
for (const noteId in becca.notes) {
becca.notes[noteId].sortParents();
}
becca.loaded = true;
log.info(`Becca (note cache) load took ${Date.now() - start}ms`);
@@ -66,7 +70,7 @@ function postProcessEntityUpdate(entityName, entity) {
}
}
eventService.subscribe([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
if (!becca.loaded) {
return;
}
@@ -89,7 +93,7 @@ eventService.subscribe([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entit
postProcessEntityUpdate(entityName, entityRow);
});
eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
if (!becca.loaded) {
return;
}
@@ -97,7 +101,7 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
postProcessEntityUpdate(entityName, entity);
});
eventService.subscribe([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => {
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => {
if (!becca.loaded) {
return;
}
@@ -151,7 +155,7 @@ function branchUpdated(branch) {
if (childNote) {
childNote.flatTextCache = null;
childNote.resortParents();
childNote.sortParents();
}
}
@@ -216,7 +220,7 @@ function noteReorderingUpdated(branchIdList) {
}
}
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
eventService.subscribeBeccaLoader(eventService.ENTER_PROTECTED_SESSION, () => {
try {
becca.decryptProtectedNotes();
}
@@ -225,7 +229,7 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
}
});
eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, load);
eventService.subscribeBeccaLoader(eventService.LEAVE_PROTECTED_SESSION, load);
module.exports = {
load,

View File

@@ -125,7 +125,7 @@ function getNoteTitleForPath(notePathArray) {
/**
* Returns notePath for noteId from cache. Note hoisting is respected.
* Archived notes are also returned, but non-archived paths are preferred if available
* Archived (and hidden) notes are also returned, but non-archived paths are preferred if available
* - this means that archived paths is returned only if there's no non-archived path
* - you can check whether returned path is archived using isArchived
*/
@@ -140,10 +140,6 @@ function getSomePathInner(note, path, respectHoisting) {
path.push(note.noteId);
path.reverse();
if (path.includes("hidden")) {
return false;
}
if (respectHoisting && !path.includes(cls.getHoistedNoteId())) {
return false;
}

View File

@@ -36,21 +36,21 @@ class Attribute extends AbstractEntity {
}
update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]) {
/** @param {string} */
/** @type {string} */
this.attributeId = attributeId;
/** @param {string} */
/** @type {string} */
this.noteId = noteId;
/** @param {string} */
/** @type {string} */
this.type = type;
/** @param {string} */
/** @type {string} */
this.name = name;
/** @param {int} */
/** @type {int} */
this.position = position;
/** @param {string} */
/** @type {string} */
this.value = value;
/** @param {boolean} */
/** @type {boolean} */
this.isInheritable = !!isInheritable;
/** @param {string} */
/** @type {string} */
this.utcDateModified = utcDateModified;
return this;
@@ -145,6 +145,10 @@ class Attribute extends AbstractEntity {
}
}
get isDeleted() {
return !(this.attributeId in this.becca.attributes);
}
beforeSaving() {
if (!this.value) {
if (this.type === 'relation') {

View File

@@ -35,19 +35,19 @@ class Branch extends AbstractEntity {
}
update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]) {
/** @param {string} */
/** @type {string} */
this.branchId = branchId;
/** @param {string} */
/** @type {string} */
this.noteId = noteId;
/** @param {string} */
/** @type {string} */
this.parentNoteId = parentNoteId;
/** @param {string} */
/** @type {string} */
this.prefix = prefix;
/** @param {int} */
/** @type {int} */
this.notePosition = notePosition;
/** @param {boolean} */
/** @type {boolean} */
this.isExpanded = !!isExpanded;
/** @param {string} */
/** @type {string} */
this.utcDateModified = utcDateModified;
return this;
@@ -77,7 +77,7 @@ class Branch extends AbstractEntity {
this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this;
}
/** @return {Note} */
/** @returns {Note} */
get childNote() {
if (!(this.noteId in this.becca.notes)) {
// entities can come out of order in sync, create skeleton which will be filled later
@@ -91,7 +91,7 @@ class Branch extends AbstractEntity {
return this.childNote;
}
/** @return {Note} */
/** @returns {Note} */
get parentNote() {
if (!(this.parentNoteId in this.becca.notes)) {
// entities can come out of order in sync, create skeleton which will be filled later
@@ -101,6 +101,10 @@ class Branch extends AbstractEntity {
return this.becca.notes[this.parentNoteId];
}
get isDeleted() {
return !(this.branchId in this.becca.branches);
}
beforeSaving() {
if (this.notePosition === undefined || this.notePosition === null) {
// TODO finding new position can be refactored into becca

View File

@@ -45,33 +45,33 @@ class Note extends AbstractEntity {
update([noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified]) {
// ------ Database persisted attributes ------
/** @param {string} */
/** @type {string} */
this.noteId = noteId;
/** @param {string} */
/** @type {string} */
this.title = title;
/** @param {boolean} */
/** @type {boolean} */
this.isProtected = !!isProtected;
/** @param {string} */
/** @type {string} */
this.type = type;
/** @param {string} */
/** @type {string} */
this.mime = mime;
/** @param {string} */
/** @type {string} */
this.dateCreated = dateCreated || dateUtils.localNowDateTime();
/** @param {string} */
/** @type {string} */
this.dateModified = dateModified;
/** @param {string} */
/** @type {string} */
this.utcDateCreated = utcDateCreated || dateUtils.utcNowDateTime();
/** @param {string} */
/** @type {string} */
this.utcDateModified = utcDateModified;
// ------ Derived attributes ------
/** @param {boolean} */
this.isDecrypted = !this.isProtected;
/** @type {boolean} */
this.isDecrypted = !this.noteId || !this.isProtected;
this.decrypt();
/** @param {string|null} */
/** @type {string|null} */
this.flatTextCache = null;
return this;
@@ -116,26 +116,32 @@ class Note extends AbstractEntity {
|| protectedSessionService.isProtectedSessionAvailable()
}
/** @returns {Branch[]} */
getParentBranches() {
return this.parentBranches;
}
/** @returns {Branch[]} */
getBranches() {
return this.parentBranches;
}
/** @returns {Note[]} */
getParentNotes() {
return this.parents;
}
/** @returns {Note[]} */
getChildNotes() {
return this.children;
}
/** @returns {boolean} */
hasChildren() {
return this.children && this.children.length > 0;
}
/** @returns {Branch[]} */
getChildBranches() {
return this.children.map(childNote => this.becca.getBranchFromChildAndParent(childNote.noteId, this.noteId));
}
@@ -370,7 +376,7 @@ class Note extends AbstractEntity {
return this.__attributeCache;
}
/** @return {Attribute[]} */
/** @returns {Attribute[]} */
__getInheritableAttributes(path) {
if (path.includes(this.noteId)) {
return [];
@@ -611,7 +617,7 @@ class Note extends AbstractEntity {
// will sort the parents so that non-search & non-archived are first and archived at the end
// this is done so that non-search & non-archived paths are always explored as first when looking for note path
resortParents() {
sortParents() {
this.parentBranches.sort((a, b) =>
a.branchId.startsWith('virt-')
|| a.parentNote.hasInheritableOwnedArchivedLabel() ? 1 : -1);
@@ -721,28 +727,38 @@ class Note extends AbstractEntity {
return !!this.targetRelations.find(rel => rel.name === 'template');
}
/** @return {Note[]} */
/** @returns {Note[]} */
getSubtreeNotesIncludingTemplated() {
const arr = [[this]];
const set = new Set();
for (const childNote of this.children) {
arr.push(childNote.getSubtreeNotesIncludingTemplated());
}
function inner(note) {
if (set.has(note)) {
return;
}
for (const targetRelation of this.targetRelations) {
if (targetRelation.name === 'template') {
const note = targetRelation.note;
set.add(note);
if (note) {
arr.push(note.getSubtreeNotesIncludingTemplated());
for (const childNote of note.children) {
inner(childNote);
}
for (const targetRelation of note.targetRelations) {
if (targetRelation.name === 'template') {
const targetNote = targetRelation.note;
if (targetNote) {
inner(targetNote);
}
}
}
}
return arr.flat();
inner(this);
return Array.from(set);
}
/** @return {Note[]} */
/** @returns {Note[]} */
getSubtreeNotes(includeArchived = true) {
const noteSet = new Set();
@@ -763,9 +779,9 @@ class Note extends AbstractEntity {
return Array.from(noteSet);
}
/** @return {String[]} */
getSubtreeNoteIds() {
return this.getSubtreeNotes().map(note => note.noteId);
/** @returns {String[]} */
getSubtreeNoteIds(includeArchived = true) {
return this.getSubtreeNotes(includeArchived).map(note => note.noteId);
}
getDescendantNoteIds() {
@@ -820,6 +836,7 @@ class Note extends AbstractEntity {
return this.getAttributes().length;
}
/** @returns {Note[]} */
getAncestors() {
if (!this.ancestorCache) {
const noteIds = new Set();
@@ -843,11 +860,22 @@ class Note extends AbstractEntity {
return this.ancestorCache;
}
/** @returns {boolean} */
hasAncestor(ancestorNoteId) {
for (const ancestorNote of this.getAncestors()) {
if (ancestorNote.noteId === ancestorNoteId) {
return true;
}
}
return false;
}
getTargetRelations() {
return this.targetRelations;
}
/** @return {Note[]} - returns only notes which are templated, does not include their subtrees
/** @returns {Note[]} - returns only notes which are templated, does not include their subtrees
* in effect returns notes which are influenced by note's non-inheritable attributes */
getTemplatedNotes() {
const arr = [this];
@@ -1084,6 +1112,10 @@ class Note extends AbstractEntity {
}
}
get isDeleted() {
return !(this.noteId in this.becca.notes);
}
beforeSaving() {
super.beforeSaving();

View File

@@ -19,29 +19,29 @@ class NoteRevision extends AbstractEntity {
constructor(row) {
super();
/** @param {string} */
/** @type {string} */
this.noteRevisionId = row.noteRevisionId;
/** @param {string} */
/** @type {string} */
this.noteId = row.noteId;
/** @param {string} */
/** @type {string} */
this.type = row.type;
/** @param {string} */
/** @type {string} */
this.mime = row.mime;
/** @param {boolean} */
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @param {string} */
/** @type {string} */
this.title = row.title;
/** @param {string} */
/** @type {string} */
this.dateLastEdited = row.dateLastEdited;
/** @param {string} */
/** @type {string} */
this.dateCreated = row.dateCreated;
/** @param {string} */
/** @type {string} */
this.utcDateLastEdited = row.utcDateLastEdited;
/** @param {string} */
/** @type {string} */
this.utcDateCreated = row.utcDateCreated;
/** @param {string} */
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @param {number} */
/** @type {number} */
this.contentLength = row.contentLength;
if (this.isProtected) {

View File

@@ -163,7 +163,23 @@ const TPL = `
<p>
To apply font changes, click on
<button class="btn btn-micro" id="reload-frontend-button">reload frontend</button>
<button class="btn btn-micro reload-frontend-button">reload frontend</button>
</p>
<h4>Content width</h4>
<p>Trilium by default limits max content width to improve readability for maximized screens on wide screens.</p>
<div class="form-group row">
<div class="col-4">
<label for="max-content-width">Max content width in pixels</label>
<input type="number" min="200" step="10" class="form-control" id="max-content-width">
</div>
</div>
<p>
To content width changes, click on
<button class="btn btn-micro reload-frontend-button">reload frontend</button>
</p>
</form>`;
@@ -192,7 +208,7 @@ export default class ApperanceOptions {
this.$monospaceFontSize = $("#monospace-font-size");
this.$monospaceFontFamily = $("#monospace-font-family");
$("#reload-frontend-button").on("click", () => utils.reloadFrontendApp("font changes"));
$(".reload-frontend-button").on("click", () => utils.reloadFrontendApp("changes from appearance options"));
this.$body = $("body");
@@ -238,6 +254,14 @@ export default class ApperanceOptions {
for (const optionName of optionsToSave) {
this['$' + optionName].on('change', () => server.put(`options/${optionName}/${this['$' + optionName].val()}`));
}
this.$maxContentWidth = $("#max-content-width");
this.$maxContentWidth.on('change', async () => {
const maxContentWidth = this.$maxContentWidth.val();
await server.put('options/maxContentWidth/' + maxContentWidth);
})
}
toggleBodyClass(prefix, value) {
@@ -292,6 +316,8 @@ export default class ApperanceOptions {
this.$monospaceFontSize.val(options.monospaceFontSize);
this.fillFontFamilyOptions(this.$monospaceFontFamily, options.monospaceFontFamily);
this.$maxContentWidth.val(options.maxContentWidth);
}
fillFontFamilyOptions($select, currentValue) {

View File

@@ -8,19 +8,19 @@ class Attribute {
}
update(row) {
/** @param {string} attributeId */
/** @type {string} attributeId */
this.attributeId = row.attributeId;
/** @param {string} noteId */
/** @type {string} noteId */
this.noteId = row.noteId;
/** @param {string} type */
/** @type {string} type */
this.type = row.type;
/** @param {string} name */
/** @type {string} name */
this.name = row.name;
/** @param {string} value */
/** @type {string} value */
this.value = row.value;
/** @param {int} position */
/** @type {int} position */
this.position = row.position;
/** @param {boolean} isInheritable */
/** @type {boolean} isInheritable */
this.isInheritable = !!row.isInheritable;
}

View File

@@ -7,19 +7,19 @@ class Branch {
}
update(row) {
/** @param {string} primary key */
/** @type {string} primary key */
this.branchId = row.branchId;
/** @param {string} */
/** @type {string} */
this.noteId = row.noteId;
/** @param {string} */
/** @type {string} */
this.parentNoteId = row.parentNoteId;
/** @param {int} */
/** @type {int} */
this.notePosition = row.notePosition;
/** @param {string} */
/** @type {string} */
this.prefix = row.prefix;
/** @param {boolean} */
/** @type {boolean} */
this.isExpanded = !!row.isExpanded;
/** @param {boolean} */
/** @type {boolean} */
this.fromSearchNote = !!row.fromSearchNote;
}

View File

@@ -3,35 +3,35 @@
*/
class NoteComplement {
constructor(row) {
/** @param {string} */
/** @type {string} */
this.noteId = row.noteId;
/**
* @param {string} - can either contain the whole content (in e.g. string notes), only part (large text notes) or nothing at all (binary notes, images)
* @type {string} - can either contain the whole content (in e.g. string notes), only part (large text notes) or nothing at all (binary notes, images)
*/
this.content = row.content;
/** @param {int} */
/** @type {int} */
this.contentLength = row.contentLength;
/** @param {string} */
/** @type {string} */
this.dateCreated = row.dateCreated;
/** @param {string} */
/** @type {string} */
this.dateModified = row.dateModified;
/** @param {string} */
/** @type {string} */
this.utcDateCreated = row.utcDateCreated;
/** @param {string} */
/** @type {string} */
this.utcDateModified = row.utcDateModified;
// "combined" date modified give larger out of note's and note_content's dateModified
/** @param {string} */
/** @type {string} */
this.combinedDateModified = row.combinedDateModified;
/** @param {string} */
/** @type {string} */
this.combinedUtcDateModified = row.combinedUtcDateModified;
}
}

View File

@@ -53,15 +53,15 @@ class NoteShort {
}
update(row) {
/** @param {string} */
/** @type {string} */
this.noteId = row.noteId;
/** @param {string} */
/** @type {string} */
this.title = row.title;
/** @param {boolean} */
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @param {string} one of 'text', 'code', 'file' or 'render' */
/** @type {string} one of 'text', 'code', 'file' or 'render' */
this.type = row.type;
/** @param {string} content-type, e.g. "application/json" */
/** @type {string} content-type, e.g. "application/json" */
this.mime = row.mime;
}
@@ -644,12 +644,9 @@ class NoteShort {
}
/**
* Clear note's attributes cache to force fresh reload for next attribute request.
* Cache is note instance scoped.
* @deprecated NOOP
*/
invalidateAttributeCache() {
this.__attributeCache = null;
}
invalidateAttributeCache() {}
/**
* Get relations which target this note
@@ -681,7 +678,7 @@ class NoteShort {
return await this.froca.getNoteComplement(this.noteId);
}
get toString() {
toString() {
return `Note(noteId=${this.noteId}, title=${this.title})`;
}

View File

@@ -79,12 +79,12 @@ class AppContext extends Component {
this.triggerEvent('initialRenderComplete');
}
/** @return {Promise} */
/** @returns {Promise} */
triggerEvent(name, data) {
return this.handleEvent(name, data);
}
/** @return {Promise} */
/** @returns {Promise} */
triggerCommand(name, data = {}) {
for (const executor of this.executors) {
const fun = executor[name + "Command"];

View File

@@ -97,7 +97,7 @@ class ContextMenu {
.append($link)
// important to use mousedown instead of click since the former does not change focus
// (especially important for focused text for spell check)
.on('mousedown', (e) => {
.on('mousedown', e => {
e.stopPropagation();
this.hide();

View File

@@ -2,19 +2,19 @@ import froca from "./froca.js";
import server from "./server.js";
import ws from "./ws.js";
/** @return {NoteShort} */
/** @returns {NoteShort} */
async function getInboxNote() {
const note = await server.get('special-notes/inbox/' + dayjs().format("YYYY-MM-DD"), "date-note");
return await froca.getNote(note.noteId);
}
/** @return {NoteShort} */
/** @returns {NoteShort} */
async function getTodayNote() {
return await getDateNote(dayjs().format("YYYY-MM-DD"));
}
/** @return {NoteShort} */
/** @returns {NoteShort} */
async function getDateNote(date) {
const note = await server.get('special-notes/date/' + date, "date-note");
@@ -23,7 +23,16 @@ async function getDateNote(date) {
return await froca.getNote(note.noteId);
}
/** @return {NoteShort} */
/** @returns {NoteShort} */
async function getWeekNote(date) {
const note = await server.get('special-notes/week/' + date, "date-note");
await ws.waitForMaxKnownEntityChangeId();
return await froca.getNote(note.noteId);
}
/** @returns {NoteShort} */
async function getMonthNote(month) {
const note = await server.get('special-notes/month/' + month, "date-note");
@@ -32,7 +41,7 @@ async function getMonthNote(month) {
return await froca.getNote(note.noteId);
}
/** @return {NoteShort} */
/** @returns {NoteShort} */
async function getYearNote(year) {
const note = await server.get('special-notes/year/' + year, "date-note");
@@ -41,7 +50,7 @@ async function getYearNote(year) {
return await froca.getNote(note.noteId);
}
/** @return {NoteShort} */
/** @returns {NoteShort} */
async function createSqlConsole() {
const note = await server.post('special-notes/sql-console');
@@ -50,7 +59,7 @@ async function createSqlConsole() {
return await froca.getNote(note.noteId);
}
/** @return {NoteShort} */
/** @returns {NoteShort} */
async function createSearchNote(opts = {}) {
const note = await server.post('special-notes/search-note', opts);
@@ -63,6 +72,7 @@ export default {
getInboxNote,
getTodayNote,
getDateNote,
getWeekNote,
getMonthNote,
getYearNote,
createSqlConsole,

View File

@@ -172,6 +172,12 @@ export default class Entrypoints extends Component {
utils.reloadFrontendApp("Switching to desktop version");
}
async switchToMobileVersionCommand() {
utils.setCookie('trilium-device', 'mobile');
utils.reloadFrontendApp("Switching to mobile version");
}
async openInWindowCommand({notePath, hoistedNoteId}) {
if (!hoistedNoteId) {
hoistedNoteId = 'root';

View File

@@ -207,7 +207,7 @@ class Froca {
froca.notes[note.noteId].searchResultsLoaded = true;
}
/** @return {NoteShort[]} */
/** @returns {NoteShort[]} */
getNotesFromCache(noteIds, silentNotFoundError = false) {
return noteIds.map(noteId => {
if (!this.notes[noteId] && !silentNotFoundError) {
@@ -221,7 +221,7 @@ class Froca {
}).filter(note => !!note);
}
/** @return {Promise<NoteShort[]>} */
/** @returns {Promise<NoteShort[]>} */
async getNotes(noteIds, silentNotFoundError = false) {
const missingNoteIds = noteIds.filter(noteId => !this.notes[noteId]);
@@ -238,14 +238,14 @@ class Froca {
}).filter(note => !!note);
}
/** @return {Promise<boolean>} */
/** @returns {Promise<boolean>} */
async noteExists(noteId) {
const notes = await this.getNotes([noteId], true);
return notes.length === 1;
}
/** @return {Promise<NoteShort>} */
/** @returns {Promise<NoteShort>} */
async getNote(noteId, silentNotFoundError = false) {
if (noteId === 'none') {
console.trace(`No 'none' note.`);
@@ -273,7 +273,7 @@ class Froca {
.filter(b => !!b);
}
/** @return {Branch} */
/** @returns {Branch} */
getBranch(branchId, silentNotFoundError = false) {
if (!(branchId in this.branches)) {
if (!silentNotFoundError) {

View File

@@ -396,6 +396,15 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
*/
this.getDateNote = dateNotesService.getDateNote;
/**
* Returns date-note for the first date of the week of the given date. If it doesn't exist, it is automatically created.
*
* @method
* @param {string} date - e.g. "2019-04-29"
* @return {Promise<NoteShort>}
*/
this.getWeekNote = dateNotesService.getWeekNote;
/**
* Returns month-note. If it doesn't exist, it is automatically created.
*

View File

@@ -65,7 +65,7 @@ export default class LoadResults {
this.attributes.push({attributeId, sourceId});
}
/** @return {Attribute[]} */
/** @returns {Attribute[]} */
getAttributes(sourceId = 'none') {
return this.attributes
.filter(row => row.sourceId !== sourceId)

View File

@@ -43,7 +43,7 @@ async function autocompleteSource(term, cb, options = {}) {
action: 'create-note',
noteTitle: term,
parentNoteId: activeNoteId || 'root',
highlightedNotePathTitle: `Create and link child note "${term}"`
highlightedNotePathTitle: `Create and link child note "${utils.escapeHtml(term)}"`
}
].concat(results);
}
@@ -53,7 +53,7 @@ async function autocompleteSource(term, cb, options = {}) {
{
action: 'external-link',
externalLink: term,
highlightedNotePathTitle: `Insert external link to "${term}"`
highlightedNotePathTitle: `Insert external link to "${utils.escapeHtml(term)}"`
}
].concat(results);
}

View File

@@ -169,7 +169,7 @@ function trim(text, doTrim) {
return text;
}
else {
return text.substr(0, Math.min(text.length, 1000));
return text.substr(0, Math.min(text.length, 2000));
}
}

View File

@@ -21,6 +21,11 @@ class NoteContext extends Component {
}
setEmpty() {
this.notePath = null;
this.noteId = null;
this.parentNoteId = null;
this.hoistedNoteId = 'root';
this.triggerEvent('noteSwitched', {
noteContext: this,
notePath: this.notePath
@@ -71,7 +76,13 @@ class NoteContext extends Component {
getMainContext() {
if (this.mainNtxId) {
return appContext.tabManager.getNoteContextById(this.mainNtxId);
try {
return appContext.tabManager.getNoteContextById(this.mainNtxId);
}
catch (e) {
this.mainNtxId = null;
return this;
}
}
else {
return this;
@@ -123,7 +134,7 @@ class NoteContext extends Component {
return this.notePath ? this.notePath.split('/') : [];
}
/** @return {NoteComplement} */
/** @returns {NoteComplement} */
async getNoteComplement() {
if (!this.noteId) {
return null;
@@ -155,12 +166,12 @@ class NoteContext extends Component {
}
async setHoistedNoteId(noteIdToHoist) {
if (this.notePathArray && !this.notePathArray.includes(noteIdToHoist)) {
this.hoistedNoteId = noteIdToHoist;
if (!this.notePathArray?.includes(noteIdToHoist)) {
await this.setNote(noteIdToHoist);
}
this.hoistedNoteId = noteIdToHoist;
await this.triggerEvent('hoistedNoteChanged', {
noteId: noteIdToHoist,
ntxId: this.ntxId

View File

@@ -153,7 +153,7 @@ class NoteListRenderer {
this.parentNote = parentNote;
const includedNoteIds = this.getIncludedNoteIds();
this.noteIds = noteIds.filter(noteId => !includedNoteIds.has(noteId));
this.noteIds = noteIds.filter(noteId => !includedNoteIds.has(noteId) && noteId !== 'hidden');
if (this.noteIds.length === 0) {
return;
@@ -180,7 +180,7 @@ class NoteListRenderer {
this.showNotePath = showNotePath;
}
/** @return {Set<string>} list of noteIds included (images, included notes) into a parent note and which
/** @returns {Set<string>} list of noteIds included (images, included notes) into a parent note and which
* don't have to be shown in the note list. */
getIncludedNoteIds() {
const includedLinks = this.parentNote

View File

@@ -139,7 +139,7 @@ export default class TabManager extends Component {
this.triggerEvent('activeNoteChanged'); // trigger this even in on popstate event
}
/** @return {NoteContext[]} */
/** @returns {NoteContext[]} */
getNoteContexts() {
return this.noteContexts;
}
@@ -175,20 +175,20 @@ export default class TabManager extends Component {
return activeContext ? activeContext.notePath : null;
}
/** @return {NoteShort} */
/** @returns {NoteShort} */
getActiveContextNote() {
const activeContext = this.getActiveContext();
return activeContext ? activeContext.note : null;
}
/** @return {string|null} */
/** @returns {string|null} */
getActiveContextNoteId() {
const activeNote = this.getActiveContextNote();
return activeNote ? activeNote.noteId : null;
}
/** @return {string|null} */
/** @returns {string|null} */
getActiveContextNoteType() {
const activeNote = this.getActiveContextNote();
@@ -299,6 +299,17 @@ export default class TabManager extends Component {
async removeNoteContext(ntxId) {
const noteContextToRemove = this.getNoteContextById(ntxId);
if (noteContextToRemove.isMainContext()) {
// forbid removing last main note context
// this was previously allowed (was replaced with empty tab) but this proved to be prone to race conditions
const mainNoteContexts = this.getNoteContexts().filter(nc => nc.isMainContext());
if (mainNoteContexts.length === 1) {
mainNoteContexts[0].setEmpty();
return;
}
}
// close dangling autocompletes after closing the tab
$(".aa-input").autocomplete("close");
@@ -353,7 +364,7 @@ export default class TabManager extends Component {
const order = {};
let i = 0;
for (const ntxId in ntxIdsInOrder) {
for (const ntxId of ntxIdsInOrder) {
order[ntxId] = i++;
}

View File

@@ -106,6 +106,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId);
if (!someNotePathSegments) {
throw new Error(`Did not find any path segments for ${note.toString()}, hoisted note ${hoistedNoteId}`);
}
// if there isn't actually any note path with hoisted note then return the original resolved note path
return someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments;
}

View File

@@ -68,12 +68,13 @@ async function executeFrontendUpdate(entityChanges) {
frontendUpdateDataQueue.push(...entityChanges);
// we set lastAcceptedEntityChangeId even before frontend update processing and send ping so that backend can start sending more updates
lastAcceptedEntityChangeId = Math.max(lastAcceptedEntityChangeId, entityChanges[entityChanges.length - 1].id);
const lastSyncEntityChange = entityChanges.slice().reverse().find(ec => ec.isSynced);
for (const entityChange of entityChanges) {
lastAcceptedEntityChangeId = Math.max(lastAcceptedEntityChangeId, entityChange.id);
if (lastSyncEntityChange) {
lastAcceptedEntityChangeSyncId = Math.max(lastAcceptedEntityChangeSyncId, lastSyncEntityChange.id);
if (entityChange.isSynced) {
lastAcceptedEntityChangeSyncId = Math.max(lastAcceptedEntityChangeSyncId, entityChange.id);
}
}
sendPing();

View File

@@ -334,7 +334,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$relatedNotesList = this.$relatedNotesContainer.find('.related-notes-list');
this.$relatedNotesMoreNotes = this.$relatedNotesContainer.find('.related-notes-more-notes');
$(window).on('mouseup', e => {
$(window).on('mousedown', e => {
if (!$(e.target).closest(this.$widget[0]).length
&& !$(e.target).closest(".algolia-autocomplete").length
&& !$(e.target).closest("#context-menu-container").length) {
@@ -626,7 +626,9 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
props.push('precision=' + this.$inputNumberPrecision.val());
}
} else if (this.attrType === 'relation-definition' && this.$inputInverseRelation.val().trim().length > 0) {
props.push("inverse=" + this.$inputInverseRelation.val());
const inverseRelationName = this.$inputInverseRelation.val();
props.push("inverse=" + utils.filterAttributeName(inverseRelationName));
}
this.$rowNumberPrecision.toggle(

View File

@@ -193,7 +193,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
this.$editor.on('keydown', async e => {
if (e.which === 13) {
await this.save();
// allow autocomplete to fill the result textarea
setTimeout(() => this.save(), 100);
}
this.attributeDetailWidget.hide();

View File

@@ -26,9 +26,17 @@ export default class ButtonWidget extends NoteContextAwareWidget {
this.$widget = $(TPL);
if (this.settings.onClick) {
this.$widget.on("click", () => this.settings.onClick(this));
this.$widget.on("click", e => {
this.$widget.tooltip("hide");
this.settings.onClick(this, e);
});
} else {
this.$widget.on("click", () => this.triggerCommand(this.settings.command));
this.$widget.on("click", () => {
this.$widget.tooltip("hide");
this.triggerCommand(this.settings.command);
});
}
this.$widget.attr("data-placement", this.settings.titlePlacement);

View File

@@ -13,6 +13,12 @@ export default class ClosePaneButton extends ButtonWidget {
this.icon("bx-x")
.title("Close this pane")
.titlePlacement("bottom")
.onClick(widget => widget.triggerCommand("closeThisNoteSplit", { ntxId: widget.getNtxId() }));
.onClick((widget, e) => {
// to avoid split pane container detecting click within the pane which would try to activate this
// pane (which is being removed)
e.stopPropagation();
widget.triggerCommand("closeThisNoteSplit", { ntxId: widget.getNtxId() });
});
}
}

View File

@@ -60,6 +60,11 @@ const TPL = `
<kbd data-command="showBackendLog"></kbd>
</a>
<a class="dropdown-item switch-to-mobile-version-button" data-trigger-command="switchToMobileVersion">
<span class="bx bx-empty"></span>
Switch to mobile version
</a>
<a class="dropdown-item" data-trigger-command="reloadFrontendApp"
title="Reload can help with some visual glitches without restarting the whole app.">
<span class="bx bx-empty"></span>
@@ -103,9 +108,11 @@ export default class GlobalMenuWidget extends BasicWidget {
this.$widget.find(".show-about-dialog-button").on('click',
() => import("../../dialogs/about.js").then(d => d.showDialog()));
this.$widget.find(".logout-button").toggle(!utils.isElectron());
const isElectron = utils.isElectron();
this.$widget.find(".open-dev-tools-button").toggle(utils.isElectron());
this.$widget.find(".logout-button").toggle(!isElectron);
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',
() => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));

View File

@@ -25,12 +25,12 @@ const TPL = `
<div class="dropdown-menu dropdown-menu-right">
<a data-trigger-command="renderActiveNote" class="dropdown-item render-note-button"><kbd data-command="renderActiveNote"></kbd> Re-render note</a>
<a data-trigger-command="findInText" class="dropdown-item">Search in note <kbd data-command="findInText"></a>
<a data-trigger-command="findInText" class="dropdown-item find-in-text-button">Search in note <kbd data-command="findInText"></a>
<a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a>
<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 data-trigger-command="printActiveNote" class="dropdown-item print-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
<a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
</div>
</div>`;
@@ -42,6 +42,8 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.$findInTextButton = this.$widget.find('.find-in-text-button');
this.$printActiveNoteButton = this.$widget.find('.print-active-note-button');
this.$showSourceButton = this.$widget.find('.show-source-button');
this.$renderNoteButton = this.$widget.find('.render-note-button');
@@ -57,14 +59,18 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.$importNoteButton = this.$widget.find('.import-files-button');
this.$importNoteButton.on("click", () => import('../../dialogs/import.js').then(d => d.showDialog(this.noteId)));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find('.dropdown-menu').dropdown('toggle'));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
}
refreshWithNote(note) {
this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book', 'search'].includes(note.type));
this.toggleDisabled(this.$showSourceButton, ['text', 'relation-map', 'search', 'code'].includes(note.type));
this.toggleDisabled(this.$printActiveNoteButton, ['text', 'code'].includes(note.type));
this.$renderNoteButton.toggle(note.type === 'render');
this.$openNoteExternallyButton.toggle(utils.isElectron());

View File

@@ -40,7 +40,7 @@ export default class Component {
return this;
}
/** @return {Promise} */
/** @returns {Promise} */
handleEvent(name, data) {
return Promise.all([
this.initialized.then(() => this.callMethod(this[name + 'Event'], data)),
@@ -48,12 +48,12 @@ export default class Component {
]);
}
/** @return {Promise} */
/** @returns {Promise} */
triggerEvent(name, data) {
return this.parent.triggerEvent(name, data);
}
/** @return {Promise} */
/** @returns {Promise} */
handleEventInChildren(name, data) {
const promises = [];
@@ -64,7 +64,7 @@ export default class Component {
return Promise.all(promises);
}
/** @return {Promise} */
/** @returns {Promise} */
triggerCommand(name, data = {}) {
const fun = this[name + 'Command'];

View File

@@ -1,5 +1,6 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import keyboardActionsService from "../../services/keyboard_actions.js";
import attributeService from "../../services/attributes.js";
const TPL = `
<div class="ribbon-container">
@@ -166,7 +167,7 @@ export default class RibbonContainer extends NoteContextAwareWidget {
});
}
toggleRibbonTab($ribbonTitle) {
toggleRibbonTab($ribbonTitle, refreshActiveTab = true) {
const activate = !$ribbonTitle.hasClass("active");
this.$tabContainer.find('.ribbon-tab-title').removeClass("active");
@@ -182,7 +183,7 @@ export default class RibbonContainer extends NoteContextAwareWidget {
const activeChild = this.getActiveRibbonWidget();
if (activeChild) {
if (activeChild && refreshActiveTab) {
activeChild.handleEvent('noteSwitched', {noteContext: this.noteContext, notePath: this.notePath});
}
} else {
@@ -248,7 +249,7 @@ export default class RibbonContainer extends NoteContextAwareWidget {
}
if ($ribbonTabToActivate) {
$ribbonTabToActivate.trigger('click');
this.toggleRibbonTab($ribbonTabToActivate, false);
}
else {
this.$bodyContainer.find('.ribbon-body').removeClass("active");
@@ -261,10 +262,6 @@ export default class RibbonContainer extends NoteContextAwareWidget {
return $ribbonComponent.hasClass("active");
}
refreshRibbonContainerCommand() {
this.refreshWithNote(this.note, true);
}
ensureOwnedAttributesAreOpen(ntxId) {
if (this.isNoteContext(ntxId) && !this.isRibbonTabActive('ownedAttributes')) {
this.toggleRibbonTabWithName('ownedAttributes', ntxId);
@@ -332,6 +329,9 @@ export default class RibbonContainer extends NoteContextAwareWidget {
this.refresh();
}
else if (loadResults.getAttributes(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refreshWithNote(this.note, true);
}
}
getActiveRibbonWidget() {

View File

@@ -19,7 +19,7 @@ export default class SplitNoteContainer extends FlexContainer {
const $renderedWidget = widget.render();
$renderedWidget.attr("data-ntx-id", noteContext.ntxId);
$renderedWidget.css("flex-basis", "0"); // so that each split has same width
$renderedWidget.addClass("note-split");
$renderedWidget.on('click', () => appContext.tabManager.activateNoteContext(noteContext.ntxId));
@@ -37,6 +37,12 @@ export default class SplitNoteContainer extends FlexContainer {
}
async openNewNoteSplitEvent({ntxId, notePath}) {
if (!ntxId) {
logError("empty ntxId!");
ntxId = appContext.tabManager.getActiveMainContext().ntxId;
}
const noteContext = await appContext.tabManager.openEmptyTab(null, 'root', appContext.tabManager.getActiveMainContext().ntxId);
// remove the original position of newly created note context
@@ -46,7 +52,7 @@ export default class SplitNoteContainer extends FlexContainer {
// insert the note context after the originating note context
ntxIds.splice(ntxIds.indexOf(ntxId) + 1, 0, noteContext.ntxId);
this.triggerCommand("noteContextReorder", ntxIds);
this.triggerCommand("noteContextReorder", {ntxIdsInOrder: ntxIds});
// move the note context rendered widget after the originating widget
this.$widget.find(`[data-ntx-id="${noteContext.ntxId}"]`)

View File

@@ -17,7 +17,7 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
top: 10px;
right: 10px;
background-color: var(--accented-background-color);
z-index: 1000;
z-index: 10; /* should be below dropdown (note actions) */
}
.map-type-switcher .bx {
@@ -332,9 +332,9 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
if (this.widgetMode === 'ribbon') {
setTimeout(() => {
const node = this.nodes.find(node => node.id === this.noteId);
const subGraphNoteIds = this.getSubGraphConnectedToCurrentNote(data);
this.graph.centerAt(node.x, node.y, 500);
this.graph.zoomToFit(400, 50, node => subGraphNoteIds.has(node.id));
}, 1000);
}
else if (this.widgetMode === 'type') {
@@ -344,6 +344,39 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
}
}
getSubGraphConnectedToCurrentNote(data) {
function getGroupedLinksBySource(links) {
const map = {};
for (const link of links) {
const key = link.source.id;
map[key] = map[key] || [];
map[key].push(link);
}
return map;
}
const linksBySource = getGroupedLinksBySource(data.links);
const subGraphNoteIds = new Set();
function traverseGraph(noteId) {
if (subGraphNoteIds.has(noteId)) {
return;
}
subGraphNoteIds.add(noteId);
for (const link of linksBySource[noteId] || []) {
traverseGraph(link.target.id);
}
}
traverseGraph(this.noteId);
return subGraphNoteIds;
}
cleanup() {
this.$container.html('');
}

View File

@@ -681,12 +681,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return extraClasses.join(" ");
}
/** @return {FancytreeNode[]} */
/** @returns {FancytreeNode[]} */
getSelectedNodes(stopOnParents = false) {
return this.tree.getSelectedNodes(stopOnParents);
}
/** @return {FancytreeNode[]} */
/** @returns {FancytreeNode[]} */
getSelectedOrActiveNodes(node = null) {
const nodes = this.getSelectedNodes(true);
@@ -771,7 +771,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
}
/** @return {FancytreeNode} */
/** @returns {FancytreeNode} */
async getNodeFromPath(notePath, expand = false, logErrors = true) {
utils.assertArguments(notePath);
/** @let {FancytreeNode} */
@@ -838,24 +838,24 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return parentNode;
}
/** @return {FancytreeNode} */
/** @returns {FancytreeNode} */
findChildNode(parentNode, childNoteId) {
return parentNode.getChildren().find(childNode => childNode.data.noteId === childNoteId);
}
/** @return {FancytreeNode} */
/** @returns {FancytreeNode} */
async expandToNote(notePath, logErrors = true) {
return this.getNodeFromPath(notePath, true, logErrors);
}
/** @return {FancytreeNode[]} */
/** @returns {FancytreeNode[]} */
getNodesByBranch(branch) {
utils.assertArguments(branch);
return this.getNodesByNoteId(branch.noteId).filter(node => node.data.branchId === branch.branchId);
}
/** @return {FancytreeNode[]} */
/** @returns {FancytreeNode[]} */
getNodesByNoteId(noteId) {
utils.assertArguments(noteId);

View File

@@ -5,7 +5,8 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js";
const NOTE_TYPES = [
{ type: "file", title: "File", selectable: false },
{ type: "image", title: "Image", selectable: false },
{ type: "search", title: "Saved search", selectable: false },
{ type: "search", title: "Saved Search", selectable: false },
{ type: "note-map", mime: '', title: "Note Map", selectable: false },
{ type: "text", mime: "text/html", title: "Text", selectable: true },
{ type: "relation-map", mime: "application/json", title: "Relation Map", selectable: true },
@@ -15,6 +16,8 @@ const NOTE_TYPES = [
{ type: "code", mime: 'text/plain', title: "Code", selectable: true }
];
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter(nt => !nt.selectable).map(nt => nt.type);
const TPL = `
<div class="dropdown note-type-widget">
<style>
@@ -48,7 +51,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
async refreshWithNote(note) {
this.$noteTypeButton.prop("disabled",
() => ["file", "image", "search"].includes(note.type));
() => NOT_SELECTABLE_NOTE_TYPES.includes(note.type));
this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime));

View File

@@ -7,6 +7,7 @@ const TPL = `
<style>
.note-update-status-widget {
margin: 10px;
contain: none;
}
</style>

View File

@@ -33,7 +33,11 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
constructor() {
super();
this.attributeDetailWidget = new AttributeDetailWidget().setParent(this);
/** @type {AttributeDetailWidget} */
this.attributeDetailWidget = new AttributeDetailWidget()
.contentSized()
.setParent(this);
this.child(this.attributeDetailWidget);
}
@@ -65,18 +69,21 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
for (const attribute of inheritedAttributes) {
const $attr = (await attributeRenderer.renderAttribute(attribute, false))
.on('click', e => this.attributeDetailWidget.showAttributeDetail({
attribute: {
noteId: attribute.noteId,
type: attribute.type,
name: attribute.name,
value: attribute.value,
isInheritable: attribute.isInheritable
},
isOwned: false,
x: e.pageX,
y: e.pageY
}));
.on('click', e => {
setTimeout(() =>
this.attributeDetailWidget.showAttributeDetail({
attribute: {
noteId: attribute.noteId,
type: attribute.type,
name: attribute.name,
value: attribute.value,
isInheritable: attribute.isInheritable
},
isOwned: false,
x: e.pageX,
y: e.pageY
}), 100);
});
this.$container
.append($attr)

View File

@@ -69,7 +69,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
this.$notePathList.empty();
if (this.noteId === 'root') {
await this.addPath('root');
await this.getRenderedPath('root');
return;
}
@@ -83,22 +83,18 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
this.$notePathIntro.text("This note is not yet placed into the note tree.");
}
const printedNotePaths = new Set();
const renderedPaths = [];
for (const notePathRecord of sortedNotePaths) {
const notePath = notePathRecord.notePath.join('/');
if (printedNotePaths.has(notePath)) {
continue;
}
printedNotePaths.add(notePath);
await this.addPath(notePath, notePathRecord);
renderedPaths.push(await this.getRenderedPath(notePath, notePathRecord));
}
this.$notePathList.empty().append(...renderedPaths);
}
async addPath(notePath, notePathRecord) {
async getRenderedPath(notePath, notePathRecord) {
const title = await treeService.getNotePathTitle(notePath);
const $noteLink = await linkService.createNoteLink(notePath, {title});
@@ -136,7 +132,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
$noteLink.append(` ${icons.join(' ')}`);
}
this.$notePathList.append($("<li>").append($noteLink));
return $("<li>").append($noteLink);
}
entitiesReloadedEvent({loadResults}) {

View File

@@ -297,9 +297,6 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
entitiesReloadedEvent({loadResults}) {
if (loadResults.getAttributes(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refresh();
this.getTitle(this.note);
this.triggerCommand('refreshRibbonContainer');
}
}
}

View File

@@ -226,8 +226,8 @@ div.ui-tooltip {
background: inherit;
}
.CodeMirror * {
font-family: var(--monospace-font-family) !important;
body .CodeMirror {
font-size: var(--monospace-font-size);
}
.CodeMirror-gutters {
@@ -950,3 +950,9 @@ input {
white-space: nowrap;
text-overflow: ellipsis;
}
.note-split {
flex-basis: 0; /* so that each split has same width */
margin-left: auto;
margin-right: auto;
}

View File

@@ -125,11 +125,13 @@ function updateNoteAttributes(req) {
for (const incAttr of incomingAttributes) {
position += 10;
const value = incAttr.value || "";
const perfectMatchAttr = existingAttrs.find(attr =>
attr.type === incAttr.type &&
attr.name === incAttr.name &&
attr.isInheritable === incAttr.isInheritable &&
attr.value === incAttr.value);
attr.value === value);
if (perfectMatchAttr) {
existingAttrs = existingAttrs.filter(attr => attr.attributeId !== perfectMatchAttr.attributeId);
@@ -145,7 +147,7 @@ function updateNoteAttributes(req) {
if (incAttr.type === 'relation') {
const targetNote = becca.getNote(incAttr.value);
if (!targetNote || targetNote.isDeleted) {
if (!targetNote) {
log.error(`Target note of relation ${JSON.stringify(incAttr)} does not exist or is deleted`);
continue;
}

View File

@@ -15,7 +15,7 @@ const htmlSanitizer = require('../../services/html_sanitizer');
const {formatAttrForSearch} = require("../../services/attribute_formatter");
function findClippingNote(todayNote, pageUrl) {
const notes = todayNote.searchNoteInSubtree(
const notes = todayNote.searchNotesInSubtree(
formatAttrForSearch({
type: 'label',
name: "pageUrl",
@@ -59,6 +59,7 @@ function addClipping(req) {
clippingNote.setLabel('clipType', 'clippings');
clippingNote.setLabel('pageUrl', pageUrl);
clippingNote.setLabel('iconClass', 'bx bx-globe');
}
const rewrittenContent = processContent(images, clippingNote, content);
@@ -92,6 +93,7 @@ function createNote(req) {
if (pageUrl) {
note.setLabel('pageUrl', pageUrl);
note.setLabel('iconClass', 'bx bx-globe');
}
const rewrittenContent = processContent(images, note, content);

View File

@@ -27,7 +27,7 @@ function getFontCss(req, res) {
style += `--main-font-size: ${optionsMap.mainFontSize}%;`;
style += `--tree-font-size: ${optionsMap.treeFontSize}%;`;
style += `--detail-font-size: ${optionsMap.detailFontSize}%;`;
style += `--monospace-font-size: ${optionsMap.monospaceFontSize};`;
style += `--monospace-font-size: ${optionsMap.monospaceFontSize}%;`;
style += '}';

View File

@@ -24,23 +24,54 @@ function buildDescendantCountMap() {
return noteIdToCountMap;
}
function getNeighbors(note, depth) {
if (depth === 0) {
return [];
}
const retNoteIds = [];
for (const relation of note.getRelations()) {
if (['relationMapLink', 'template', 'image'].includes(relation.name)) {
continue;
}
const targetNote = relation.getTargetNote();
retNoteIds.push(targetNote.noteId);
for (const noteId of getNeighbors(targetNote, depth - 1)) {
retNoteIds.push(noteId);
}
}
return retNoteIds;
}
function getLinkMap(req) {
const mapRootNote = becca.getNote(req.params.noteId);
// if the map root itself has ignore (journal typically) then there wouldn't be anything to display so
// we'll just ignore it
const ignoreExcludeFromNoteMap = mapRootNote.hasLabel('excludeFromNoteMap');
const noteIds = new Set();
const noteIds = new Set(
mapRootNote.getSubtreeNotes(false)
.filter(note => ignoreExcludeFromNoteMap || !note.hasLabel('excludeFromNoteMap'))
.map(note => note.noteId)
);
const notes = mapRootNote.getSubtreeNotes(false)
.filter(note => ignoreExcludeFromNoteMap || !note.hasLabel('excludeFromNoteMap'))
.map(note => [
for (const noteId of getNeighbors(mapRootNote, 3)) {
noteIds.add(noteId);
}
const notes = Array.from(noteIds).map(noteId => {
const note = becca.getNote(noteId);
return [
note.noteId,
note.isContentAvailable() ? note.title : '[protected]',
note.type
]);
notes.forEach(([noteId]) => noteIds.add(noteId));
];
});
const links = Object.values(becca.attributes).filter(rel => {
if (rel.type !== 'relation' || rel.name === 'relationMapLink' || rel.name === 'template') {

View File

@@ -54,6 +54,7 @@ const ALLOWED_OPTIONS = new Set([
'dailyBackupEnabled',
'weeklyBackupEnabled',
'monthlyBackupEnabled',
'maxContentWidth'
]);
function getOptions() {

View File

@@ -5,11 +5,11 @@ const attributeService = require('../../services/attributes');
const becca = require('../../becca/becca');
const syncService = require('../../services/sync');
function exec(req) {
async function exec(req) {
try {
const {body} = req;
const result = scriptService.executeScript(
const result = await scriptService.executeScript(
body.script,
body.params,
body.startNoteId,

View File

@@ -247,6 +247,12 @@ function getRelatedNotes(req) {
const allResults = matchingNameAndValue.concat(matchingName);
const allResultNoteIds = new Set();
for (const record of allResults) {
allResultNoteIds.add(record.noteId);
}
for (const record of allResults) {
if (results.length >= 20) {
break;
@@ -260,7 +266,7 @@ function getRelatedNotes(req) {
}
return {
count: allResults.length,
count: allResultNoteIds.size,
results
};
}

View File

@@ -14,6 +14,10 @@ function getDateNote(req) {
return dateNoteService.getDateNote(req.params.date);
}
function getWeekNote(req) {
return dateNoteService.getWeekNote(req.params.date);
}
function getMonthNote(req) {
return dateNoteService.getMonthNote(req.params.month);
}
@@ -65,6 +69,7 @@ function getHoistedNote() {
module.exports = {
getInboxNote,
getDateNote,
getWeekNote,
getMonthNote,
getYearNote,
getDateNotesForMonth,

View File

@@ -7,12 +7,15 @@ const config = require('../services/config');
const optionService = require('../services/options');
const log = require('../services/log');
const env = require('../services/env');
const utils = require('../services/utils');
const protectedSessionService = require("../services/protected_session");
function index(req, res) {
const options = optionService.getOptionsMap();
let view = req.cookies['trilium-device'] === 'mobile' ? 'mobile' : 'desktop';
let view = (!utils.isElectron() && req.cookies['trilium-device'] === 'mobile')
? 'mobile'
: 'desktop';
const csrfToken = req.csrfToken();
log.info(`Generated CSRF token ${csrfToken} with secret ${res.getHeader('set-cookie')}`);
@@ -32,7 +35,8 @@ function index(req, res) {
isDev: env.isDev(),
isMainWindow: !req.query.extra,
extraHoistedNoteId: req.query.extraHoistedNoteId,
isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable()
isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(),
maxContentWidth: parseInt(options.maxContentWidth)
});
}

View File

@@ -262,6 +262,7 @@ function register(app) {
apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote);
apiRoute(GET, '/api/special-notes/date/:date', specialNotesRoute.getDateNote);
apiRoute(GET, '/api/special-notes/week/:date', specialNotesRoute.getWeekNote);
apiRoute(GET, '/api/special-notes/month/:month', specialNotesRoute.getMonthNote);
apiRoute(GET, '/api/special-notes/year/:year', specialNotesRoute.getYearNote);
apiRoute(GET, '/api/special-notes/notes-for-month/:month', specialNotesRoute.getDateNotesForMonth);

View File

@@ -58,21 +58,21 @@ function BackendScriptApi(currentNote, apiParams) {
* @param {string} noteId
* @returns {Note|null}
*/
this.getNote = becca.getNote;
this.getNote = noteId => becca.getNote(noteId);
/**
* @method
* @param {string} branchId
* @returns {Branch|null}
*/
this.getBranch = becca.getBranch;
this.getBranch = branchId => becca.getBranch(branchId);
/**
* @method
* @param {string} attributeId
* @returns {Attribute|null}
*/
this.getAttribute = becca.getAttribute;
this.getAttribute = attributeId => becca.getAttribute(attributeId);
/**
* This is a powerful search method - you can search by attributes and their values, e.g.:

View File

@@ -1 +1 @@
module.exports = { buildDate:"2021-10-12T22:42:48+02:00", buildRevision: "64a3a8b561f614a1940551bb3feb9e035f7cb475" };
module.exports = { buildDate:"2021-11-01T09:19:28+01:00", buildRevision: "930d29d64adb90707ad8854739a4628b02765bc5" };

View File

@@ -34,7 +34,7 @@ function getNoteStartingWith(parentNoteId, startsWith) {
return becca.getNote(noteId);
}
/** @return {Note} */
/** @returns {Note} */
function getRootCalendarNote() {
let rootNote = attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
@@ -57,7 +57,7 @@ function getRootCalendarNote() {
return rootNote;
}
/** @return {Note} */
/** @returns {Note} */
function getYearNote(dateStr, rootNote) {
if (!rootNote) {
rootNote = getRootCalendarNote();
@@ -97,7 +97,7 @@ function getMonthNoteTitle(rootNote, monthNumber, dateObj) {
.replace(/{month}/g, monthName);
}
/** @return {Note} */
/** @returns {Note} */
function getMonthNote(dateStr, rootNote) {
if (!rootNote) {
rootNote = getRootCalendarNote();
@@ -152,7 +152,7 @@ function getDateNoteTitle(rootNote, dayNumber, dateObj) {
.replace(/{weekDay2}/g, weekDay.substr(0, 2));
}
/** @return {Note} */
/** @returns {Note} */
function getDateNote(dateStr) {
let dateNote = attributeService.getNoteWithLabel(DATE_LABEL, dateStr);

View File

@@ -27,6 +27,19 @@ function subscribe(eventTypes, listener) {
}
}
function subscribeBeccaLoader(eventTypes, listener) {
if (!Array.isArray(eventTypes)) {
eventTypes = [ eventTypes ];
}
for (const eventType of eventTypes) {
eventListeners[eventType] = eventListeners[eventType] || [];
// becca loader should be the first listener so that other listeners can already work
// with updated becca
eventListeners[eventType] = [listener, ...eventListeners[eventType]];
}
}
function emit(eventType, data) {
const listeners = eventListeners[eventType];
@@ -45,6 +58,7 @@ function emit(eventType, data) {
module.exports = {
subscribe,
subscribeBeccaLoader,
emit,
// event types:
NOTE_TITLE_CHANGED,

View File

@@ -138,7 +138,8 @@ eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) =>
isInheritable: entity.isInheritable
}).save();
targetNote.invalidateAttributeCache();
// becca will not be updated before we'll check from the other side which would create infinite relation creation (#2269)
targetNote.invalidateThisCache();
}
});
});
@@ -150,9 +151,6 @@ eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) =>
for (const relation of relations) {
if (relation.value === note.noteId) {
note.invalidateAttributeCache();
targetNote.invalidateAttributeCache();
relation.markAsDeleted();
}
}

View File

@@ -393,7 +393,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
}
}
/** @return {string} path without leading or trailing slash and backslashes converted to forward ones*/
/** @returns {string} path without leading or trailing slash and backslashes converted to forward ones*/
function normalizeFilePath(filePath) {
filePath = filePath.replace(/\\/g, "/");

View File

@@ -89,7 +89,8 @@ const defaultOptions = [
{ name: 'autoReadonlySizeCode', value: '30000', isSynced: false },
{ name: 'dailyBackupEnabled', value: 'true', isSynced: false },
{ name: 'weeklyBackupEnabled', value: 'true', isSynced: false },
{ name: 'monthlyBackupEnabled', value: 'true', isSynced: false }
{ name: 'monthlyBackupEnabled', value: 'true', isSynced: false },
{ name: 'maxContentWidth', value: '1200', isSynced: false },
];
function initStartupOptions() {

View File

@@ -20,7 +20,7 @@ function parse(value) {
else if (token.startsWith('inverse')) {
const chunks = token.split('=');
defObj.inverseRelation = chunks[1];
defObj.inverseRelation = chunks[1].replace(/[^\p{L}\p{N}_:]/ug, "")
}
else {
console.log("Unrecognized attribute definition token:", token);

View File

@@ -3,7 +3,6 @@
const lex = require('./lex');
const handleParens = require('./handle_parens');
const parse = require('./parse');
const NoteSet = require("../note_set");
const SearchResult = require("../search_result");
const SearchContext = require("../search_context");
const becca = require('../../../becca/becca');

View File

@@ -137,7 +137,15 @@ function saveSqlConsole(sqlConsoleNoteId) {
attributeService.getNoteWithLabel('sqlConsoleHome')
|| dateNoteService.getDateNote(today);
return sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
const result = sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
for (const parentBranch of sqlConsoleNote.getParentBranches()) {
if (parentBranch.parentNote.hasAncestor("hidden")) {
parentBranch.markAsDeleted();
}
}
return result;
}
function createSearchNote(searchString, ancestorNoteId) {
@@ -158,25 +166,34 @@ function createSearchNote(searchString, ancestorNoteId) {
return note;
}
function saveSearchNote(searchNoteId) {
const searchNote = becca.getNote(searchNoteId);
function getSearchHome() {
const hoistedNote = getHoistedNote();
let searchHome;
if (!hoistedNote.isRoot()) {
searchHome = hoistedNote.searchNoteInSubtree('#hoistedSearchHome')
return hoistedNote.searchNoteInSubtree('#hoistedSearchHome')
|| hoistedNote.searchNoteInSubtree('#searchHome')
|| hoistedNote;
}
else {
} else {
const today = dateUtils.localNowDate();
searchHome = hoistedNote.searchNoteInSubtree('#searchHome')
return hoistedNote.searchNoteInSubtree('#searchHome')
|| dateNoteService.getDateNote(today);
}
}
return searchNote.cloneTo(searchHome.noteId);
function saveSearchNote(searchNoteId) {
const searchNote = becca.getNote(searchNoteId);
const searchHome = getSearchHome();
const result = searchNote.cloneTo(searchHome.noteId);
for (const parentBranch of searchNote.getParentBranches()) {
if (parentBranch.parentNote.hasAncestor("hidden")) {
parentBranch.markAsDeleted();
}
}
return result;
}
function getHoistedNote() {

View File

@@ -22,7 +22,7 @@ class TaskContext {
this.increaseProgressCount();
}
/** @return {TaskContext} */
/** @returns {TaskContext} */
static getInstance(taskId, taskType, data) {
if (!taskContexts[taskId]) {
taskContexts[taskId] = new TaskContext(taskId, taskType, data);

View File

@@ -298,6 +298,10 @@ function normalize(str) {
return removeDiacritic(str).toLowerCase();
}
function filterAttributeName(name) {
return name.replace(/[^\p{L}\p{N}_:]/ug, "");
}
module.exports = {
randomSecureToken,
randomString,
@@ -331,5 +335,6 @@ module.exports = {
timeLimit,
deferred,
removeDiacritic,
normalize
normalize,
filterAttributeName
};

View File

@@ -58,6 +58,12 @@
};
</script>
<style>
.note-split {
max-width: <%= maxContentWidth %>px;
}
</style>
<!-- Required for correct loading of scripts in Electron -->
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>

View File

@@ -1,5 +1,13 @@
<div id="note-revisions-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
<style>
#note-revision-content-wrapper {
flex-grow: 1;
margin-left: 20px;
display: flex;
flex-direction: column;
min-width: 0;
}
#note-revision-content {
overflow: auto;
word-break: break-word;
@@ -9,6 +17,12 @@
max-width: 100%;
object-fit: contain;
}
#note-revision-content pre {
max-width: 100%;
word-break: break-all;
white-space: pre-wrap;
}
</style>
<div class="modal-dialog modal-xl" role="document">
@@ -34,7 +48,7 @@
<div id="note-revision-list" style="position: static; height: 100%; overflow: auto;" class="dropdown-menu"></div>
</div>
<div id="note-revision-content-wrapper" style="flex-grow: 1; margin-left: 20px; display: flex; flex-direction: column;">
<div id="note-revision-content-wrapper">
<div style="flex-grow: 0; display: flex; justify-content: space-between;">
<h3 id="note-revision-title" style="margin: 3px; flex-grow: 100;"></h3>

View File

@@ -134,12 +134,12 @@
<p>This setup needs to be initiated from the desktop instance:</p>
<ol>
<li>please open your desktop instance of Trilium Notes</li>
<li>click on Options button in the top right</li>
<li>click on Sync tab</li>
<li>configure server instance address to the: <span id="current-host"></span> and click save.</li>
<li>click on "Test sync" button</li>
<li>once you've done all this, click <a href="/">here</a></li>
<li>Open your desktop instance of Trilium Notes.</li>
<li>From the Trilium Menu, click Options.</li>
<li>Click on Sync tab.</li>
<li>Change server instance address to: <span id="current-host"></span> and click save.</li>
<li>Click "Test sync" button to verify connection is successfull.</li>
<li>Once you've completed these steps, click <a href="/">here</a>.</li>
</ol>
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>