Compare commits

...

387 Commits

Author SHA1 Message Date
Elian Doran
b811f3d399 release 0.90.0-beta 2024-07-14 22:32:17 +03:00
Elian Doran
bbbbc3b860 bin/build*: Adjust error tolerance 2024-07-14 21:48:15 +03:00
Elian Doran
c9f27547a2 bin/build*: Check if dpkg-deb tool is correct 2024-07-14 21:45:48 +03:00
Elian Doran
6bf063b92f bin/build*: Check that required binaries are present 2024-07-14 21:35:08 +03:00
Elian Doran
590442b0d5 client,server: Trilium Notes -> TriliumNext Notes (closes #195) 2024-07-14 21:20:42 +03:00
Elian Doran
4862b324a5 bin/release: Get rid of sponge 2024-07-14 21:13:06 +03:00
Elian Doran
f678c4337b bin/release: Fail if jq is missing 2024-07-14 21:12:51 +03:00
Elian Doran
686b9c840e release 0.9.0 2024-07-14 21:04:43 +03:00
Elian Doran
2e8b2d4b81 server: Fix empty note titles (closes #205) 2024-07-14 20:50:42 +03:00
Elian Doran
3d423d25f8 Merge pull request #208 from TriliumNext/bugfix/fix_importing_non_text_files
Unable to import a PDF file
2024-07-14 17:14:04 +03:00
Elian Doran
79123b0c63 server: Fix import of non-text files 2024-07-14 15:21:45 +03:00
Elian Doran
595402adea dev: Fix npm start-electron script 2024-07-14 13:03:06 +03:00
Elian Doran
f40257b591 Merge pull request #206 from TriliumNext/bugfix/image_upload_regression
server: Fix regression in uploading images
2024-07-14 11:07:34 +03:00
Elian Doran
b6ca2281f7 Merge pull request #199 from TriliumNext/feature/port_0.63.7
Port 0.63.7
2024-07-14 10:17:18 +03:00
Elian Doran
186650b1cf ci: Add workflow to build macOS ARM64 2024-07-14 10:12:12 +03:00
Elian Doran
1dad4bc80f Merge remote-tracking branch 'origin/develop' into feature/port_0.63.7 2024-07-14 09:58:37 +03:00
Elian Doran
d66a70f099 Merge pull request #201 from TriliumNext/feature/fix_build_scripts
Fix build scripts & add CI for running them
2024-07-14 09:58:00 +03:00
Elian Doran
0792714e45 server: Fix regression in uploading images
The attachment is first saved with no content while the image is being
asynchronously resized. On our side we had a guard condition fail if the
content was empty, whereas the original implementation was simply using
an empty string instead.
2024-07-14 09:52:01 +03:00
Elian Doran
6ecbf1c528 Merge pull request #198 from TriliumNext/bugfix/note_import_type
Fix import note type
2024-07-14 00:51:04 +03:00
Elian Doran
5460359753 Merge pull request #196 from TriliumNext/feature/fix_docker_build
Fix Docker build
2024-07-14 00:43:33 +03:00
Elian Doran
75ebe24abb ci: Remove temporary branch config 2024-07-14 00:43:04 +03:00
Elian Doran
3f5e5fd840 ci: Set up Wine 2024-07-14 00:30:49 +03:00
Elian Doran
998db5ce95 ci: Add build Windows 2024-07-14 00:14:30 +03:00
Elian Doran
7aed6d4ad4 ci: Add build Linux Server 2024-07-13 23:16:35 +03:00
Elian Doran
99ea741275 ci: Add build Linux 2024-07-13 22:52:28 +03:00
Elian Doran
e6ff99198f ci: Fix docker build after macOS integration 2024-07-13 22:10:57 +03:00
Elian Doran
7c43a49046 ci: Add workflow for building macOS x86_64 2024-07-13 21:39:22 +03:00
Elian Doran
5b30bae5b2 bin: Adapt copy-trilium to TypeScript changes 2024-07-13 21:11:03 +03:00
Elian Doran
947aaa6156 webpack: Switch to TypeScript-based config 2024-07-13 21:10:40 +03:00
Elian Doran
534fe87fb1 Merge pull request #200 from TriliumNext/feature/basic_ci
Basic CI pipeline
2024-07-13 20:21:23 +03:00
Elian Doran
1652a7a2ec ci: Remove test branch 2024-07-13 20:04:00 +03:00
Elian Doran
7184c648ab ci: Test Docker publishing 2024-07-13 19:39:56 +03:00
Elian Doran
be239d2230 ci: Set up node environment & cache 2024-07-13 19:11:25 +03:00
Elian Doran
439bf410ac ci: Use newer versions of actions 2024-07-13 19:05:39 +03:00
Elian Doran
5aadcb69f8 Merge branch 'feature/fix_docker_build' into feature/basic_ci 2024-07-13 19:02:57 +03:00
Elian Doran
543c29e539 server: Change build directory 2024-07-13 19:02:51 +03:00
Elian Doran
8bcff49198 ci: Install npm dependencies 2024-07-13 18:57:13 +03:00
Elian Doran
c0cc1e3b52 ci: Run tsc & create server-package.json 2024-07-13 18:56:18 +03:00
Elian Doran
19550eec6a ci: Set up Docker build in dev workflow 2024-07-13 18:51:46 +03:00
Elian Doran
45f2691053 ci: Disable original CI workflows 2024-07-13 18:47:53 +03:00
Elian Doran
ba2931b854 Update package.json 2024-07-13 18:41:36 +03:00
Elian Doran
d5c1bd1085 Merge remote-tracking branch 'upstream/master' into feature/port_0.63.7
; Conflicts:
;	package-lock.json
;	src/routes/api/files.ts
;	src/services/build.js
;	src/services/notes.ts
2024-07-13 18:36:04 +03:00
Elian Doran
606490a611 server: Remove log and fix whitespace 2024-07-13 16:56:13 +03:00
Elian Doran
d99cc11d8b server: Fix import of notes with type=file 2024-07-13 16:52:31 +03:00
Elian Doran
1b0690ddfc server: Fix note type import
A regression caused by the port to TypeScript caused all note types to
be treated as a "text" instead of other types such as canvas. The MIME
type, however, was unaffected.
2024-07-13 16:43:33 +03:00
Elian Doran
ef92d17272 Revert change to tsconfig.json 2024-07-13 14:02:19 +03:00
Elian Doran
7fd63e8064 docker: Remove unneeded build directory 2024-07-13 11:45:20 +03:00
Elian Doran
d18bfb9d69 docker: Fix build by generating artifacts 2024-07-13 11:40:52 +03:00
Elian Doran
60d134a89b Revert "docker: Fix webpack"
This reverts commit 0a7fcf399f.
2024-07-13 11:32:02 +03:00
Elian Doran
9b88228be9 docker: Cache system dependencies installation 2024-07-13 11:14:32 +03:00
Elian Doran
0a7fcf399f docker: Fix webpack 2024-07-13 11:06:45 +03:00
Elian Doran
408369fcac docker: Add build directory to gitignore 2024-07-13 10:54:37 +03:00
Elian Doran
c435b97e10 docker: Fix "Missing script: "rebuild"" 2024-07-13 10:54:29 +03:00
Elian Doran
e7c3dab56f Merge pull request #194 from TriliumNext/feature/disable_format_on_save
Disable format on save temporarily
2024-07-13 09:48:04 +03:00
Elian Doran
374b1f9bb2 Disable format on save for VS Code 2024-07-13 00:27:53 +03:00
MeIchthys
5f23a4e3c7 Fix TrilumRocks Link 2024-07-03 01:15:31 -04:00
meichthys
4e93209fbd - Update chat link to link to Matrix space instead of single room
- Emphasize Awesome Trilium repo
- Remove Ukraine images
- Add note about links still referring to original repo
- Emojify all headings
- Add "Why TriliumNext" section
2024-07-03 01:14:39 -04:00
meichthys
1cff016923 Revert "Update README.md (#171)"
This reverts commit a80a857e46.
2024-07-03 00:29:41 -04:00
MeIchthys
a80a857e46 Update README.md (#171)
* Emojify readme and remove original repo specific items (links, images, maintenance mode notice, etc)

* Update readme links to point to TriliumNext repo

* Add link to TriliumRocks
2024-07-02 08:08:44 -04:00
perfectra1n
47c5f60a85 Merge pull request #179 from TriliumNext/perfectra1n-patch-1
Add "unofficial" section, and add some fancier wording
2024-06-17 17:57:24 -07:00
perfectra1n
ec2e6f2507 Update README.md 2024-06-15 22:30:35 -07:00
perfectra1n
8eb1e803b5 Add "unofficial" section, and add some fancier wording around "discuss with us" 2024-06-15 22:28:30 -07:00
Elian Doran
44ad22ceea Merge pull request #167 from TriliumNext/feat/chatroom-links
Include links to chatrooms in readme
2024-06-03 18:55:16 +03:00
alexpietsch
40dfc46250 add links to readme 2024-05-31 19:37:09 +02:00
Alex
9775a8b7c5 feat: Add prettier config (#165)
* add prettier config

* format some files

* Revert "format some files"

This reverts commit 2c5681ba88c422a92737a9c109021aef8deff2f4.
2024-05-31 12:04:42 -04:00
zadam
82a437f2a8 remove unnecessary debounce which caused missed changes 2024-05-30 06:55:45 +02:00
zadam
a64a0e52ec Merge branch 'refs/heads/stable'
# Conflicts:
#	package-lock.json
2024-05-30 06:46:36 +02:00
zadam
88c4171031 lock file 2024-05-30 06:46:22 +02:00
zadam
b6c7e2e48f error handling 2024-05-30 06:25:03 +02:00
zadam
305d28b5b3 remove warning about the relation map - trilium is sunsetting anyway, there's no point now in deprecating this feature, fixes #4779 2024-05-30 06:24:50 +02:00
zadam
a88bf68eb6 verify that the uploaded modified file is temporary
(cherry picked from commit a2711cfb7b)
2024-05-30 06:10:30 +02:00
zadam
8ebebecd93 Merge pull request #4772 from gigamonster256/macos-arm64
Support compiling native MacOS arm64 (Apple Silicon) builds
2024-05-30 06:06:08 +02:00
Caleb Norton
f0d30dbe49 Add macos arm64 to release script 2024-05-18 03:48:33 -05:00
Caleb Norton
d142d3261d support compiling native arm64 macos builds 2024-05-18 03:34:24 -05:00
zadam
25b49e1ca2 release 0.63.6 2024-05-18 06:17:21 +02:00
zadam
c7f19e04fa Merge remote-tracking branch 'origin/master' 2024-05-18 05:50:53 +02:00
zadam
a2711cfb7b verify that the uploaded modified file is temporary 2024-05-18 05:50:46 +02:00
Elian Doran
ade6d08ded Merge pull request #67 from TriliumNext/fix/EtapiError-prototype
fix: set prototype
2024-05-08 21:37:36 +03:00
Elian Doran
045f318612 Merge pull request #60 from TriliumNext/fix/start-electron
fix: start-electron script
2024-05-08 21:36:36 +03:00
Elian Doran
2d67abbc12 Merge branch 'zadam:master' into develop 2024-05-07 20:44:45 +03:00
Alex
5f539427a9 fix: set prototype 2024-05-05 12:40:00 +02:00
zadam
f984b361ee Merge pull request #4717 from MatMasIt/italian-translation
Italian translation of README
2024-05-05 05:13:26 +02:00
Alex
3e82d30a32 fix: skipLibCheck 2024-05-01 11:32:08 +02:00
alexpietsch
d2604e91d4 feat: add ts script for cross plattform use 2024-05-01 00:05:24 +02:00
Alex
e2b4eaf9cc fix: copy only necessary modules 2024-04-29 23:37:08 +02:00
Alex
0a69ed91b6 fix: add postinstall step 2024-04-29 14:58:16 +02:00
Alex
8c0ce174b5 fix: script permissions 2024-04-29 14:54:39 +02:00
alexpietsch
c8adf2a685 fix: restore start-electron script functionality 2024-04-29 00:36:27 +02:00
Elian Doran
a68b75f069 Merge pull request #48 from TriliumNext/feature/typescript_backend_11
Convert backend to TypeScript (89% -> 92%, final)
2024-04-20 09:37:19 +03:00
Elian Doran
2771bd4ece Merge pull request #47 from TriliumNext/feature/typescript_backend_10
Convert backend to TypeScript (84% -> 89%)
2024-04-20 09:36:08 +03:00
Elian Doran
19cf9df52c Merge pull request #45 from TriliumNext/feature/typescript_backend_9
Convert backend to TypeScript (81% -> 84%)
2024-04-20 09:35:18 +03:00
Elian Doran
15168fb213 server-ts: Use unknown for validators 2024-04-18 21:26:29 +03:00
Elian Doran
0d700d6951 server-ts: Address review 2024-04-17 23:00:02 +03:00
Elian Doran
67cb02ed92 server-ts: Address some more review comments 2024-04-17 22:54:05 +03:00
Elian Doran
1d1ccc8d63 server-ts: Fix regression 2024-04-17 22:49:41 +03:00
Elian Doran
262e4db0f2 server-ts: Remove unnecessary type comments 2024-04-17 22:35:38 +03:00
Elian Doran
c814187a25 server-ts: Address review 2024-04-17 22:24:27 +03:00
Elian Doran
138be84e45 server-ts: Address requested changes 2024-04-16 21:10:39 +03:00
Elian Doran
625d935f08 Merge pull request #44 from TriliumNext/feature/typescript_backend_8
Convert backend to TypeScript (80% -> 81%)
2024-04-16 21:01:20 +03:00
Elian Doran
98d12901a5 Merge pull request #43 from TriliumNext/feature/typescript_backend_7
Convert backend to TypeScript (71% -> 80%)
2024-04-15 21:51:00 +03:00
Elian Doran
1372cc1cb9 server-ts: Fix regression 2024-04-15 21:20:59 +03:00
Elian Doran
a072016fc5 Merge pull request #42 from TriliumNext/feature/typescript_backend_6
Convert backend to TypeScript (67% -> 71%)
2024-04-15 21:12:55 +03:00
Elian Doran
97230ca82a server-ts: Remove some esdoc types 2024-04-15 21:12:34 +03:00
Elian Doran
4c69384b5d Merge pull request #41 from TriliumNext/feature/typescript_backend_5
Convert backend to TypeScript (64% -> 67%)
2024-04-15 21:09:37 +03:00
Elian Doran
6ac3c172b1 Merge pull request #28 from TriliumNext/feature/typescript_backend_4
Convert backend to TypeScript (50% -> 64%)
2024-04-15 21:00:38 +03:00
Elian Doran
a66e4435ba server-ts: Address requested changes 2024-04-13 17:30:48 +03:00
Elian Doran
ae29699e65 server-ts: Port www 2024-04-11 23:14:37 +03:00
Elian Doran
9022bc338a server-ts: Port app 2024-04-11 23:03:19 +03:00
Elian Doran
dededcd303 server-ts: Port routes/routes 2024-04-11 23:00:24 +03:00
Elian Doran
4638351ec8 server-ts: Update progress script 2024-04-11 22:21:38 +03:00
Elian Doran
1ac65fff47 server-ts: Address requested changes 2024-04-11 19:49:01 +03:00
Elian Doran
17eda952e4 Merge branch 'feature/typescript_backend_5' into feature/typescript_backend_10 2024-04-10 19:29:00 +03:00
Elian Doran
8629993fe4 Merge branch 'feature/typescript_backend_5' into feature/typescript_backend_9 2024-04-10 19:28:36 +03:00
Elian Doran
8297e1a835 Merge branch 'feature/typescript_backend_5' into feature/typescript_backend_8 2024-04-10 19:28:10 +03:00
Elian Doran
b517b18394 Merge branch 'feature/typescript_backend_5' into feature/typescript_backend_7 2024-04-10 19:27:31 +03:00
Elian Doran
32e34eec90 Merge branch 'feature/typescript_backend_5' into feature/typescript_backend_6 2024-04-10 19:26:30 +03:00
Elian Doran
fb32ab9707 server-ts: Address requested changes 2024-04-10 19:26:08 +03:00
Elian Doran
8acfb5b558 Merge pull request #25 from TriliumNext/feature/typescript_backend_3
Convert backend to TypeScript (35% -> 50%)
2024-04-10 19:16:29 +03:00
Elian Doran
a7ae16bb39 Merge pull request #18 from TriliumNext/feature/typescript_backend_2
Convert backend to TypeScript (19% -> 35%)
2024-04-10 19:16:06 +03:00
Elian Doran
c08393f04b server-ts: Port share/routes 2024-04-10 19:04:38 +03:00
Elian Doran
88aba1c844 server-ts: Port share/shaca/content_renderer 2024-04-09 22:58:24 +03:00
Elian Doran
7c76d28f75 server-ts: Port share/shaca/shaca_loader 2024-04-09 22:49:05 +03:00
Elian Doran
b3c2602620 server-ts: Port share/shaca/shaca 2024-04-09 22:42:48 +03:00
Elian Doran
0865e90cae server-ts: Port share/shaca/sbranch 2024-04-09 22:33:59 +03:00
Elian Doran
e1d74cd2f5 server-ts: Port share/shaca/sattribute 2024-04-09 22:29:40 +03:00
Elian Doran
c4c2259e69 server-ts: Port share/shaca/sattachment 2024-04-09 22:21:07 +03:00
Elian Doran
3e4b0d5f91 server-ts: Port share/shaca/snote 2024-04-09 22:13:01 +03:00
Elian Doran
b07df6061f server-ts: Port share/share_root 2024-04-09 21:51:23 +03:00
Elian Doran
80a1b8b44d server-ts: Port share/sql 2024-04-09 21:50:47 +03:00
Elian Doran
45e3632c6e server-ts: Port share/shaca/abstract_shaca_entity 2024-04-09 21:48:15 +03:00
Elian Doran
39e152b0b9 Merge branch 'feature/typescript_backend_6' into feature/typescript_backend_10 2024-04-09 21:46:24 +03:00
Elian Doran
2a99ecc384 Merge branch 'feature/typescript_backend_6' into feature/typescript_backend_9 2024-04-09 21:45:40 +03:00
Elian Doran
22477f90ae Merge branch 'feature/typescript_backend_6' into feature/typescript_backend_8 2024-04-09 21:45:04 +03:00
Elian Doran
d8c2ba0ed8 Merge branch 'feature/typescript_backend_6' into feature/typescript_backend_7 2024-04-09 21:44:26 +03:00
Elian Doran
74441273a8 server-ts: Fix getAttribute permitting null 2024-04-09 21:43:42 +03:00
Elian Doran
ed79c1c62a server-ts: Convert etapi/special_notes 2024-04-07 18:21:18 +03:00
Elian Doran
9b9b452055 server-ts: Convert etapi/spec 2024-04-07 18:18:26 +03:00
Elian Doran
5fdf094e9d server-ts: Convert etapi/notes 2024-04-07 16:56:45 +03:00
Elian Doran
e4024408bd server-ts: Convert etapi/bbranches 2024-04-07 15:21:42 +03:00
Elian Doran
602b4988ae server-ts: Convert etapi/backup 2024-04-07 15:18:35 +03:00
Elian Doran
071f9400d7 server-ts: Convert etapi/auth 2024-04-07 15:17:45 +03:00
Elian Doran
1e2a30adcc server-ts: Convert etapi/attributes 2024-04-07 15:15:50 +03:00
Elian Doran
a6de065bf4 server-ts: Convert etapi/attachments 2024-04-07 15:13:34 +03:00
Elian Doran
3bd7231ba9 server-ts: Convert etapi/validators 2024-04-07 14:59:40 +03:00
Elian Doran
4bb46aeb9c server-ts: Convert etapi/mappers 2024-04-07 14:56:22 +03:00
Elian Doran
26859e83e4 server-ts: Convert etapi/app_info 2024-04-07 14:54:42 +03:00
Elian Doran
569bdf19be server-ts: Convert etapi/etapi_utils 2024-04-07 14:54:01 +03:00
Elian Doran
b8eb301f34 server-ts: Add types for multer 2024-04-07 14:38:51 +03:00
Elian Doran
10c21d3403 server-ts: Convert routes/index 2024-04-07 14:36:47 +03:00
Elian Doran
a18b054dbb server-ts: Convert routes/setup 2024-04-07 14:33:41 +03:00
Elian Doran
2e906af77d server-ts: Convert routes/session_parser 2024-04-07 14:32:08 +03:00
Elian Doran
34cd2eba91 server-ts: Remove use of (req as any) 2024-04-07 14:29:08 +03:00
Elian Doran
7fe6d1ab4d server-ts: Convert routes/login 2024-04-07 14:22:01 +03:00
Elian Doran
c2eefad287 server-ts: Convert routes/error_handlers 2024-04-07 14:13:57 +03:00
Elian Doran
202fda3c30 server-ts: Convert routes/electron 2024-04-07 14:09:37 +03:00
Elian Doran
4940207b15 server-ts: Convert routes/custom 2024-04-07 14:05:50 +03:00
Elian Doran
79c9d7e305 server-ts: Convert routes/assets 2024-04-07 14:02:52 +03:00
Elian Doran
6e042c20e9 server-ts: Convert routes/api/tree 2024-04-06 23:34:47 +03:00
Elian Doran
7a98718e64 server-ts: Convert routes/api/sync 2024-04-06 23:28:51 +03:00
Elian Doran
6bbb1f8404 server-ts: Convert routes/api/stats 2024-04-06 23:15:00 +03:00
Elian Doran
cecfc4cd34 server-ts: Convert routes/api/sql 2024-04-06 23:12:22 +03:00
Elian Doran
c1875a8c8f server-ts: Convert routes/api/special_notes 2024-04-06 23:11:02 +03:00
Elian Doran
249e81c9eb server-ts: Convert routes/api/similar_notes 2024-04-06 23:09:32 +03:00
Elian Doran
5804dc52bc server-ts: Convert routes/api/setup 2024-04-06 23:08:41 +03:00
Elian Doran
90cf913083 server-ts: Convert routes/api/sender 2024-04-06 23:01:08 +03:00
Elian Doran
fa82158e30 server-ts: Convert routes/api/search 2024-04-06 22:48:20 +03:00
Elian Doran
de42df40bb server-ts: Convert routes/api/script 2024-04-06 22:38:17 +03:00
Elian Doran
6265aa99d3 server-ts: Convert routes/api/revisions 2024-04-06 22:32:03 +03:00
Elian Doran
4ab6f159e5 server-ts: Fix "Missing or incorrect type for target branch ID"
When attempting to add a new note from the relation map
2024-04-06 22:17:47 +03:00
Elian Doran
b50ceaf299 server-ts: Convert routes/api/relation-map 2024-04-06 22:13:47 +03:00
Elian Doran
96c8c9080d server-ts: Convert routes/api/recent_notes 2024-04-06 22:07:58 +03:00
Elian Doran
66d7548046 server-ts: Convert routes/api/recent_changes 2024-04-06 22:07:03 +03:00
Elian Doran
4b1c351195 server-ts: Convert routes/api/password 2024-04-06 22:00:03 +03:00
Elian Doran
eb7a7e4988 server-ts: Convert routes/api/other 2024-04-06 21:58:32 +03:00
Elian Doran
3d75366f02 server-ts: Convert routes/api/options 2024-04-06 21:57:26 +03:00
Elian Doran
c63c7d518c server-ts: Convert routes/api/notes 2024-04-06 21:55:27 +03:00
Elian Doran
37697c7db7 server-ts: Convert routes/api/note_map 2024-04-06 21:45:58 +03:00
Elian Doran
b1744c3867 server-ts: Convert routes/api/login 2024-04-06 21:34:34 +03:00
Elian Doran
b0d6035a67 server-ts: Convert routes/api/keys 2024-04-06 21:31:09 +03:00
Elian Doran
3902719008 server-ts: Convert routes/api/import 2024-04-06 21:30:27 +03:00
Elian Doran
291b791b67 server-ts: Convert routes/api/image 2024-04-06 21:21:22 +03:00
Elian Doran
b552f40ae8 server-ts: Convert routes/api/fonts 2024-04-05 22:24:21 +03:00
Elian Doran
0ab137de03 server-ts: Convert routes/api/files 2024-04-05 22:22:18 +03:00
Elian Doran
952c3cc12f server-ts: Convert routes/api/export 2024-04-05 20:58:31 +03:00
Elian Doran
e451237361 server-ts: Convert routes/api/etapi_tokens 2024-04-05 20:56:23 +03:00
Elian Doran
27637b0483 server-ts: Convert routes/api/consistency_checks 2024-04-05 20:55:21 +03:00
Elian Doran
122ff3bb1d server-ts: Convert routes/api/cloning 2024-04-05 20:47:07 +03:00
Elian Doran
f98f84d419 server-ts: Convert routes/api/bnote 2024-04-05 20:45:57 +03:00
Elian Doran
40ef533c5f server-ts: Convert routes/api/bulk_action 2024-04-05 20:36:10 +03:00
Elian Doran
fd77c5e8c4 server-ts: Convert routes/api/branches 2024-04-05 20:33:04 +03:00
Elian Doran
706b9d0f46 server-ts: Convert routes/api/backend_log 2024-04-05 20:28:19 +03:00
Elian Doran
82f5553980 server-ts: Convert routes/api/autocomplete 2024-04-05 20:26:45 +03:00
Elian Doran
c0349b3f84 server-ts: Convert routes/api/attributes 2024-04-05 20:22:10 +03:00
Elian Doran
9330241045 server-ts: Convert routes/api/attachments 2024-04-05 20:16:46 +03:00
Elian Doran
b13ad5d01e server-ts: Convert routes/api/app_info 2024-04-05 20:12:54 +03:00
Elian Doran
ea36b37f66 server-ts: Convert services/scheduler 2024-04-04 23:08:32 +03:00
Elian Doran
92ca32bd70 server-ts: Convert services/handlers 2024-04-04 23:04:54 +03:00
Elian Doran
59c533cb6c server-ts: Convert services/script 2024-04-04 22:47:58 +03:00
Elian Doran
884b6618fb server-ts: Convert services/script_context 2024-04-04 22:29:12 +03:00
Elian Doran
15dee4b952 server-ts: Convert services/backend_script_api 2024-04-04 22:04:30 +03:00
Elian Doran
a154dc76ce server-ts: Port services/options 2024-04-03 23:28:26 +03:00
Elian Doran
532ed1d3f9 server-ts: Port services/setup 2024-04-03 23:18:39 +03:00
Elian Doran
984ce49168 server-ts: Port services/special_notes 2024-04-03 23:05:06 +03:00
Elian Doran
16283d4054 server-ts: Port services/spaced_update 2024-04-03 22:53:41 +03:00
Elian Doran
915de23e34 server-ts: Port services/anonymize 2024-04-03 22:51:07 +03:00
Elian Doran
53d4873c1f server-ts: Port services/import/zip 2024-04-03 22:46:14 +03:00
Elian Doran
764d251b0a server-ts: Port services/import/enex 2024-04-03 21:29:26 +03:00
Elian Doran
aa233b8adb server-ts: Port consistency_checks 2024-04-03 20:48:54 +03:00
Elian Doran
ed47c23e23 Merge branch 'feature/typescript_backend_3' into feature/typescript_backend_4 2024-04-03 20:21:55 +03:00
Elian Doran
5baabecdee Merge branch 'feature/typescript_backend_2' into feature/typescript_backend_3 2024-04-03 20:21:13 +03:00
Elian Doran
17c7e2d8e7 server-ts: Address further suggestions 2024-04-03 20:04:20 +03:00
Elian Doran
5d452a1525 server-ts: Address review 2024-04-03 19:22:49 +03:00
Elian Doran
f857b8a9bb server-ts: Refactor out abstract init in entities 2024-04-03 19:05:10 +03:00
Elian Doran
a354b54a08 server-ts: Fix getContent in updateNoteData 2024-04-03 18:53:56 +03:00
Elian Doran
e96b56e061 server-ts: Fix build errors 2024-04-03 00:02:51 +03:00
Elian Doran
0daa4cc89a Merge branch 'feature/typescript_backend_3' into feature/typescript_backend_4 2024-04-02 23:57:01 +03:00
Elian Doran
99d50957dd server-ts: Address self-review 2024-04-02 23:55:02 +03:00
Elian Doran
d4c8d24d50 server-ts: Address some review comments 2024-04-02 23:39:45 +03:00
Elian Doran
a420129631 server-ts: Fix build errors after merge 2024-04-02 23:34:12 +03:00
Elian Doran
090d353fd0 Merge branch 'feature/typescript_backend_2' into feature/typescript_backend_3 2024-04-02 23:26:16 +03:00
Elian Doran
b84b27692c server-ts: Fix some issues from self-review 2024-04-02 23:22:45 +03:00
Elian Doran
adc384a971 Merge pull request #13 from TriliumNext/feature/typescript_backend
Convert backend to TypeScript (0% -> 19%)
2024-04-02 22:30:39 +03:00
Elian Doran
08f0c01eef server-ts: Solve build errors after merge 2024-03-30 11:09:45 +02:00
Elian Doran
db2b33704f Merge branch 'feature/typescript_backend' into feature/typescript_backend_2 2024-03-30 10:54:06 +02:00
Elian Doran
3eb7ed5dda server-ts: Address requested changes 2024-03-30 10:49:40 +02:00
Elian Doran
5e5add7e47 Merge remote-tracking branch 'origin/develop' into feature/typescript_backend 2024-03-30 09:23:00 +02:00
Mattia Mascarello
ac1e1ebe43 Forgot to translate a heading 2024-03-28 20:51:28 +01:00
Mattia Mascarello
499bfaea9b Added italian to other READMEs language section 2024-03-28 20:42:53 +01:00
Mattia Mascarello
fa4772b91e proofreading 2024-03-28 20:38:20 +01:00
Mattia Mascarello
fa7b190c6b Translated readme in italian 2024-03-28 19:23:01 +01:00
zadam
84feec2e7e Merge branch 'stable'
# Conflicts:
#	package-lock.json
2024-03-28 07:26:17 +01:00
zadam
3035473751 release 0.63.5 2024-03-28 07:11:39 +01:00
zadam
399458b52f package lock 2024-03-28 07:08:31 +01:00
zadam
becac5fbad fix rendering of math on non-root paths, closes #4713 2024-03-28 07:08:15 +01:00
zadam
1e26b31090 release 0.63.4 2024-03-28 07:00:01 +01:00
zadam
0ebd03869d use "n space" instead of zero length space to make it obvious there is a character there 2024-03-28 06:57:44 +01:00
zadam
a4a713f102 allow cancelling mention UI with escape, fixes #4692, #4684 2024-03-28 06:43:26 +01:00
zadam
83d5d6bbd8 fix copy link in browser, closes #4682 2024-03-27 20:42:36 +01:00
zadam
90f4c6b0d1 allow converting file attachments to file notes 2024-03-27 06:59:57 +01:00
Elian Doran
926b3e9650 Merge remote-tracking branch 'origin/develop' into feature/typescript_backend 2024-03-17 21:44:06 +02:00
Elian Doran
cbad58201e server-ts: Solve requested changes 2024-03-17 21:40:14 +02:00
Elian Doran
040ed39a4e server-ts: Fix undefined in task_context 2024-03-17 21:37:40 +02:00
Elian Doran
9f6a8dc75c server-ts: Fix undefined in ws 2024-03-17 21:34:50 +02:00
zadam
bfb8aa6481 remove eslint 2024-03-06 07:17:51 +01:00
zadam
2bb4cccd82 fix excalidraw 0.17 integration 2024-03-06 07:13:30 +01:00
zadam
6fad5f2b51 Merge branch 'master' into excali-17-2 2024-03-06 06:53:19 +01:00
zadam
36357bdf86 update mermaid 2024-03-06 06:52:41 +01:00
zadam
80eaf10656 remove husky 2024-03-06 06:51:25 +01:00
zadam
f46ce0417d more prettier removals 2024-03-06 06:48:25 +01:00
zadam
6f1e6402f0 package upgrades + removal of prettier 2024-03-06 06:47:30 +01:00
zadam
bdfa13a8a0 Merge pull request #4676 from st3iny/fix/invisible-unchecked-todo
fix: invisible unchecked to-do items
2024-03-06 06:32:23 +01:00
Richard Steinmetz
d5622dfbf7 fix: invisible unchecked to-do items 2024-03-05 22:29:10 +01:00
zadam
0948853539 release 0.63.3 2024-03-03 06:58:18 +01:00
zadam
0ad337c8e8 add API method to erase a revision #4662 2024-03-03 06:34:43 +01:00
zadam
9565b0b43d Merge pull request #4671 from AlexeiKharchev/master
Reduce warnings count caused by bin/copy-trilium.sh
2024-03-03 06:26:18 +01:00
zadam
a115b14136 fix highlighting search results with regexp characters in fulltext string, closes #4665 2024-03-02 07:35:52 +01:00
Alexei Kharchev
bb8bfc61ed Improved scriptes bin/copy-trilium.sh, bin/build-linux-x64.sh 2024-03-02 14:18:39 +08:00
zadam
2d19f073d9 fix searching fulltext with tags, closes #4661 2024-03-02 07:13:02 +01:00
Alexei Kharchev
ff5d5d20d9 Reduce warnings count caused by bin/copy-trilium.sh 2024-03-02 11:49:17 +08:00
Elian Doran
8c5f680dca server-ts: Fix build errors 2024-02-25 15:06:43 +02:00
Elian Doran
7f0102181d server-ts: Port services/import/single 2024-02-25 14:52:20 +02:00
Elian Doran
59d618f06b server-ts: Port services/import/markdown 2024-02-25 08:12:07 +02:00
Elian Doran
cc1a545e13 server-ts: Port services/import/opml 2024-02-25 08:07:17 +02:00
Elian Doran
fd37fd3a45 server-ts: Port services/import/mime 2024-02-25 07:57:17 +02:00
Elian Doran
052a0a44f2 server-ts: Port services/import/utils 2024-02-25 07:54:51 +02:00
Elian Doran
fa0ed35752 server-ts: Port services/image 2024-02-20 23:29:03 +02:00
Elian Doran
29d37c40c1 server-ts: Port services/export/zip 2024-02-19 23:08:43 +02:00
Elian Doran
1728365fa1 server-ts: Port services/export/single 2024-02-19 22:12:00 +02:00
Elian Doran
cfeb0cc6f7 server-ts: Port services/export/opml 2024-02-19 22:07:21 +02:00
Elian Doran
ec4bd6659a server-ts: Port services/export/md 2024-02-19 21:59:40 +02:00
Elian Doran
0903cf2646 Merge branch 'feature/typescript_backend_2' into feature/typescript_backend_3 2024-02-18 20:43:27 +02:00
Elian Doran
cbc01e1a8d server-ts: FIXME -> TODO 2024-02-18 20:41:30 +02:00
Elian Doran
11c7533984 Merge branch 'feature/typescript_backend' into feature/typescript_backend_2 2024-02-18 20:39:18 +02:00
Elian Doran
aff1c30557 server-ts: FIXME -> TODO 2024-02-18 20:29:23 +02:00
Elian Doran
3d9da26bb3 server-ts: Address review comments 2024-02-18 18:11:56 +02:00
Elian Doran
91ddabbb9b server-ts: Remove redundant file 2024-02-18 13:44:41 +02:00
Elian Doran
3030fbe60b server-ts: Port services/notes 2024-02-18 13:42:05 +02:00
Elian Doran
5f4f30f84b server-ts: Port services/options_init 2024-02-18 13:32:00 +02:00
Elian Doran
45cb7ced2c server-ts: Port services/host 2024-02-18 13:26:28 +02:00
Elian Doran
dbccf6b433 server-ts: Port services/one_time_timer 2024-02-18 13:25:18 +02:00
Elian Doran
6df09cb157 server-ts: Port services/bulk_actions 2024-02-18 13:23:37 +02:00
Elian Doran
6cedad07e5 server-ts: Port services/cloning 2024-02-18 13:16:54 +02:00
Elian Doran
e4556afcc9 server-ts: Port services/session_secret 2024-02-18 13:13:16 +02:00
Elian Doran
d89b791914 server-ts: Port services/sync 2024-02-18 13:10:51 +02:00
Elian Doran
43c89c0e9d server-ts: Port services/content_hash 2024-02-18 12:50:58 +02:00
Elian Doran
0ba80b176c server-ts: Port services/sync_update 2024-02-18 12:40:30 +02:00
Elian Doran
3ea4b7a72b server-ts: Port services/tray 2024-02-18 12:28:32 +02:00
Elian Doran
330334dcb4 server-ts: Port services/window 2024-02-18 12:19:09 +02:00
Elian Doran
ddcbb29a67 server-ts: Update script to display remaining filelist 2024-02-18 11:50:22 +02:00
Elian Doran
9ea4fcd667 server-ts: Port services/branches 2024-02-18 11:48:38 +02:00
Elian Doran
0d4fb42731 server-ts: Port services/tree 2024-02-18 11:47:32 +02:00
Elian Doran
d8d729342d server-ts: Port services/attributes 2024-02-18 11:26:05 +02:00
Elian Doran
2fbd2e3c29 server-ts: Port services/search/services/search 2024-02-18 11:16:30 +02:00
Elian Doran
15169289f0 server-ts: Port services/search/services/parse 2024-02-18 02:27:04 +02:00
Elian Doran
29b3fb3646 server-ts: Port services/search/expressions/property_comparison 2024-02-18 01:50:16 +02:00
Elian Doran
533a597a5c server-ts: Port services/search/services/lex 2024-02-18 01:46:32 +02:00
Elian Doran
deed58c2fc server-ts: Port services/search/services/handle_parens 2024-02-18 01:38:51 +02:00
Elian Doran
8acb64198c server-ts: Port services/search/services/build_comparator 2024-02-18 01:38:42 +02:00
Elian Doran
f5b690d088 server-ts: Port services/search/expressions/relation_where 2024-02-18 01:19:50 +02:00
Elian Doran
0f7f0ceedc server-ts: Port services/search/expressions/parent_of 2024-02-18 01:18:20 +02:00
Elian Doran
87708aa9c3 server-ts: Port services/search/expressions/order_by_and_limit 2024-02-18 01:17:02 +02:00
Elian Doran
c63d05b582 server-ts: Port services/search/expressions/or 2024-02-18 01:06:42 +02:00
Elian Doran
460982d290 server-ts: Port services/search/expressions/note_flat_text 2024-02-18 01:05:34 +02:00
Elian Doran
414964e791 server-ts: Port services/search/expressions/note_content_fulltext 2024-02-18 01:01:17 +02:00
Elian Doran
3df6acda32 server-ts: Port services/search/expressions/not 2024-02-18 00:51:18 +02:00
Elian Doran
571c9f544c server-ts: Port services/search/expressions/label_comparison 2024-02-18 00:50:14 +02:00
Elian Doran
acb31b621f server-ts: Port services/search/expressions/is_hidden 2024-02-18 00:48:37 +02:00
Elian Doran
35886c3b00 server-ts: Port services/search/expressions/descendant_of 2024-02-18 00:47:25 +02:00
Elian Doran
ccc51e0123 server-ts: Port services/search/expressions/child_of 2024-02-18 00:46:25 +02:00
Elian Doran
0df488667e server-ts: Port services/search/expressions/attribute_exists 2024-02-18 00:45:21 +02:00
Elian Doran
7018cc77a3 server-ts: Port services/search/expressions/and 2024-02-18 00:43:55 +02:00
Elian Doran
8d2eddd14d server-ts: Port services/search/expressions/true 2024-02-18 00:42:17 +02:00
Elian Doran
414515bc87 server-ts: Port services/search/expressions/ancestor 2024-02-18 00:40:15 +02:00
Elian Doran
ce60fc0c3a server-ts: Port services/search/expressions/expression 2024-02-18 00:36:37 +02:00
Elian Doran
216f3f2c07 server-ts: Port services/search/value_extractor 2024-02-18 00:34:36 +02:00
Elian Doran
80e6ced5db server-ts: Port services/search/search_result 2024-02-18 00:30:16 +02:00
Elian Doran
fbf77f3382 server-ts: Port services/search/search_context 2024-02-18 00:28:05 +02:00
Elian Doran
1010d11827 server-ts: Port services/hoisted_note 2024-02-18 00:22:46 +02:00
Elian Doran
5d683721b1 server-ts: Change data type 2024-02-17 23:32:32 +02:00
Elian Doran
de7f4de05b server-ts: Port services/notes & hidden_subtree 2024-02-17 23:02:19 +02:00
Elian Doran
669988953d server-ts: Port services/request 2024-02-17 21:58:35 +02:00
Elian Doran
dc22d05657 server-ts: Port services/html_sanitizer 2024-02-17 21:33:47 +02:00
Elian Doran
00c692cf28 server-ts: Port services/note_types 2024-02-17 21:29:28 +02:00
Elian Doran
77fb7bc6e8 server-ts: Port services/erase 2024-02-17 21:13:04 +02:00
Elian Doran
f31d788e2e server-ts: Port services/auth 2024-02-17 21:08:56 +02:00
Elian Doran
45582ebaac server-ts: Port services/meta/* 2024-02-17 21:01:31 +02:00
Elian Doran
748a551def server-ts: Port similarity 2024-02-17 20:55:36 +02:00
Elian Doran
d35613f510 server-ts: Port becca_loader 2024-02-17 20:45:31 +02:00
Elian Doran
3d5ef81860 server-ts: Simplify deferred promise in sql_init 2024-02-17 20:33:18 +02:00
Elian Doran
9f99b4282a server-ts: Port becca/becca_service 2024-02-17 20:30:21 +02:00
Elian Doran
3b7812f829 server-ts: Port becca/entity_constructor 2024-02-17 20:28:05 +02:00
Elian Doran
1c7532df46 server-ts: Port sync_options 2024-02-17 20:24:32 +02:00
Elian Doran
a029ee268a server-ts: Fix export 2024-02-17 20:06:54 +02:00
Elian Doran
1b58dbe401 server-ts: Port etapi_tokens service 2024-02-17 19:55:40 +02:00
Elian Doran
56ca0ea5d0 server-ts: Port migration, port, sql_init services 2024-02-17 19:51:22 +02:00
Elian Doran
246a90d73c server-ts: Port revisions service 2024-02-17 19:44:46 +02:00
Elian Doran
a58abffe58 server-ts: Port migration service 2024-02-17 19:42:30 +02:00
Elian Doran
8936a3fa3b server-ts: Port backup service 2024-02-17 19:33:29 +02:00
Elian Doran
6b8ee084e6 server-ts: Port keyboard_actions 2024-02-17 19:29:15 +02:00
Elian Doran
5916a8b5b8 server-ts: Fix build errors 2024-02-17 19:20:32 +02:00
Elian Doran
a183f21078 server-ts: Convert attribute_formatter 2024-02-17 19:15:50 +02:00
Elian Doran
9826fed905 server-ts: Port app* services 2024-02-17 19:09:36 +02:00
Elian Doran
a06aa9df8a server-ts: Port anonymization service 2024-02-17 19:02:14 +02:00
Elian Doran
1e91db865b server-ts: Implement review comments 2024-02-17 18:55:41 +02:00
Elian Doran
8af5434462 server-ts: Add script to check progress 2024-02-17 13:32:43 +02:00
Elian Doran
6f0659c03c server-ts: Bring back nodemon 2024-02-17 13:32:43 +02:00
Elian Doran
b8ccf5ba8f server-ts: Fix error with sanitize_attribute_name 2024-02-17 13:32:43 +02:00
Elian Doran
45a6c9558f server-ts: Fix references to js files for converted files 2024-02-17 13:32:43 +02:00
Elian Doran
4df599fec2 server-ts: Fix some build errors 2024-02-17 13:32:43 +02:00
Elian Doran
030aec2f3e server-ts: Fix more references to js files for converted files 2024-02-17 13:32:43 +02:00
Elian Doran
8b250e4a13 server-ts: Fix references to js files for converted files 2024-02-17 13:32:43 +02:00
Elian Doran
2f96dc2d9d server-ts: Fix most type errors in becca 2024-02-17 13:32:43 +02:00
Elian Doran
26388ad3b6 server-ts: Fix errors in becca-interface 2024-02-17 13:32:43 +02:00
Elian Doran
2c0063a5cc server-ts: Remove .js extensions in src/becca 2024-02-17 13:32:43 +02:00
Elian Doran
3a20bef1a9 server-ts: Fix more errors in becca entities 2024-02-17 13:32:42 +02:00
Elian Doran
9aec3390dd server-ts: Fix most errors in bnote 2024-02-17 13:32:42 +02:00
Elian Doran
f9ba8ca87d server-ts: Fix errors in abstract_becca_entity 2024-02-17 13:32:42 +02:00
Elian Doran
f51f070b2f server-ts: Port bbranch (with some build errors) 2024-02-17 13:32:42 +02:00
Elian Doran
cf18e61a33 server-ts: Port ws service & dependencies 2024-02-17 13:32:42 +02:00
Elian Doran
4b1d2c6bad server-ts: Port options service 2024-02-17 13:32:42 +02:00
Elian Doran
06287da9d8 server-ts: Port battribute 2024-02-17 13:32:42 +02:00
Elian Doran
eef8297ce1 server-ts: Port bblob 2024-02-17 13:32:42 +02:00
Elian Doran
2f15d79476 server-ts: Port betapi_token 2024-02-17 13:32:42 +02:00
Elian Doran
768aaf2d78 server-ts: Port boption 2024-02-17 13:32:42 +02:00
Elian Doran
6dd2cd39aa server-ts: Convert a few classes in becca 2024-02-17 13:32:42 +02:00
Elian Doran
e04bd5aaf0 server-ts: Convert more classes, including entity_changes.js 2024-02-17 13:32:42 +02:00
Elian Doran
cb14d4d8f9 server-ts: Convert blob.js 2024-02-17 13:32:42 +02:00
Elian Doran
85af0a24ee server-ts: Convert becca.js 2024-02-17 13:32:42 +02:00
Elian Doran
3a7d0d1f13 server-ts: Add some type fixes to sql.ts 2024-02-17 13:32:42 +02:00
Elian Doran
5309d4ad5c server-ts: instance_id.js -> ts 2024-02-17 13:32:42 +02:00
Elian Doran
786d851293 server-ts: events.js -> ts 2024-02-17 13:32:41 +02:00
Elian Doran
b63e870038 server-ts: protected_session.js -> ts 2024-02-17 13:32:41 +02:00
Elian Doran
c20d2273e6 server-ts: services/encryption/*.js -> ts 2024-02-17 13:32:41 +02:00
Elian Doran
dc359b2a74 server-ts: resource_dir.js -> ts 2024-02-17 13:32:41 +02:00
Elian Doran
d3c9e7e157 server-ts: errors/*.js -> .ts 2024-02-17 13:32:41 +02:00
Elian Doran
82a9808dea server-ts: sql.js -> sql.ts 2024-02-17 13:32:41 +02:00
Elian Doran
e2a727ec8a server-ts: date_utils.js -> date_utils.ts 2024-02-17 13:32:41 +02:00
Elian Doran
7a9365457a server-ts: utils.js -> utils.ts 2024-02-17 13:32:41 +02:00
Elian Doran
28735fa16a server-ts: Fix build errors 2024-02-17 13:32:41 +02:00
Elian Doran
ff2e05fe83 server-ts: Fix references to cls.js 2024-02-17 13:32:41 +02:00
Elian Doran
96b6d17d77 server-ts: Remove need for 'allowImportingTsExtensions' 2024-02-17 13:32:40 +02:00
Elian Doran
78f631373b server-ts: cls.js -> cls.ts 2024-02-17 13:32:40 +02:00
Elian Doran
981221d599 server-ts: data_dir.js -> data_dir.ts 2024-02-17 13:32:40 +02:00
Elian Doran
bda11fad40 server-ts: Fix errors in log.ts 2024-02-17 13:32:40 +02:00
Elian Doran
39627a4fc4 server-ts: log.js -> log.ts 2024-02-17 13:32:40 +02:00
Elian Doran
094895ec72 server-ts: Enable ts-node instead of nodemon 2024-02-17 13:32:40 +02:00
zadam
8199073342 excali 17 2023-12-27 22:48:46 +01:00
311 changed files with 10172 additions and 11343 deletions

View File

@@ -5,3 +5,6 @@
/docs
/npm-debug.log
node_modules
src/**/*.ts
!src/services/asset_path.ts

View File

@@ -1,7 +0,0 @@
node_modules
dist
bin
docs
libraries
coverage
play

View File

@@ -1,212 +0,0 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
node: true,
},
// plugins: ['prettier'], // to be activated
extends: ['eslint:recommended', 'airbnb-base', 'plugin:jsonc/recommended-with-jsonc', 'prettier'],
overrides: [
{
files: ['*.json', '*.json5', '*.jsonc'],
parser: 'jsonc-eslint-parser',
},
{
files: ['package.json'],
parser: 'jsonc-eslint-parser',
rules: {
'jsonc/sort-keys': [
'off',
{
pathPattern: '^$',
order: [
'name',
'version',
'private',
'packageManager',
'description',
'type',
'keywords',
'homepage',
'bugs',
'license',
'author',
'contributors',
'funding',
'files',
'main',
'module',
'exports',
'unpkg',
'jsdelivr',
'browser',
'bin',
'man',
'directories',
'repository',
'publishConfig',
'scripts',
'peerDependencies',
'peerDependenciesMeta',
'optionalDependencies',
'dependencies',
'devDependencies',
'engines',
'config',
'overrides',
'pnpm',
'husky',
'lint-staged',
'eslintConfig',
],
},
{
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$',
order: { type: 'asc' },
},
],
},
},
],
globals: {
$: true,
jQuery: true,
glob: true,
log: true,
EditorWatchdog: true,
React: true,
appState: true,
ExcalidrawLib: true,
elements: true,
files: true,
ReactDOM: true,
// src\public\app\widgets\type_widgets\relation_map.js
jsPlumb: true,
panzoom: true,
logError: true,
// src\public\app\widgets\type_widgets\image.js
WZoom: true,
// \src\public\app\widgets\type_widgets\read_only_text.js
renderMathInElement: true,
// \src\public\app\widgets\type_widgets\editable_text.js
BalloonEditor: true,
FancytreeNode: true,
CKEditorInspector: true,
// \src\public\app\widgets\type_widgets\editable_code.js
CodeMirror: true,
// \src\public\app\services\resizer.js
Split: true,
// \src\public\app\services\content_renderer.js
mermaid: true,
// src\public\app\services\frontend_script_api.js
dayjs: true,
// \src\public\app\widgets\note_map.js
ForceGraph: true,
// \src\public\app\setup.js
ko: true,
syncInProgress: true,
// src\public\app\services\utils.js
logInfo: true,
__non_webpack_require__: true,
describe: true,
it: true,
expect: true
},
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
// eslint:recommended
'no-unused-vars': 'off',
'linebreak-style': 'off',
'no-useless-escape': 'off',
'no-empty': 'off',
'no-constant-condition': 'off',
'getter-return': 'off',
'no-cond-assign': 'off',
'no-async-promise-executor': 'off',
'no-extra-semi': 'off',
'no-inner-declarations': 'off',
// prettier
'prettier/prettier': ['off', { endOfLine: 'auto' }],
// airbnb-base
'no-console': 'off',
'no-plusplus': 'off',
'no-param-reassign': 'off',
'global-require': 'off',
'no-use-before-define': 'off',
'no-await-in-loop': 'off',
radix: 'off',
'import/order': 'off',
'import/no-extraneous-dependencies': 'off',
'prefer-destructuring': 'off',
'no-shadow': 'off',
'no-new': 'off',
'no-restricted-syntax': 'off',
strict: 'off',
'class-methods-use-this': 'off',
'no-else-return': 'off',
'import/no-dynamic-require': 'off',
'no-underscore-dangle': 'off',
'prefer-template': 'off',
'consistent-return': 'off',
'no-continue': 'off',
'object-shorthand': 'off',
'one-var': 'off',
'prefer-const': 'off',
'spaced-comment': 'off',
'no-loop-func': 'off',
'arrow-body-style': 'off',
'guard-for-in': 'off',
'no-return-assign': 'off',
'dot-notation': 'off',
'func-names': 'off',
'import/no-useless-path-segments': 'off',
'default-param-last': 'off',
'prefer-arrow-callback': 'off',
'no-unneeded-ternary': 'off',
'no-return-await': 'off',
'import/extensions': 'off',
'no-var': 'off',
'import/newline-after-import': 'off',
'no-restricted-globals': 'off',
'operator-assignment': 'off',
'no-eval': 'off',
'max-classes-per-file': 'off',
'vars-on-top': 'off',
'no-bitwise': 'off',
'no-lonely-if': 'off',
'no-multi-assign': 'off',
'no-promise-executor-return': 'off',
'no-empty-function': 'off',
'import/no-unresolved': 'off',
camelcase: 'off',
eqeqeq: 'off',
'lines-between-class-members': 'off',
'import/no-cycle': 'off',
'new-cap': 'off',
'prefer-object-spread': 'off',
'no-new-func': 'off',
'no-unused-expressions': 'off',
'lines-around-directive': 'off',
'prefer-exponentiation-operator': 'off',
'no-restricted-properties': 'off',
'prefer-rest-params': 'off',
'no-unreachable-loop': 'off',
'no-alert': 'off',
'no-useless-return': 'off',
'no-nested-ternary': 'off',
'prefer-regex-literals': 'off',
'import/no-named-as-default-member': 'off',
yoda: 'off',
'no-script-url': 'off',
'no-prototype-builtins':'off'
},
};

25
.github/workflows/dev.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Dev
on:
push:
jobs:
build_docker:
name: Build Docker image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
cache-from: type=gha
cache-to: type=gha,mode=max

147
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,147 @@
name: Main
on:
push:
branches:
- 'develop'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build_darwin-x64:
name: Build macOS x86_64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm ci
- run: ./bin/build-mac-x64.sh
- uses: actions/upload-artifact@v4
with:
name: trilium-mac-x64.zip
path: dist/trilium-mac-x64*.zip
build_darwin-arm64:
name: Build macOS aarch64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm ci
- run: ./bin/build-mac-arm64.sh
- uses: actions/upload-artifact@v4
with:
name: trilium-mac-arm64.zip
path: dist/trilium-mac-arm64*.zip
build_linux-x64:
name: Build Linux x86_64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm ci
- run: ./bin/build-linux-x64.sh
- uses: actions/upload-artifact@v4
with:
name: trilium-linux-x64.tar.xz
path: dist/trilium-linux-x64-*.tar.xz
- uses: actions/upload-artifact@v4
with:
name: trilium_amd64.deb
path: dist/trilium_*.deb
build_linux_server-x64:
name: Build Linux Server x86_64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm ci
- run: ./bin/build-server.sh
- uses: actions/upload-artifact@v4
with:
name: trilium-linux-x64-server.tar.xz
path: dist/trilium-linux-x64-server-*.tar.xz
build_windows-x64:
name: Build Windows x86_64
runs-on: ubuntu-latest
steps:
- name: Set up Wine
run: |
sudo dpkg --add-architecture i386
wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add -
sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport
sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu $(lsb_release -cs) main"
sudo apt install --install-recommends winehq-stable
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm ci
- run: ./bin/build-win-x64.sh
- uses: actions/upload-artifact@v4
with:
name: trilium-windows-x64.zip
path: dist/trilium-windows-x64-*.zip
build_docker:
name: Build Docker image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
id: push
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
.DS_Store
node_modules/
dist/
build/
src/public/app-dist/
npm-debug.log
yarn-error.log

1
.husky/.gitignore vendored
View File

@@ -1 +0,0 @@
_

View File

@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
#npx lint-staged

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

14
.prettierrc Normal file
View File

@@ -0,0 +1,14 @@
{
"printWidth": 200,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "always",
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
}

View File

@@ -1,13 +0,0 @@
//https://prettier.io/docs/en/options.html
module.exports = {
semi: true,
trailingComma: 'none',
singleQuote: true,
printWidth: 100,
tabWidth: 4,
useTabs: false,
quoteProps: "as-needed",
bracketSpacing: true,
arrowParens: "avoid"
// htmlWhitespaceSensitivity: 'ignore',
};

View File

@@ -1,6 +0,0 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
]
}

37
.vscode/settings.json vendored
View File

@@ -1,33 +1,6 @@
{
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[json]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"editor.formatOnSave": true,
"eslint.format.enable": true,
"eslint.probe": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"html",
"vue",
"markdown",
"json",
"jsonc"
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"html",
"vue",
"markdown",
"json",
"jsonc"
],
"files.eol": "\n",
}
"editor.formatOnSave": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n",
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -1,6 +1,18 @@
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!!
FROM node:18.18.2-alpine
# Configure system dependencies
RUN apk add --no-cache --virtual .build-dependencies \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3
# Create app directory
WORKDIR /usr/src/app
@@ -9,25 +21,21 @@ COPY . .
COPY server-package.json package.json
# Copy TypeScript build artifacts into the original directory structure.
RUN ls
RUN cp -R build/src/* src/.
RUN rm -r build
# Install app dependencies
RUN set -x \
&& apk add --no-cache --virtual .build-dependencies \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3 \
&& npm install \
&& apk del .build-dependencies \
&& npm run webpack \
&& npm prune --omit=dev \
&& cp src/public/app/share.js src/public/app-dist/. \
&& cp -r src/public/app/doc_notes src/public/app-dist/. \
&& rm -rf src/public/app
&& rm -rf src/public/app \
&& rm src/services/asset_path.ts
# Some setup tools need to be kept
RUN apk add --no-cache su-exec shadow

View File

@@ -1,6 +1,6 @@
# Trilium Notes
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Trilium Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://github.com/zadam/trilium/wiki/Screenshot-tour)以快速了解:

93
README.it.md Normal file
View File

@@ -0,0 +1,93 @@
# Trilium Notes
## Trilium è in manutenzione - vedi i dettagli in https://github.com/zadam/trilium/issues/4620
Le discussioni preliminari sull'organizzazione si stanno svolgendo in [Trilium Next discussions](https://github.com/orgs/TriliumNext/discussions).
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
Trilium Notes è un'applicazione per appunti ad organizzazione gerarchica, studiata per la costruzione di archivi di conoscenza personali di grandi dimensioni.
Vedi [fotografie](https://github.com/zadam/trilium/wiki/Screenshot-tour) per una panoramica veloce:
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
L'Ucraina si sta difendendo dall'aggressione russa, considera [donare all'esercito ucraino o a organizzazioni umanitarie](https://standforukraine.com/).
<p float="left">
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
</p>
## 🎁 Funzionalità
* Gli appunti possono essere organizzati in un albero di profondità arbitraria. Un singolo appunto può essere collocato in più posti nell'albero (vedi [clonazione](https://github.com/zadam/trilium/wiki/Cloning-notes))
* Ricco editor visuale (WYSIWYG), con supporto -tra l'altro- per tabelle, immagini ed [espressioni matematiche](https://github.com/zadam/trilium/wiki/Text-notes#math-support) e con [formattazione automatica](https://github.com/zadam/trilium/wiki/Text-notes#autoformat) per markdown
* Supporto per la modifica di [appunti con codice sorgente](https://github.com/zadam/trilium/wiki/Code-notes), con evidenziazione della sintassi
* [Navigazione veloce](https://github.com/zadam/trilium/wiki/Note-navigation) tra gli appunti, ricerca testuale completa e [fissaggio degli appunti](https://github.com/zadam/trilium/wiki/Note-hoisting)
* Supporto integrato ed automatico per le [revisioni degli appunti](https://github.com/zadam/trilium/wiki/Note-revisions)
* Gli [attributi](https://github.com/zadam/trilium/wiki/Attributes) degli appunti possono essere utilizzati per l'organizzazione, per l'interrogazione e per lo scripting avanzato (prorgrammazione).
* [Sincronizzazione](https://github.com/zadam/trilium/wiki/Synchronization) con un server di sincronizzazione auto-ospitato
* c'è un [servizio di terze parti per ospitare server di sincronizzazione](https://trilium.cc/paid-hosting)
* [Condivisione](https://github.com/zadam/trilium/wiki/Sharing) (pubblicazione) di appunti sull'internet pubblico
* Robusta [crittografia](https://github.com/zadam/trilium/wiki/Protected-notes) configurabile singolarmente per ogni appunto
* Disegno di diagrammi con Excalidraw (tipo di appunto "canvas")
* [Mappe relazionali](https://github.com/zadam/trilium/wiki/Relation-map) e [mappe di collegamenti](https://github.com/zadam/trilium/wiki/Link-map) per visualizzare gli appunti e le loro relazioni
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - vedi [Esempi avanzati](https://github.com/zadam/trilium/wiki/Advanced-showcases)
* [API REST](https://github.com/zadam/trilium/wiki/ETAPI) per l'automazione
* Si adatta bene sia in termini di usabilità che di prestazioni fino ad oltre 100 000 appunti
* Interfaccia utente ottimizzata per il [mobile](https://github.com/zadam/trilium/wiki/Mobile-frontend) (smartphone e tablet)
* [Tema Notturno](https://github.com/zadam/trilium/wiki/Themes)
* Supporto per importazione ed esportazione da e per [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) e [Markdown import](https://github.com/zadam/trilium/wiki/Markdown)
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) per il salvataggio facile di contenuti web
Dai un'occhiata a [awesome-trilium](https://github.com/Nriver/awesome-trilium) per temi, script, plugin e altro di terze parti.
## 🏗 Rilasci
Trilium è fornito come applicazione desktop (Linux e Windows) o come applicazione web ospitata sul tuo server (Linux). La versione desktop per Mac OS è disponibile, ma [non è supportata](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
* Se vuoi usare Trilium sul tuo desktop, scarica il rilascio binario per la tua piattaforma dall'[ultimo rilascio](https://github.com/zadam/trilium/releases/latest), decomprimi l'archivio e avvia l'eseguibile ```trilium```.
* Se vuoi installare Trilium su un server, segui [questa pagina](https://github.com/zadam/trilium/wiki/Server-installation).
* Per ora solo Chrome e Firefox sono i browser supportati (testati).
Trilium è anche disponibile su Flatpak:
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
## 📝 Documentazione
[Vedi la wiki per una lista completa delle pagine di documentazione.](https://github.com/zadam/trilium/wiki/)
Puoi anche leggere ["Patterns of personal knowledge base"](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) per avere un'ispirazione su come potresti utilizzare Trilium.
## 💻 Contribuire
Usa un ambiente di sviluppo basato su browser
[![Apri in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/zadam/trilium)
O clona localmente ed esegui
```
npm install
npm run start-server
```
## 📢 Riconoscimenti
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - miglior editor visuale (WYSIWYG) sul mercato, squadra di sviluppo attenta e reattiva
* [FancyTree](https://github.com/mar10/fancytree) - libreria per alberi molto ricca di funzionalità, senza pari. Trilium Notes non sarebbe lo stesso senza di essa.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - editor di codice con supporto per un'enorme quantità di linguaggi.
* [jsPlumb](https://github.com/jsplumb/jsplumb) - libreria per la connettività visuale senza pari. Utilizzata per [mappe relazionali](https://github.com/zadam/trilium/wiki/Relation-map) e [mappe di collegamenti](https://github.com/zadam/trilium/wiki/Link-map).
## 🤝 Supporto
È possibile supportare Trilium attraverso Github Sponsors, [PayPal](https://paypal.me/za4am) o Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
## 🔑 Licenza
Questo programma è software libero: è possibile redistribuirlo e/o modificarlo nei termini della GNU Affero General Public License come pubblicata dalla Free Software Foundation, sia la versione 3 della Licenza, o (a propria scelta) qualsiasi versione successiva.

View File

@@ -1,6 +1,6 @@
# Trilium Notes
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
Trilium Notes は、大規模な個人知識ベースの構築に焦点を当てた、階層型ノートアプリケーションです。概要は[スクリーンショット](https://github.com/zadam/trilium/wiki/Screenshot-tour)をご覧ください:

View File

@@ -1,88 +1,95 @@
# Trilium Notes
# TriliumNext Notes
## Trilium is in maintenance mode - see details in https://github.com/zadam/trilium/issues/4620
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md)
Preliminary disccusions on the successor organization are taking place in [Trilium Next discussions](https://github.com/orgs/TriliumNext/discussions).
TriliumNext Notes is a hierarchical note taking application with focus on building large personal knowledge bases.
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
See [screenshots](https://triliumnext.github.io/Docs/Wiki/Screenshot%20tour) for quick overview:
Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.
<a href="https://triliumnext.github.io/Docs/Wiki/Screenshot%20tour"><img src="https://github.com/TriliumNext/Docs/blob/main/Wiki/images/screenshot.png?raw=true" alt="Trilium Screenshot" width="1000"></a>
See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
## ⚠️ Why TriliumNext?
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
[The original Trilium project is in maintenance mode](https://github.com/zadam/trilium/issues/4620)
Ukraine is currently defending itself from Russian aggression, please consider [donating to Ukrainian Army or humanitarian charities](https://standforukraine.com/).
## 🗭 Discuss with us
<p float="left">
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
</p>
Feel free to join our official discussions and community. We are focused on the development on Trilium, and would love to hear what features, suggestions, or issues you may have!
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions)
- [Github Discussions](https://github.com/TriliumNext/Notes/discussions) (For Asynchronous discussions)
- [Wiki](https://github.com/zadam/trilium/wiki) (For common how-to questions and user guides)
The two rooms linked above are mirrored, so you can use either XMPP or Matrix, from any client you prefer, on pretty much any platform under the sun!
### Unofficial Communities
[Trilium Rocks](https://discord.gg/aqdX9mXX4r)
## 🎁 Features
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes))
* Rich WYSIWYG note editing including e.g. tables, images and [math](https://github.com/zadam/trilium/wiki/Text-notes#math-support) with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)
* Support for editing [notes with source code](https://github.com/zadam/trilium/wiki/Code-notes), including syntax highlighting
* Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation), full text search and [note hoisting](https://github.com/zadam/trilium/wiki/Note-hoisting)
* Seamless [note versioning](https://github.com/zadam/trilium/wiki/Note-revisions)
* Note [attributes](https://github.com/zadam/trilium/wiki/Attributes) can be used for note organization, querying and advanced [scripting](https://github.com/zadam/trilium/wiki/Scripts)
* [Synchronization](https://github.com/zadam/trilium/wiki/Synchronization) with self-hosted sync server
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/Cloning-notes))
* Rich WYSIWYG note editing including e.g. tables, images and [math](https://triliumnext.github.io/Docs/Wiki/Text-notes) with markdown [autoformat](https://triliumnext.github.io/Docs/Wiki/Text-notes#autoformat)
* Support for editing [notes with source code](https://triliumnext.github.io/Docs/Wiki/Code-notes), including syntax highlighting
* Fast and easy [navigation between notes](https://triliumnext.github.io/Docs/Wiki/Note-navigation), full text search and [note hoisting](https://triliumnext.github.io/Docs/Wiki/Note-hoisting)
* Seamless [note versioning](https://triliumnext.github.io/Docs/Wiki/Note-revisions)
* Note [attributes](https://triliumnext.github.io/Docs/Wiki/Attributes) can be used for note organization, querying and advanced [scripting](https://triliumnext.github.io/Docs/Wiki/Scripts)
* [Synchronization](https://triliumnext.github.io/Docs/Wiki/Synchronization) with self-hosted sync server
* there's a [3rd party service for hosting synchronisation server](https://trilium.cc/paid-hosting)
* [Sharing](https://github.com/zadam/trilium/wiki/Sharing) (publishing) notes to public internet
* Strong [note encryption](https://github.com/zadam/trilium/wiki/Protected-notes) with per-note granularity
* [Sharing](https://triliumnext.github.io/Docs/Wiki/Sharing) (publishing) notes to public internet
* Strong [note encryption](https://triliumnext.github.io/Docs/Wiki/Protected-notes) with per-note granularity
* Sketching diagrams with built-in Excalidraw (note type "canvas")
* [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) and [link maps](https://github.com/zadam/trilium/wiki/Link-map) for visualizing notes and their relations
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases)
* [REST API](https://github.com/zadam/trilium/wiki/ETAPI) for automation
* [Relation maps](https://triliumnext.github.io/Docs/Wiki/Relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/Link-map) for visualizing notes and their relations
* [Scripting](https://triliumnext.github.io/Docs/Wiki/Scripts) - see [Advanced showcases](https://triliumnext.github.io/Docs/Wiki/Advanced-showcases)
* [REST API](https://triliumnext.github.io/Docs/Wiki/ETAPI) for automation
* Scales well in both usability and performance upwards of 100 000 notes
* Touch optimized [mobile frontend](https://github.com/zadam/trilium/wiki/Mobile-frontend) for smartphones and tablets
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) for easy saving of web content
* Touch optimized [mobile frontend](https://triliumnext.github.io/Docs/Wiki/Mobile-frontend) for smartphones and tablets
* [Night theme](https://triliumnext.github.io/Docs/Wiki/Themes)
* [Evernote](https://triliumnext.github.io/Docs/Wiki/Evernote-import) and [Markdown import & export](https://triliumnext.github.io/Docs/Wiki/Markdown)
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/Web-clipper) for easy saving of web content
Check out [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
Check out the following third-party resources for more TriliumNext related goodies:
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
## 🏗 Builds
Trilium is provided as either desktop application (Linux and Windows) or web application hosted on your server (Linux). Mac OS desktop build is available, but it is [unsupported](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
Trilium is provided as either desktop application (Linux and Windows) or web application hosted on your server (Linux). Mac OS desktop build is available, but it is [unsupported](https://triliumnext.github.io/Docs/Wiki/FAQ#mac-os-support).
* If you want to use Trilium on the desktop, download binary release for your platform from [latest release](https://github.com/zadam/trilium/releases/latest), unzip the package and run ```trilium``` executable.
* 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.
* If you want to use TriliumNext on the desktop, download binary release for your platform from [latest release](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run ```trilium``` executable.
* If you want to install TriliumNext on your own server, follow [this page](https://triliumnext.github.io/Docs/Wiki/Server-installation).
* Currently only recent versions of Chrome and Firefox are supported (tested) browsers.
Trilium is also provided as a Flatpak:
TriliumNext will 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)
<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">
## 📝 Documentation
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)
[See wiki for complete list of documentation pages.](https://triliumnext.github.io/Docs)
You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.
You can also read [Patterns of personal knowledge base](https://triliumnext.github.io/Docs/Wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.
## 💻 Contribute
Use a browser based dev environment
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/zadam/trilium)
Or clone locally and run
Clone locally and run
```
npm install
npm run start-server
```
## 📢 Shoutouts
## 👏 Shoutouts
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team
* [FancyTree](https://github.com/mar10/fancytree) - very feature rich tree library without real competition. Trilium Notes would not be the same without it.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with support for huge amount of languages
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library without competition. Used in [relation maps](https://github.com/zadam/trilium/wiki/Relation-map) and [link maps](https://github.com/zadam/trilium/wiki/Link-map)
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library without competition. Used in [relation maps](https://triliumnext.github.io/Docs/Wiki/Relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/Link-map)
## 🤝 Support
You can support Trilium using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
You can support the original Trilium developer using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
Support for the TriliumNext organization will be possible in the near future.
## 🔑 License

View File

@@ -1,6 +1,6 @@
# Trilium Notes
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Trilium Notes это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://github.com/zadam/trilium/wiki/Screenshot-tour):

10
_check_ts_progress.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
cloc HEAD \
--git --md \
--include-lang=javascript,typescript \
--found=filelist.txt \
--exclude-dir=public,libraries,views,docs
grep -R \.js$ filelist.txt
rm filelist.txt

Binary file not shown.

View File

@@ -1,5 +1,15 @@
#!/usr/bin/env bash
if ! command -v dpkg-deb &> /dev/null; then
echo "Missing command: dpkg-deb"
exit 1
fi
if dpkg-deb 2>&1 | grep BusyBox &> /dev/null; then
echo "The dpkg-deb binary provided by BusyBox is not compatible. The Debian tool needs to be used instead."
exit 1
fi
echo "Packaging debian x64 distribution..."
VERSION=`jq -r ".version" package.json`

View File

@@ -5,6 +5,9 @@ SERIES=${VERSION:0:4}-latest
cat package.json | grep -v electron > server-package.json
echo "Compiling typescript..."
npx tsc
sudo docker build -t zadam/trilium:$VERSION --network host -t zadam/trilium:$SERIES .
if [[ $VERSION != *"beta"* ]]; then

View File

@@ -1,50 +1,58 @@
#!/usr/bin/env bash
if ! command -v jq &> /dev/null; then
echo "Missing command: jq"
exit 1
fi
if ! command -v fakeroot &> /dev/null; then
echo "Missing command: fakeroot"
exit 1
fi
if ! command -v dpkg-deb &> /dev/null; then
echo "Missing command: dpkg-deb"
exit 1
fi
if dpkg-deb 2>&1 | grep BusyBox &> /dev/null; then
echo "The dpkg-deb binary provided by BusyBox is not compatible. The Debian tool needs to be used instead."
exit 1
fi
SRC_DIR=./dist/trilium-linux-x64-src
if [ "$1" != "DONTCOPY" ]
then
./bin/copy-trilium.sh $SRC_DIR
fi
[ "$1" != "DONTCOPY" ] && ./bin/copy-trilium.sh "$SRC_DIR"
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
rm -r "$SRC_DIR"/src/public/app-dist/*.mobile.*
echo "Copying required linux-x64 binaries"
cp -r bin/better-sqlite3/linux-desktop-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
cp -r bin/better-sqlite3/linux-desktop-better_sqlite3.node "$SRC_DIR"/node_modules/better-sqlite3/build/Release/better_sqlite3.node
echo "Packaging linux x64 electron build"
./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite
./node_modules/.bin/electron-packager "$SRC_DIR" --asar --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite
BUILD_DIR=./dist/trilium-linux-x64
rm -rf $BUILD_DIR
rm -rf "$BUILD_DIR"
mv "./dist/Trilium Notes-linux-x64" $BUILD_DIR
mv "./dist/Trilium Notes-linux-x64" "$BUILD_DIR"
cp images/app-icons/png/128x128.png $BUILD_DIR/icon.png
cp images/app-icons/png/128x128.png "$BUILD_DIR"/icon.png
cp bin/tpl/anonymize-database.sql "$BUILD_DIR"/
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp -r dump-db "$BUILD_DIR"/
rm -rf "$BUILD_DIR"/dump-db/node_modules
cp -r dump-db $BUILD_DIR/
rm -rf $BUILD_DIR/dump-db/node_modules
cp bin/tpl/trilium-portable.sh $BUILD_DIR/
chmod 755 $BUILD_DIR/trilium-portable.sh
cp bin/tpl/trilium-safe-mode.sh $BUILD_DIR/
chmod 755 $BUILD_DIR/trilium-safe-mode.sh
cp bin/tpl/trilium-no-cert-check.sh $BUILD_DIR/
chmod 755 $BUILD_DIR/trilium-no-cert-check.sh
for f in 'trilium-portable' 'trilium-safe-mode' 'trilium-no-cert-check'; do
cp bin/tpl/"$f".sh "$BUILD_DIR"/
chmod 755 "$BUILD_DIR"/"$f".sh
done
echo "Packaging linux x64 electron distribution..."
VERSION=`jq -r ".version" package.json`
cd dist
tar cJf trilium-linux-x64-${VERSION}.tar.xz trilium-linux-x64
cd ..
pushd dist
tar cJf "trilium-linux-x64-${VERSION}.tar.xz" trilium-linux-x64
popd
bin/build-debian.sh

38
bin/build-mac-arm64.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
SRC_DIR=./dist/trilium-mac-arm64-src
if [ "$1" != "DONTCOPY" ]
then
./bin/copy-trilium.sh $SRC_DIR
fi
echo "Copying required mac arm64 binaries"
cp -r bin/better-sqlite3/mac-arm64-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
echo "Packaging mac arm64 electron build"
./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=darwin --arch=arm64 --overwrite --icon=images/app-icons/mac/icon.icns
BUILD_DIR=./dist/trilium-mac-arm64
rm -rf $BUILD_DIR
# Mac build has by default useless directory level
mv "./dist/Trilium Notes-darwin-arm64" $BUILD_DIR
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp -r dump-db $BUILD_DIR/
rm -rf $BUILD_DIR/dump-db/node_modules
echo "Zipping mac arm64 electron distribution..."
VERSION=`jq -r ".version" package.json`
cd dist
rm trilium-mac-arm64-${VERSION}.zip
zip -r9 --symlinks trilium-mac-arm64-${VERSION}.zip trilium-mac-arm64

View File

@@ -7,9 +7,9 @@ then
./bin/copy-trilium.sh $SRC_DIR
fi
echo "Copying required mac binaries"
echo "Copying required mac x64 binaries"
cp -r bin/better-sqlite3/mac-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
cp -r bin/better-sqlite3/mac-x64-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*

View File

@@ -1,5 +1,10 @@
#!/usr/bin/env bash
if ! command -v wine &> /dev/null; then
echo "Missing command: wine"
exit 1
fi
SRC_DIR=./dist/trilium-windows-x64-src
if [ "$1" != "DONTCOPY" ]

View File

@@ -1,5 +1,30 @@
#!/usr/bin/env bash
if ! command -v jq &> /dev/null; then
echo "Missing command: jq"
exit 1
fi
if ! command -v fakeroot &> /dev/null; then
echo "Missing command: fakeroot"
exit 1
fi
if ! command -v dpkg-deb &> /dev/null; then
echo "Missing command: dpkg-deb"
exit 1
fi
if dpkg-deb 2>&1 | grep BusyBox &> /dev/null; then
echo "The dpkg-deb binary provided by BusyBox is not compatible. The Debian tool needs to be used instead."
exit 1
fi
if ! command -v wine &> /dev/null; then
echo "Missing command: wine"
exit 1
fi
echo "Deleting existing builds"
rm -rf dist/*
@@ -13,11 +38,15 @@ cp -r $SRC_DIR ./dist/trilium-linux-x64-src
cp -r $SRC_DIR ./dist/trilium-linux-x64-server
cp -r $SRC_DIR ./dist/trilium-windows-x64-src
cp -r $SRC_DIR ./dist/trilium-mac-x64-src
cp -r $SRC_DIR ./dist/trilium-mac-arm64-src
set -e
bin/build-win-x64.sh DONTCOPY
bin/build-mac-x64.sh DONTCOPY
bin/build-mac-arm64.sh DONTCOPY
bin/build-linux-x64.sh DONTCOPY
bin/build-server.sh DONTCOPY

78
bin/copy-dist.ts Normal file
View File

@@ -0,0 +1,78 @@
import fs from "fs-extra";
import path from "path";
const DEST_DIR = "./dist";
const DEST_DIR_SRC = path.join(DEST_DIR, "src");
const DEST_DIR_NODE_MODULES = path.join(DEST_DIR, "node_modules");
async function copyNodeModuleFileOrFolder(source: string) {
const adjustedSource = source.substring(13);
const destination = path.join(DEST_DIR_NODE_MODULES, adjustedSource);
console.log(`Copying ${source} to ${destination}`);
await fs.ensureDir(path.dirname(destination));
await fs.copy(source, destination);
}
const copy = async () => {
for (const srcFile of fs.readdirSync("build")) {
const destFile = path.join(DEST_DIR, path.basename(srcFile));
console.log(`Copying source ${srcFile} -> ${destFile}.`);
fs.copySync(path.join("build", srcFile), destFile, { recursive: true });
}
const filesToCopy = ["config-sample.ini"];
for (const file of filesToCopy) {
console.log(`Copying ${file}`);
await fs.copy(file, path.join(DEST_DIR, file));
}
const dirsToCopy = ["images", "libraries", "db"];
for (const dir of dirsToCopy) {
console.log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR, dir));
}
const srcDirsToCopy = ["./src/public", "./src/views", "./build"];
for (const dir of srcDirsToCopy) {
console.log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir)));
}
const nodeModulesFile = [
"node_modules/react/umd/react.production.min.js",
"node_modules/react/umd/react.development.js",
"node_modules/react-dom/umd/react-dom.production.min.js",
"node_modules/react-dom/umd/react-dom.development.js",
"node_modules/katex/dist/katex.min.js",
"node_modules/katex/dist/contrib/mhchem.min.js",
"node_modules/katex/dist/contrib/auto-render.min.js",
];
for (const file of nodeModulesFile) {
await copyNodeModuleFileOrFolder(file);
}
const nodeModulesFolder = [
"node_modules/@excalidraw/excalidraw/dist/",
"node_modules/katex/dist/",
"node_modules/dayjs/",
"node_modules/force-graph/dist/",
"node_modules/boxicons/css/",
"node_modules/boxicons/fonts/",
"node_modules/mermaid/dist/",
"node_modules/jquery/dist/",
"node_modules/jquery-hotkeys/",
"node_modules/print-this/",
"node_modules/split.js/dist/",
"node_modules/panzoom/dist/",
];
for (const folder of nodeModulesFolder) {
await copyNodeModuleFileOrFolder(folder);
}
};
copy()
.then(() => console.log("Copying complete!"))
.catch((err) => console.error("Error during copy:", err));

View File

@@ -1,50 +1,65 @@
#!/usr/bin/env bash
shopt -s globstar
if [[ $# -eq 0 ]] ; then
echo "Missing argument of target directory"
exit 1
fi
if ! [[ $(which npm) ]]; then
echo "Missing npm"
exit 1
fi
n exec 18.18.2 npm run webpack
# Trigger the TypeScript build
echo TypeScript build start
npx tsc
echo TypeScript build finished
DIR=$1
# Copy the TypeScript artifacts
DIR="$1"
rm -rf "$DIR"
mkdir -pv "$DIR"
rm -rf $DIR
mkdir $DIR
echo Webpack start
npm run webpack
echo Webpack finish
echo "Copying Trilium to build directory $DIR"
cp -r images $DIR/
cp -r libraries $DIR/
cp -r src $DIR/
cp -r db $DIR/
cp -r package.json $DIR/
cp -r package-lock.json $DIR/
cp -r README.md $DIR/
cp -r LICENSE $DIR/
cp -r config-sample.ini $DIR/
cp -r electron.js $DIR/
cp webpack-* $DIR/
for d in 'images' 'libraries' 'src' 'db'; do
cp -r "$d" "$DIR"/
done
for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample.ini'; do
cp "$f" "$DIR"/
done
script_dir=$(realpath $(dirname $0))
cp -Rv "$script_dir/../build/src" "$DIR"
cp "$script_dir/../build/electron.js" "$DIR"
# run in subshell (so we return to original dir)
(cd $DIR && n exec 18.18.2 npm install --only=prod)
(cd $DIR && npm install --only=prod)
if [[ -d "$DIR"/node_modules ]]; then
# cleanup of useless files in dependencies
rm -r $DIR/node_modules/image-q/demo
rm -r $DIR/node_modules/better-sqlite3/Release
rm -r $DIR/node_modules/better-sqlite3/deps/sqlite3.tar.gz
rm -r $DIR/node_modules/@jimp/plugin-print/fonts
rm -r $DIR/node_modules/jimp/browser
rm -r $DIR/node_modules/jimp/fonts
for d in 'image-q/demo' 'better-sqlite3/Release' 'better-sqlite3/deps/sqlite3.tar.gz' '@jimp/plugin-print/fonts' 'jimp/browser' 'jimp/fonts'; do
[[ -e "$DIR"/node_modules/"$d" ]] && rm -rv "$DIR"/node_modules/"$d"
done
# delete all tests (there are often large images as test file for jimp etc.)
find $DIR/node_modules -name test -exec rm -rf {} \;
find $DIR/node_modules -name docs -exec rm -rf {} \;
find $DIR/node_modules -name demo -exec rm -rf {} \;
for d in 'test' 'docs' 'demo'; do
find "$DIR"/node_modules -name "$d" -exec rm -rf {} \;
done
fi
find $DIR/libraries -name "*.map" -type f -delete
cp $DIR/src/public/app/share.js $DIR/src/public/app-dist/
cp -r $DIR/src/public/app/doc_notes $DIR/src/public/app-dist/
d="$DIR"/src/public
[[ -d "$d"/app-dist ]] || mkdir -pv "$d"/app-dist
cp "$d"/app/share.js "$d"/app-dist/
cp -r "$d"/app/doc_notes "$d"/app-dist/
rm -rf $DIR/src/public/app
rm -rf "$d"/app
unset f d DIR

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
const anonymizationService = require('../src/services/anonymization.js');
const anonymizationService = require('../src/services/anonymization');
const fs = require('fs');
const path = require('path');

View File

@@ -7,6 +7,11 @@ if [[ $# -eq 0 ]] ; then
exit 1
fi
if ! command -v jq &> /dev/null; then
echo "Missing command: jq"
exit 1
fi
VERSION=$1
if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
@@ -22,13 +27,14 @@ fi
echo "Releasing Trilium $VERSION"
jq '.version = "'$VERSION'"' package.json|sponge package.json
jq '.version = "'$VERSION'"' package.json > package.json.tmp
mv package.json.tmp package.json
git add package.json
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js
echo 'export = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.ts
git add src/services/build.js
git add src/services/build.ts
TAG=v$VERSION
@@ -48,6 +54,7 @@ LINUX_X64_BUILD=trilium-linux-x64-$VERSION.tar.xz
DEBIAN_X64_BUILD=trilium_${VERSION}_amd64.deb
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.zip
MAC_X64_BUILD=trilium-mac-x64-$VERSION.zip
MAC_ARM64_BUILD=trilium-mac-arm64-$VERSION.zip
SERVER_BUILD=trilium-linux-x64-server-$VERSION.tar.xz
echo "Creating release in GitHub"
@@ -68,4 +75,5 @@ gh release create "$TAG" \
"dist/$LINUX_X64_BUILD" \
"dist/$WINDOWS_X64_BUILD" \
"dist/$MAC_X64_BUILD" \
"dist/$MAC_ARM64_BUILD" \
"dist/$SERVER_BUILD"

View File

@@ -1,6 +1,6 @@
module.exports = () => {
const sql = require('../../src/services/sql.js');
const utils = require('../../src/services/utils.js');
const sql = require('../../src/services/sql');
const utils = require('../../src/services/utils');
const existingBlobIds = new Set();

View File

@@ -1,9 +1,9 @@
module.exports = () => {
const beccaLoader = require('../../src/becca/becca_loader.js');
const becca = require('../../src/becca/becca.js');
const cls = require('../../src/services/cls.js');
const log = require('../../src/services/log.js');
const sql = require('../../src/services/sql.js');
const beccaLoader = require('../../src/becca/becca_loader');
const becca = require('../../src/becca/becca');
const cls = require('../../src/services/cls');
const log = require('../../src/services/log');
const sql = require('../../src/services/sql');
cls.init(() => {
// emergency disabling of image compression since it appears to make problems in migration to 0.61
@@ -13,7 +13,7 @@ module.exports = () => {
for (const note of Object.values(becca.notes)) {
try {
const attachment = note.convertToParentAttachment({autoConversion: true});
const attachment = note.convertToParentAttachment({ autoConversion: true });
if (attachment) {
log.info(`Auto-converted note '${note.noteId}' into attachment '${attachment.attachmentId}'.`);

View File

@@ -1,7 +1,7 @@
const http = require("http");
const ini = require("ini");
const fs = require("fs");
const dataDir = require('./src/services/data_dir.js');
const dataDir = require('./src/services/data_dir');
const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8'));
if (config.Network.https) {
@@ -10,8 +10,8 @@ if (config.Network.https) {
process.exit(0);
}
const port = require('./src/services/port.js');
const host = require('./src/services/host.js');
const port = require('./src/services/port');
const host = require('./src/services/host');
const options = { timeout: 2000 };

View File

@@ -1,5 +1,5 @@
const crypto = require("crypto");
const sql = require('./sql.js');
const sql = require('./sql');
const decryptService = require('./decrypt.js');
function getDataKey(password) {

View File

@@ -74,7 +74,7 @@ function dumpDocument(documentPath, targetPath, options) {
return;
}
let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
let { content } = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
if (content !== null && noteRow.isProtected && dataKey) {
content = decryptService.decrypt(dataKey, content);
@@ -108,7 +108,7 @@ function dumpDocument(documentPath, targetPath, options) {
}
try {
fs.mkdirSync(childTargetPath, {recursive: true});
fs.mkdirSync(childTargetPath, { recursive: true });
}
catch (e) {
console.error(`DUMPERROR: Creating directory ${childTargetPath} failed with error '${e.message}'`);
@@ -157,7 +157,7 @@ function validatePaths(documentPath, targetPath) {
}
if (!fs.existsSync(targetPath)) {
const ret = fs.mkdirSync(targetPath, {recursive: true});
const ret = fs.mkdirSync(targetPath, { recursive: true });
if (!ret) {
console.error(`Target path '${targetPath}' could not be created. Run with --help to see usage.`);

View File

@@ -1,62 +0,0 @@
'use strict';
const {app, globalShortcut, BrowserWindow} = require('electron');
const sqlInit = require('./src/services/sql_init.js');
const appIconService = require('./src/services/app_icon.js');
const windowService = require('./src/services/window.js');
const tray = require('./src/services/tray.js');
// Adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();
appIconService.installLocalAppIcon();
require('electron-dl')({ saveAs: true });
// needed for excalidraw export https://github.com/zadam/trilium/issues/4271
app.commandLine.appendSwitch("enable-experimental-web-platform-features");
// 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()
}
});
app.on('ready', async () => {
// app.setAppUserModelId('com.github.zadam.trilium');
// if db is not initialized -> setup process
// if db is initialized, then we need to wait until the migration process is finished
if (sqlInit.isDbInitialized()) {
await sqlInit.dbReady;
await windowService.createMainWindow(app);
if (process.platform === 'darwin') {
app.on('activate', async () => {
if (BrowserWindow.getAllWindows().length === 0) {
await windowService.createMainWindow(app);
}
});
}
tray.createTray();
}
else {
await windowService.createSetupWindow();
}
await windowService.registerGlobalShortcuts();
});
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});
// this is to disable electron warning spam in the dev console (local development only)
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
require('./src/www.js');

63
electron.ts Normal file
View File

@@ -0,0 +1,63 @@
"use strict";
import electron = require("electron");
import sqlInit = require("./src/services/sql_init");
import appIconService = require("./src/services/app_icon");
import windowService = require("./src/services/window");
import tray = require("./src/services/tray");
// Adds debug features like hotkeys for triggering dev tools and reload
require("electron-debug")();
appIconService.installLocalAppIcon();
require("electron-dl")({ saveAs: true });
// needed for excalidraw export https://github.com/zadam/trilium/issues/4271
electron.app.commandLine.appendSwitch(
"enable-experimental-web-platform-features"
);
// 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.
electron.app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
electron.app.quit();
}
});
electron.app.on("ready", async () => {
// electron.app.setAppUserModelId('com.github.zadam.trilium');
// if db is not initialized -> setup process
// if db is initialized, then we need to wait until the migration process is finished
if (sqlInit.isDbInitialized()) {
await sqlInit.dbReady;
await windowService.createMainWindow(electron.app);
if (process.platform === "darwin") {
electron.app.on("activate", async () => {
if (electron.BrowserWindow.getAllWindows().length === 0) {
await windowService.createMainWindow(electron.app);
}
});
}
tray.createTray();
} else {
await windowService.createSetupWindow();
}
await windowService.registerGlobalShortcuts();
});
electron.app.on("will-quit", () => {
electron.globalShortcut.unregisterAll();
});
// this is to disable electron warning spam in the dev console (local development only)
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
require("./src/www.js");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,12 +2,11 @@
"restartable": "rs",
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
"verbose": false,
"execMap": {
"js": "node --harmony"
},
"exec": "ts-node",
"watch": ["src/"],
"signal": "SIGTERM",
"env": {
"NODE_ENV": "development"
},
"ext": "js,json"
"ext": "ts,js,json"
}

8913
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.63.2-beta",
"version": "0.90.0-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -13,13 +13,13 @@
"url": "https://github.com/zadam/trilium.git"
},
"scripts": {
"start-server": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.js",
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.js",
"qstart-server": "npm run qswitch-server && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.js",
"start-electron": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
"start-server": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
"qstart-server": "npm run qswitch-server && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
"start-electron": "rimraf ./dist && tsc && ts-node ./bin/copy-dist.ts && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron.js --inspect=5858 .",
"start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
"qstart-electron": "npm run qswitch-electron && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
"start-test-server": "npm run qswitch-server; rm -rf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 node src/www.js",
"start-test-server": "npm run qswitch-server; rm -rf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/www.ts",
"switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install",
"switch-electron": "./node_modules/.bin/electron-rebuild",
"qswitch-server": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-server-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node",
@@ -27,24 +27,22 @@
"build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js",
"build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "webpack -c webpack.config.js",
"webpack": "webpack -c webpack.config.ts",
"test-jasmine": "TRILIUM_DATA_DIR=~/trilium/data-test jasmine",
"test-es6": "node -r esm spec-es6/attribute_parser.spec.js ",
"test": "npm run test-jasmine && npm run test-es6",
"postinstall": "rimraf ./node_modules/canvas",
"lint": "eslint . --cache",
"prepare": "husky install || echo 'Husky install failed, expected on flatpak build'"
"postinstall": "rimraf ./node_modules/canvas"
},
"dependencies": {
"@braintree/sanitize-url": "6.0.4",
"@electron/remote": "2.1.0",
"@excalidraw/excalidraw": "0.16.1",
"archiver": "6.0.1",
"async-mutex": "0.4.0",
"axios": "1.6.2",
"@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "0.17.3",
"archiver": "7.0.0",
"async-mutex": "0.4.1",
"axios": "1.6.7",
"better-sqlite3": "8.4.0",
"boxicons": "2.1.4",
"chokidar": "3.5.3",
"chokidar": "3.6.0",
"cls-hooked": "4.2.2",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
@@ -54,35 +52,35 @@
"debounce": "1.2.1",
"ejs": "3.1.9",
"electron-debug": "3.2.0",
"electron-dl": "3.5.1",
"electron-dl": "3.5.2",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"express": "4.18.2",
"express": "4.18.3",
"express-partial-content": "1.0.2",
"express-rate-limit": "7.1.4",
"express-session": "1.17.3",
"force-graph": "1.43.4",
"fs-extra": "11.1.1",
"express-rate-limit": "7.2.0",
"express-session": "1.18.0",
"force-graph": "1.43.5",
"fs-extra": "11.2.0",
"helmet": "7.1.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.0",
"https-proxy-agent": "7.0.2",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.4",
"image-type": "4.1.0",
"ini": "3.0.1",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.22.10",
"jimp": "0.22.12",
"joplin-turndown-plugin-gfm": "1.0.12",
"jquery": "3.7.1",
"jquery-hotkeys": "0.2.2",
"jsdom": "22.1.0",
"jsdom": "24.0.0",
"katex": "0.16.9",
"marked": "9.1.6",
"mermaid": "10.6.1",
"marked": "12.0.0",
"mermaid": "10.9.0",
"mime-types": "2.1.35",
"multer": "1.4.5-lts.1",
"node-abi": "3.51.0",
"node-abi": "3.56.0",
"normalize-strings": "1.1.1",
"open": "8.4.1",
"panzoom": "9.4.3",
@@ -94,51 +92,67 @@
"rimraf": "5.0.5",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.11.0",
"sanitize-html": "2.12.1",
"sax": "1.3.0",
"semver": "7.5.4",
"semver": "7.6.0",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"split.js": "1.6.5",
"stream-throttle": "0.1.3",
"striptags": "3.2.0",
"tmp": "0.2.1",
"tmp": "0.2.3",
"tree-kill": "1.2.2",
"turndown": "7.1.2",
"unescape": "1.0.1",
"ws": "8.14.2",
"ws": "8.16.0",
"xml2js": "0.6.2",
"yauzl": "2.10.0"
"yauzl": "3.1.2"
},
"devDependencies": {
"@types/archiver": "^6.0.2",
"@types/better-sqlite3": "^7.6.9",
"@types/cls-hooked": "^4.3.8",
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.7",
"@types/csurf": "^1.11.5",
"@types/ejs": "^3.1.5",
"@types/escape-html": "^1.0.4",
"@types/express": "^4.17.21",
"@types/express-session": "^1.18.0",
"@types/html": "^1.0.4",
"@types/ini": "^4.1.0",
"@types/jsdom": "^21.1.6",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.11",
"@types/node": "^20.11.19",
"@types/safe-compare": "^1.1.2",
"@types/sanitize-html": "^2.11.0",
"@types/sax": "^1.2.7",
"@types/semver": "^7.5.8",
"@types/serve-favicon": "^2.5.7",
"@types/stream-throttle": "^0.1.4",
"@types/tmp": "^0.2.6",
"@types/turndown": "^5.0.4",
"@types/ws": "^8.5.10",
"@types/xml2js": "^0.4.14",
"cross-env": "7.0.3",
"electron": "25.9.8",
"electron-builder": "24.6.4",
"electron-builder": "24.13.3",
"electron-packager": "17.1.2",
"electron-rebuild": "3.2.9",
"eslint": "8.54.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-jsonc": "2.10.0",
"eslint-plugin-prettier": "5.0.1",
"esm": "3.2.25",
"husky": "8.0.3",
"jasmine": "5.1.0",
"jsdoc": "4.0.2",
"jsonc-eslint-parser": "2.4.0",
"lint-staged": "15.1.0",
"lorem-ipsum": "2.0.8",
"nodemon": "3.0.1",
"prettier": "3.1.0",
"nodemon": "3.1.0",
"rcedit": "4.0.1",
"webpack": "5.89.0",
"ts-node": "^10.9.2",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"webpack": "5.90.3",
"webpack-cli": "5.1.4"
},
"optionalDependencies": {
"electron-installer-debian": "3.2.0"
},
"lint-staged": {
"*.js": "eslint --cache --fix"
}
}

View File

@@ -1,4 +1,4 @@
const lex = require('../../src/services/search/services/lex.js');
const lex = require('../../src/services/search/services/lex');
describe("Lexer fulltext", () => {
it("simple lexing", () => {

View File

@@ -1,4 +1,4 @@
const handleParens = require('../../src/services/search/services/handle_parens.js');
const handleParens = require('../../src/services/search/services/handle_parens');
describe("Parens handler", () => {
it("handles parens", () => {

View File

@@ -1,5 +1,5 @@
const SearchContext = require('../../src/services/search/search_context.js');
const parse = require('../../src/services/search/services/parse.js');
const SearchContext = require('../../src/services/search/search_context');
const parse = require('../../src/services/search/services/parse');
function tokens(toks, cur = 0) {
return toks.map(arg => {

View File

@@ -1,8 +1,8 @@
const searchService = require('../../src/services/search/services/search.js');
const searchService = require('../../src/services/search/services/search');
const BNote = require('../../src/becca/entities/bnote.js');
const BBranch = require('../../src/becca/entities/bbranch.js');
const SearchContext = require('../../src/services/search/search_context.js');
const dateUtils = require('../../src/services/date_utils.js');
const SearchContext = require('../../src/services/search/search_context');
const dateUtils = require('../../src/services/date_utils');
const becca = require('../../src/becca/becca.js');
const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js');

View File

@@ -1,7 +1,7 @@
const {note} = require('./becca_mocking.js');
const ValueExtractor = require('../../src/services/search/value_extractor.js');
const ValueExtractor = require('../../src/services/search/value_extractor');
const becca = require('../../src/becca/becca.js');
const SearchContext = require('../../src/services/search/search_context.js');
const SearchContext = require('../../src/services/search/search_context');
const dsc = new SearchContext();

View File

@@ -1,6 +1,6 @@
const anonymizationService = require('./services/anonymization.js');
const sqlInit = require('./services/sql_init.js');
require('./becca/entity_constructor.js');
import anonymizationService = require('./services/anonymization');
import sqlInit = require('./services/sql_init');
require('./becca/entity_constructor');
sqlInit.dbReady.then(async () => {
try {
@@ -16,7 +16,7 @@ sqlInit.dbReady.then(async () => {
console.log("Anonymization failed.");
}
}
catch (e) {
catch (e: any) {
console.error(e.message, e.stack);
}

View File

@@ -1,60 +0,0 @@
const express = require('express');
const path = require('path');
const favicon = require('serve-favicon');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const compression = require('compression');
const sessionParser = require('./routes/session_parser.js');
const utils = require('./services/utils.js');
require('./services/handlers.js');
require('./becca/becca_loader.js');
const app = express();
// view engine setup
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,
crossOriginEmbedderPolicy: false
}));
app.use(express.text({limit: '500mb'}));
app.use(express.json({limit: '500mb'}));
app.use(express.raw({limit: '500mb'}));
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public/root')));
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
app.use(`/robots.txt`, express.static(path.join(__dirname, 'public/robots.txt')));
app.use(sessionParser);
app.use(favicon(`${__dirname}/../images/app-icons/win/icon.ico`));
require('./routes/assets.js').register(app);
require('./routes/routes.js').register(app);
require('./routes/custom.js').register(app);
require('./routes/error_handlers.js').register(app);
// triggers sync timer
require('./services/sync.js');
// triggers backup timer
require('./services/backup.js');
// trigger consistency checks timer
require('./services/consistency_checks.js');
require('./services/scheduler.js');
if (utils.isElectron()) {
require('@electron/remote/main').initialize();
}
module.exports = app;

60
src/app.ts Normal file
View File

@@ -0,0 +1,60 @@
import express = require('express');
import path = require('path');
import favicon = require('serve-favicon');
import cookieParser = require('cookie-parser');
import helmet = require('helmet');
import compression = require('compression');
import sessionParser = require('./routes/session_parser');
import utils = require('./services/utils');
require('./services/handlers');
require('./becca/becca_loader');
const app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
if (!utils.isElectron()) {
app.use(compression()); // HTTP compression
}
app.use(helmet.default({
hidePoweredBy: false, // errors out in electron
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false
}));
app.use(express.text({ limit: '500mb' }));
app.use(express.json({ limit: '500mb' }));
app.use(express.raw({ limit: '500mb' }));
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public/root')));
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
app.use(`/robots.txt`, express.static(path.join(__dirname, 'public/robots.txt')));
app.use(sessionParser);
app.use(favicon(`${__dirname}/../images/app-icons/win/icon.ico`));
require('./routes/assets').register(app);
require('./routes/routes').register(app);
require('./routes/custom').register(app);
require('./routes/error_handlers').register(app);
// triggers sync timer
require('./services/sync');
// triggers backup timer
require('./services/backup');
// trigger consistency checks timer
require('./services/consistency_checks');
require('./services/scheduler');
if (utils.isElectron()) {
require('@electron/remote/main').initialize();
}
export = app;

View File

@@ -1,32 +1,52 @@
"use strict";
import sql = require('../services/sql');
import NoteSet = require('../services/search/note_set');
import NotFoundError = require('../errors/not_found_error');
import BOption = require('./entities/boption');
import BNote = require('./entities/bnote');
import BEtapiToken = require('./entities/betapi_token');
import BAttribute = require('./entities/battribute');
import BBranch = require('./entities/bbranch');
import BRevision = require('./entities/brevision');
import BAttachment = require('./entities/battachment');
import { AttachmentRow, RevisionRow } from './entities/rows';
import BBlob = require('./entities/bblob');
import BRecentNote = require('./entities/brecent_note');
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
const sql = require('../services/sql.js');
const NoteSet = require('../services/search/note_set.js');
const NotFoundError = require('../errors/not_found_error.js');
interface AttachmentOpts {
includeContentLength?: boolean;
}
/**
* Becca is a backend cache of all notes, branches, and attributes.
* There's a similar frontend cache Froca, and share cache Shaca.
*/
class Becca {
export default class Becca {
loaded!: boolean;
notes!: Record<string, BNote>;
branches!: Record<string, BBranch>;
childParentToBranch!: Record<string, BBranch>;
attributes!: Record<string, BAttribute>;
/** Points from attribute type-name to list of attributes */
attributeIndex!: Record<string, BAttribute[]>;
options!: Record<string, BOption>;
etapiTokens!: Record<string, BEtapiToken>;
allNoteSetCache: NoteSet | null;
constructor() {
this.reset();
this.allNoteSetCache = null;
}
reset() {
/** @type {Object.<String, BNote>} */
this.notes = {};
/** @type {Object.<String, BBranch>} */
this.branches = {};
/** @type {Object.<String, BBranch>} */
this.childParentToBranch = {};
/** @type {Object.<String, BAttribute>} */
this.attributes = {};
/** @type {Object.<String, BAttribute[]>} Points from attribute type-name to list of attributes */
this.attributes = {};
this.attributeIndex = {};
/** @type {Object.<String, BOption>} */
this.options = {};
/** @type {Object.<String, BEtapiToken>} */
this.etapiTokens = {};
this.dirtyNoteSetCache();
@@ -38,8 +58,7 @@ class Becca {
return this.getNote('root');
}
/** @returns {BAttribute[]} */
findAttributes(type, name) {
findAttributes(type: string, name: string): BAttribute[] {
name = name.trim().toLowerCase();
if (name.startsWith('#') || name.startsWith('~')) {
@@ -49,9 +68,8 @@ class Becca {
return this.attributeIndex[`${type}-${name}`] || [];
}
/** @returns {BAttribute[]} */
findAttributesWithPrefix(type, name) {
const resArr = [];
findAttributesWithPrefix(type: string, name: string): BAttribute[] {
const resArr: BAttribute[][] = [];
const key = `${type}-${name}`;
for (const idx in this.attributeIndex) {
@@ -69,18 +87,16 @@ class Becca {
}
}
addNote(noteId, note) {
addNote(noteId: string, note: BNote) {
this.notes[noteId] = note;
this.dirtyNoteSetCache();
}
/** @returns {BNote|null} */
getNote(noteId) {
getNote(noteId: string): BNote | null {
return this.notes[noteId];
}
/** @returns {BNote|null} */
getNoteOrThrow(noteId) {
getNoteOrThrow(noteId: string): BNote {
const note = this.notes[noteId];
if (!note) {
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
@@ -89,9 +105,8 @@ class Becca {
return note;
}
/** @returns {BNote[]} */
getNotes(noteIds, ignoreMissing = false) {
const filteredNotes = [];
getNotes(noteIds: string[], ignoreMissing: boolean = false): BNote[] {
const filteredNotes: BNote[] = [];
for (const noteId of noteIds) {
const note = this.notes[noteId];
@@ -110,13 +125,11 @@ class Becca {
return filteredNotes;
}
/** @returns {BBranch|null} */
getBranch(branchId) {
getBranch(branchId: string): BBranch | null {
return this.branches[branchId];
}
/** @returns {BBranch|null} */
getBranchOrThrow(branchId) {
getBranchOrThrow(branchId: string): BBranch {
const branch = this.getBranch(branchId);
if (!branch) {
throw new NotFoundError(`Branch '${branchId}' was not found in becca.`);
@@ -124,13 +137,11 @@ class Becca {
return branch;
}
/** @returns {BAttribute|null} */
getAttribute(attributeId) {
getAttribute(attributeId: string): BAttribute | null {
return this.attributes[attributeId];
}
/** @returns {BAttribute} */
getAttributeOrThrow(attributeId) {
getAttributeOrThrow(attributeId: string): BAttribute {
const attribute = this.getAttribute(attributeId);
if (!attribute) {
throw new NotFoundError(`Attribute '${attributeId}' does not exist.`);
@@ -139,21 +150,26 @@ class Becca {
return attribute;
}
/** @returns {BBranch|null} */
getBranchFromChildAndParent(childNoteId, parentNoteId) {
getBranchFromChildAndParent(childNoteId: string, parentNoteId: string): BBranch | null {
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
}
/** @returns {BRevision|null} */
getRevision(revisionId) {
getRevision(revisionId: string): BRevision | null {
const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]);
const BRevision = require('./entities/brevision.js'); // avoiding circular dependency problems
const BRevision = require('./entities/brevision'); // avoiding circular dependency problems
return row ? new BRevision(row) : null;
}
/** @returns {BAttachment|null} */
getAttachment(attachmentId, opts = {}) {
getRevisionOrThrow(revisionId: string): BRevision {
const revision = this.getRevision(revisionId);
if (!revision) {
throw new NotFoundError(`Revision '${revisionId}' has not been found.`);
}
return revision;
}
getAttachment(attachmentId: string, opts: AttachmentOpts = {}): BAttachment | null {
opts.includeContentLength = !!opts.includeContentLength;
const query = opts.includeContentLength
@@ -163,14 +179,13 @@ class Becca {
WHERE attachmentId = ? AND isDeleted = 0`
: `SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0`;
const BAttachment = require('./entities/battachment.js'); // avoiding circular dependency problems
const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems
return sql.getRows(query, [attachmentId])
.map(row => new BAttachment(row))[0];
}
/** @returns {BAttachment} */
getAttachmentOrThrow(attachmentId, opts = {}) {
getAttachmentOrThrow(attachmentId: string, opts: AttachmentOpts = {}): BAttachment {
const attachment = this.getAttachment(attachmentId, opts);
if (!attachment) {
throw new NotFoundError(`Attachment '${attachmentId}' has not been found.`);
@@ -178,38 +193,36 @@ class Becca {
return attachment;
}
/** @returns {BAttachment[]} */
getAttachments(attachmentIds) {
const BAttachment = require('./entities/battachment.js'); // avoiding circular dependency problems
return sql.getManyRows("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds)
getAttachments(attachmentIds: string[]): BAttachment[] {
const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems
return sql.getManyRows<AttachmentRow>("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds)
.map(row => new BAttachment(row));
}
/** @returns {BBlob|null} */
getBlob(entity) {
getBlob(entity: { blobId?: string }): BBlob | null {
if (!entity.blobId) {
return null;
}
const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]);
const BBlob = require('./entities/bblob.js'); // avoiding circular dependency problems
const BBlob = require('./entities/bblob'); // avoiding circular dependency problems
return row ? new BBlob(row) : null;
}
/** @returns {BOption|null} */
getOption(name) {
getOption(name: string): BOption | null {
return this.options[name];
}
/** @returns {BEtapiToken[]} */
getEtapiTokens() {
getEtapiTokens(): BEtapiToken[] {
return Object.values(this.etapiTokens);
}
/** @returns {BEtapiToken|null} */
getEtapiToken(etapiTokenId) {
getEtapiToken(etapiTokenId: string): BEtapiToken | null {
return this.etapiTokens[etapiTokenId];
}
/** @returns {AbstractBeccaEntity|null} */
getEntity(entityName, entityId) {
getEntity<T extends AbstractBeccaEntity<T>>(entityName: string, entityId: string): AbstractBeccaEntity<T> | null {
if (!entityName || !entityId) {
return null;
}
@@ -231,22 +244,20 @@ class Becca {
throw new Error(`Unknown entity name '${camelCaseEntityName}' (original argument '${entityName}')`);
}
return this[camelCaseEntityName][entityId];
return (this as any)[camelCaseEntityName][entityId];
}
/** @returns {BRecentNote[]} */
getRecentNotesFromQuery(query, params = []) {
getRecentNotesFromQuery(query: string, params: string[] = []): BRecentNote[] {
const rows = sql.getRows(query, params);
const BRecentNote = require('./entities/brecent_note.js'); // avoiding circular dependency problems
const BRecentNote = require('./entities/brecent_note'); // avoiding circular dependency problems
return rows.map(row => new BRecentNote(row));
}
/** @returns {BRevision[]} */
getRevisionsFromQuery(query, params = []) {
const rows = sql.getRows(query, params);
getRevisionsFromQuery(query: string, params: string[] = []): BRevision[] {
const rows = sql.getRows<RevisionRow>(query, params);
const BRevision = require('./entities/brevision.js'); // avoiding circular dependency problems
const BRevision = require('./entities/brevision'); // avoiding circular dependency problems
return rows.map(row => new BRevision(row));
}
@@ -260,8 +271,8 @@ class Becca {
if (!this.allNoteSetCache) {
const allNotes = [];
for (const noteId in becca.notes) {
const note = becca.notes[noteId];
for (const noteId in this.notes) {
const note = this.notes[noteId];
// in the process of loading data sometimes we create "skeleton" note instances which are expected to be filled later
// in case of inconsistent data this might not work and search will then crash on these
@@ -277,6 +288,26 @@ class Becca {
}
}
const becca = new Becca();
/**
* This interface contains the data that is shared across all the objects of a given derived class of {@link AbstractBeccaEntity}.
* For example, all BAttributes will share their content, but all BBranches will have another set of this data.
*/
export interface ConstructorData<T extends AbstractBeccaEntity<T>> {
primaryKeyName: string;
entityName: string;
hashedProperties: (keyof T)[];
}
module.exports = becca;
export interface NotePojo {
noteId: string;
title?: string;
isProtected?: boolean;
type: string;
mime: string;
blobId?: string;
isDeleted: boolean;
dateCreated?: string;
dateModified?: string;
utcDateCreated: string;
utcDateModified?: string;
}

7
src/becca/becca.ts Normal file
View File

@@ -0,0 +1,7 @@
"use strict";
import Becca from "./becca-interface";
const becca = new Becca();
export = becca;

View File

@@ -1,24 +1,26 @@
"use strict";
const sql = require('../services/sql.js');
const eventService = require('../services/events.js');
const becca = require('./becca.js');
const sqlInit = require('../services/sql_init.js');
const log = require('../services/log.js');
const BNote = require('./entities/bnote.js');
const BBranch = require('./entities/bbranch.js');
const BAttribute = require('./entities/battribute.js');
const BOption = require('./entities/boption.js');
const BEtapiToken = require('./entities/betapi_token.js');
const cls = require('../services/cls.js');
const entityConstructor = require('../becca/entity_constructor.js');
import sql = require('../services/sql');
import eventService = require('../services/events');
import becca = require('./becca');
import sqlInit = require('../services/sql_init');
import log = require('../services/log');
import BNote = require('./entities/bnote');
import BBranch = require('./entities/bbranch');
import BAttribute = require('./entities/battribute');
import BOption = require('./entities/boption');
import BEtapiToken = require('./entities/betapi_token');
import cls = require('../services/cls');
import entityConstructor = require('../becca/entity_constructor');
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows';
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
const beccaLoaded = new Promise((res, rej) => {
const beccaLoaded = new Promise<void>((res, rej) => {
sqlInit.dbReady.then(() => {
cls.init(() => {
load();
require('../services/options_init.js').initStartupOptions();
require('../services/options_init').initStartupOptions();
res();
});
@@ -38,23 +40,23 @@ function load() {
new BNote().update(row).init();
}
const branchRows = sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0`);
const branchRows = sql.getRawRows<BranchRow>(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0`);
// in-memory sort is faster than in the DB
branchRows.sort((a, b) => a.notePosition - b.notePosition);
branchRows.sort((a, b) => (a.notePosition || 0) - (b.notePosition || 0));
for (const row of branchRows) {
new BBranch().update(row).init();
}
for (const row of sql.getRawRows(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0`)) {
for (const row of sql.getRawRows<AttributeRow>(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0`)) {
new BAttribute().update(row).init();
}
for (const row of sql.getRows(`SELECT name, value, isSynced, utcDateModified FROM options`)) {
for (const row of sql.getRows<OptionRow>(`SELECT name, value, isSynced, utcDateModified FROM options`)) {
new BOption(row);
}
for (const row of sql.getRows(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) {
for (const row of sql.getRows<EtapiTokenRow>(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) {
new BEtapiToken(row);
}
});
@@ -68,13 +70,13 @@ function load() {
log.info(`Becca (note cache) load took ${Date.now() - start}ms`);
}
function reload(reason) {
function reload(reason: string) {
load();
require('../services/ws.js').reloadFrontend(reason || "becca reloaded");
require('../services/ws').reloadFrontend(reason || "becca reloaded");
}
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({ entityName, entityRow }) => {
if (!becca.loaded) {
return;
}
@@ -88,7 +90,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
if (beccaEntity) {
beccaEntity.updateFromRow(entityRow);
} else {
beccaEntity = new EntityClass();
beccaEntity = new EntityClass() as AbstractBeccaEntity<AbstractBeccaEntity<any>>;
beccaEntity.updateFromRow(entityRow);
beccaEntity.init();
}
@@ -97,7 +99,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
postProcessEntityUpdate(entityName, entityRow);
});
eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({ entityName, entity }) => {
if (!becca.loaded) {
return;
}
@@ -112,7 +114,7 @@ eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, en
* @param entityRow - can be a becca entity (change comes from this trilium instance) or just a row (from sync).
* It should be therefore treated as a row.
*/
function postProcessEntityUpdate(entityName, entityRow) {
function postProcessEntityUpdate(entityName: string, entityRow: any) {
if (entityName === 'notes') {
noteUpdated(entityRow);
} else if (entityName === 'branches') {
@@ -124,7 +126,7 @@ function postProcessEntityUpdate(entityName, entityRow) {
}
}
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => {
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({ entityName, entityId }) => {
if (!becca.loaded) {
return;
}
@@ -140,13 +142,13 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENT
}
});
function noteDeleted(noteId) {
function noteDeleted(noteId: string) {
delete becca.notes[noteId];
becca.dirtyNoteSetCache();
}
function branchDeleted(branchId) {
function branchDeleted(branchId: string) {
const branch = becca.branches[branchId];
if (!branch) {
@@ -173,23 +175,26 @@ function branchDeleted(branchId) {
}
delete becca.childParentToBranch[`${branch.noteId}-${branch.parentNoteId}`];
delete becca.branches[branch.branchId];
}
function noteUpdated(entityRow) {
const note = becca.notes[entityRow.noteId];
if (note) {
// type / mime could have been changed, and they are present in flatTextCache
note.flatTextCache = null;
if (branch.branchId) {
delete becca.branches[branch.branchId];
}
}
function branchUpdated(branchRow) {
function noteUpdated(entityRow: NoteRow) {
const note = becca.notes[entityRow.noteId];
if (note) {
// TODO, this wouldn't have worked in the original implementation since the variable was named __flatTextCache.
// type / mime could have been changed, and they are present in flatTextCache
note.__flatTextCache = null;
}
}
function branchUpdated(branchRow: BranchRow) {
const childNote = becca.notes[branchRow.noteId];
if (childNote) {
childNote.flatTextCache = null;
childNote.__flatTextCache = null;
childNote.sortParents();
// notes in the subtree can get new inherited attributes
@@ -204,7 +209,7 @@ function branchUpdated(branchRow) {
}
}
function attributeDeleted(attributeId) {
function attributeDeleted(attributeId: string) {
const attribute = becca.attributes[attributeId];
if (!attribute) {
@@ -239,8 +244,7 @@ function attributeDeleted(attributeId) {
}
}
/** @param {BAttribute} attributeRow */
function attributeUpdated(attributeRow) {
function attributeUpdated(attributeRow: BAttribute) {
const attribute = becca.attributes[attributeRow.attributeId];
const note = becca.notes[attributeRow.noteId];
@@ -253,7 +257,7 @@ function attributeUpdated(attributeRow) {
}
}
function noteReorderingUpdated(branchIdList) {
function noteReorderingUpdated(branchIdList: number[]) {
const parentNoteIds = new Set();
for (const branchId in branchIdList) {
@@ -267,7 +271,7 @@ function noteReorderingUpdated(branchIdList) {
}
}
function etapiTokenDeleted(etapiTokenId) {
function etapiTokenDeleted(etapiTokenId: string) {
delete becca.etapiTokens[etapiTokenId];
}
@@ -275,14 +279,14 @@ eventService.subscribeBeccaLoader(eventService.ENTER_PROTECTED_SESSION, () => {
try {
becca.decryptProtectedNotes();
}
catch (e) {
catch (e: any) {
log.error(`Could not decrypt protected notes: ${e.message} ${e.stack}`);
}
});
eventService.subscribeBeccaLoader(eventService.LEAVE_PROTECTED_SESSION, load);
module.exports = {
export = {
load,
reload,
beccaLoaded

View File

@@ -1,10 +1,10 @@
"use strict";
const becca = require('./becca.js');
const cls = require('../services/cls.js');
const log = require('../services/log.js');
import becca = require('./becca');
import cls = require('../services/cls');
import log = require('../services/log');
function isNotePathArchived(notePath) {
function isNotePathArchived(notePath: string[]) {
const noteId = notePath[notePath.length - 1];
const note = becca.notes[noteId];
@@ -24,9 +24,9 @@ function isNotePathArchived(notePath) {
return false;
}
function getNoteTitle(childNoteId, parentNoteId) {
function getNoteTitle(childNoteId: string, parentNoteId?: string) {
const childNote = becca.notes[childNoteId];
const parentNote = becca.notes[parentNoteId];
const parentNote = parentNoteId ? becca.notes[parentNoteId] : null;
if (!childNote) {
log.info(`Cannot find note '${childNoteId}'`);
@@ -40,7 +40,7 @@ function getNoteTitle(childNoteId, parentNoteId) {
return `${(branch && branch.prefix) ? `${branch.prefix} - ` : ''}${title}`;
}
function getNoteTitleArrayForPath(notePathArray) {
function getNoteTitleArrayForPath(notePathArray: string[]) {
if (!notePathArray || !Array.isArray(notePathArray)) {
throw new Error(`${notePathArray} is not an array.`);
}
@@ -76,13 +76,13 @@ function getNoteTitleArrayForPath(notePathArray) {
return titles;
}
function getNoteTitleForPath(notePathArray) {
function getNoteTitleForPath(notePathArray: string[]) {
const titles = getNoteTitleArrayForPath(notePathArray);
return titles.join(' / ');
}
module.exports = {
export = {
getNoteTitle,
getNoteTitleForPath,
isNotePathArchived

View File

@@ -1,66 +1,77 @@
"use strict";
const utils = require('../../services/utils.js');
const sql = require('../../services/sql.js');
const entityChangesService = require('../../services/entity_changes.js');
const eventService = require('../../services/events.js');
const dateUtils = require('../../services/date_utils.js');
const cls = require('../../services/cls.js');
const log = require('../../services/log.js');
const protectedSessionService = require('../../services/protected_session.js');
const blobService = require('../../services/blob.js');
import utils = require('../../services/utils');
import sql = require('../../services/sql');
import entityChangesService = require('../../services/entity_changes');
import eventService = require('../../services/events');
import dateUtils = require('../../services/date_utils');
import cls = require('../../services/cls');
import log = require('../../services/log');
import protectedSessionService = require('../../services/protected_session');
import blobService = require('../../services/blob');
import Becca, { ConstructorData } from '../becca-interface';
let becca = null;
let becca: Becca;
interface ContentOpts {
forceSave?: boolean;
forceFrontendReload?: boolean;
}
/**
* Base class for all backend entities.
*
* @type T the same entity type needed for self-reference in {@link ConstructorData}.
*/
class AbstractBeccaEntity {
/** @protected */
beforeSaving() {
if (!this[this.constructor.primaryKeyName]) {
this[this.constructor.primaryKeyName] = utils.newEntityId();
abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
utcDateModified?: string;
dateCreated?: string;
dateModified?: string;
utcDateCreated!: string;
isProtected?: boolean;
isSynced?: boolean;
blobId?: string;
protected beforeSaving() {
const constructorData = (this.constructor as unknown as ConstructorData<T>);
if (!(this as any)[constructorData.primaryKeyName]) {
(this as any)[constructorData.primaryKeyName] = utils.newEntityId();
}
}
/** @protected */
getUtcDateChanged() {
return this.utcDateModified || this.utcDateCreated;
}
/**
* @protected
* @returns {Becca}
*/
get becca() {
protected get becca(): Becca {
if (!becca) {
becca = require('../becca.js');
becca = require('../becca');
}
return becca;
return becca as Becca;
}
/** @protected */
putEntityChange(isDeleted) {
protected putEntityChange(isDeleted: boolean) {
const constructorData = (this.constructor as unknown as ConstructorData<T>);
entityChangesService.putEntityChange({
entityName: this.constructor.entityName,
entityId: this[this.constructor.primaryKeyName],
entityName: constructorData.entityName,
entityId: (this as any)[constructorData.primaryKeyName],
hash: this.generateHash(isDeleted),
isErased: false,
utcDateChanged: this.getUtcDateChanged(),
isSynced: this.constructor.entityName !== 'options' || !!this.isSynced
isSynced: constructorData.entityName !== 'options' || !!this.isSynced
});
}
/**
* @protected
* @returns {string}
*/
generateHash(isDeleted) {
generateHash(isDeleted?: boolean): string {
const constructorData = (this.constructor as unknown as ConstructorData<T>);
let contentToHash = "";
for (const propertyName of this.constructor.hashedProperties) {
contentToHash += `|${this[propertyName]}`;
for (const propertyName of constructorData.hashedProperties) {
contentToHash += `|${(this as any)[propertyName]}`;
}
if (isDeleted) {
@@ -70,31 +81,40 @@ class AbstractBeccaEntity {
return utils.hash(contentToHash).substr(0, 10);
}
/** @protected */
getPojoToSave() {
protected getPojoToSave() {
return this.getPojo();
}
/**
* @protected
* @abstract
*/
getPojo() {
throw new Error(`Unimplemented getPojo() for entity '${this.constructor.name}'`)
hasStringContent(): boolean {
// TODO: Not sure why some entities don't implement it.
return true;
}
abstract getPojo(): {};
init() {
// Do nothing by default, can be overriden in derived classes.
}
abstract updateFromRow(row: unknown): void;
get isDeleted(): boolean {
// TODO: Not sure why some entities don't implement it.
return false;
}
/**
* Saves entity - executes SQL, but doesn't commit the transaction on its own
*
* @returns {this}
*/
save(opts = {}) {
const entityName = this.constructor.entityName;
const primaryKeyName = this.constructor.primaryKeyName;
// TODO: opts not used but called a few times, maybe should be used by derived classes or passed to beforeSaving.
save(opts?: {}): this {
const constructorData = (this.constructor as unknown as ConstructorData<T>);
const entityName = constructorData.entityName;
const primaryKeyName = constructorData.primaryKeyName;
const isNewEntity = !this[primaryKeyName];
this.beforeSaving(opts);
const isNewEntity = !(this as any)[primaryKeyName];
this.beforeSaving();
const pojo = this.getPojoToSave();
@@ -124,14 +144,14 @@ class AbstractBeccaEntity {
return this;
}
/** @protected */
_setContent(content, opts = {}) {
protected _setContent(content: string | Buffer, opts: ContentOpts = {}) {
// client code asks to save entity even if blobId didn't change (something else was changed)
opts.forceSave = !!opts.forceSave;
opts.forceFrontendReload = !!opts.forceFrontendReload;
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`);
const constructorData = (this.constructor as unknown as ConstructorData<T>);
throw new Error(`Cannot set null content to ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}'`);
}
if (this.hasStringContent()) {
@@ -140,32 +160,36 @@ class AbstractBeccaEntity {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
const unencryptedContentForHashCalculation = this.#getUnencryptedContentForHashCalculation(content);
const unencryptedContentForHashCalculation = this.getUnencryptedContentForHashCalculation(content);
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.encrypt(content);
const encryptedContent = protectedSessionService.encrypt(content);
if (!encryptedContent) {
throw new Error(`Unable to encrypt the content of the entity.`);
}
content = encryptedContent;
} else {
throw new Error(`Cannot update content of blob since protected session is not available.`);
}
}
sql.transactional(() => {
const newBlobId = this.#saveBlob(content, unencryptedContentForHashCalculation, opts);
const newBlobId = this.saveBlob(content, unencryptedContentForHashCalculation, opts);
const oldBlobId = this.blobId;
if (newBlobId !== oldBlobId || opts.forceSave) {
this.blobId = newBlobId;
this.save();
if (newBlobId !== oldBlobId) {
this.#deleteBlobIfNotUsed(oldBlobId);
if (oldBlobId && newBlobId !== oldBlobId) {
this.deleteBlobIfNotUsed(oldBlobId);
}
}
});
}
#deleteBlobIfNotUsed(oldBlobId) {
private deleteBlobIfNotUsed(oldBlobId: string) {
if (sql.getValue("SELECT 1 FROM notes WHERE blobId = ? LIMIT 1", [oldBlobId])) {
return;
}
@@ -184,7 +208,7 @@ class AbstractBeccaEntity {
sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]);
}
#getUnencryptedContentForHashCalculation(unencryptedContent) {
private getUnencryptedContentForHashCalculation(unencryptedContent: Buffer | string) {
if (this.isProtected) {
// a "random" prefix makes sure that the calculated hash/blobId is different for a decrypted/encrypted content
const encryptedPrefixSuffix = "t$[nvQg7q)&_ENCRYPTED_?M:Bf&j3jr_";
@@ -196,7 +220,7 @@ class AbstractBeccaEntity {
}
}
#saveBlob(content, unencryptedContentForHashCalculation, opts = {}) {
private saveBlob(content: string | Buffer, unencryptedContentForHashCalculation: string | Buffer, opts: ContentOpts = {}) {
/*
* We're using the unencrypted blob for the hash calculation, because otherwise the random IV would
* cause every content blob to be unique which would balloon the database size (esp. with revisioning).
@@ -243,41 +267,37 @@ class AbstractBeccaEntity {
return newBlobId;
}
/**
* @protected
* @returns {string|Buffer}
*/
_getContent() {
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
protected _getContent(): string | Buffer {
const row = sql.getRow<{ content: string | Buffer }>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) {
throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`);
const constructorData = (this.constructor as unknown as ConstructorData<T>);
throw new Error(`Cannot find content for ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}', blobId '${this.blobId}'`);
}
return blobService.processContent(row.content, this.isProtected, this.hasStringContent());
return blobService.processContent(row.content, this.isProtected || false, this.hasStringContent());
}
/**
* Mark the entity as (soft) deleted. It will be completely erased later.
*
* This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
*
* @param [deleteId=null]
*/
markAsDeleted(deleteId = null) {
const entityId = this[this.constructor.primaryKeyName];
const entityName = this.constructor.entityName;
markAsDeleted(deleteId: string | null = null) {
const constructorData = (this.constructor as unknown as ConstructorData<T>);
const entityId = (this as any)[constructorData.primaryKeyName];
const entityName = constructorData.entityName;
this.utcDateModified = dateUtils.utcNowDateTime();
sql.execute(`UPDATE ${entityName} SET isDeleted = 1, deleteId = ?, utcDateModified = ?
WHERE ${this.constructor.primaryKeyName} = ?`,
WHERE ${constructorData.primaryKeyName} = ?`,
[deleteId, this.utcDateModified, entityId]);
if (this.dateModified) {
this.dateModified = dateUtils.localNowDateTime();
sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${this.constructor.primaryKeyName} = ?`,
sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${constructorData.primaryKeyName} = ?`,
[this.dateModified, entityId]);
}
@@ -289,13 +309,14 @@ class AbstractBeccaEntity {
}
markAsDeletedSimple() {
const entityId = this[this.constructor.primaryKeyName];
const entityName = this.constructor.entityName;
const constructorData = (this.constructor as unknown as ConstructorData<T>);
const entityId = (this as any)[constructorData.primaryKeyName];
const entityName = constructorData.entityName;
this.utcDateModified = dateUtils.utcNowDateTime();
sql.execute(`UPDATE ${entityName} SET isDeleted = 1, utcDateModified = ?
WHERE ${this.constructor.primaryKeyName} = ?`,
WHERE ${constructorData.primaryKeyName} = ?`,
[this.utcDateModified, entityId]);
log.info(`Marking ${entityName} ${entityId} as deleted`);
@@ -306,4 +327,4 @@ class AbstractBeccaEntity {
}
}
module.exports = AbstractBeccaEntity;
export = AbstractBeccaEntity;

View File

@@ -1,30 +1,64 @@
"use strict";
const utils = require('../../services/utils.js');
const dateUtils = require('../../services/date_utils.js');
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
const sql = require('../../services/sql.js');
const protectedSessionService = require('../../services/protected_session.js');
const log = require('../../services/log.js');
import utils = require('../../services/utils');
import dateUtils = require('../../services/date_utils');
import AbstractBeccaEntity = require('./abstract_becca_entity');
import sql = require('../../services/sql');
import protectedSessionService = require('../../services/protected_session');
import log = require('../../services/log');
import { AttachmentRow } from './rows';
import BNote = require('./bnote');
import BBranch = require('./bbranch');
const attachmentRoleToNoteTypeMapping = {
'image': 'image'
'image': 'image',
'file': 'file'
};
interface ContentOpts {
// TODO: Found in bnote.ts, to check if it's actually used and not a typo.
forceSave?: boolean;
/** will also save this BAttachment entity */
forceFullSave?: boolean;
/** override frontend heuristics on when to reload, instruct to reload */
forceFrontendReload?: boolean;
}
/**
* Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
* larger amounts of data and generally not accessible to the user.
*
* @extends AbstractBeccaEntity
*/
class BAttachment extends AbstractBeccaEntity {
class BAttachment extends AbstractBeccaEntity<BAttachment> {
static get entityName() { return "attachments"; }
static get primaryKeyName() { return "attachmentId"; }
static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; }
constructor(row) {
noteId?: number;
attachmentId?: string;
/** either noteId or revisionId to which this attachment belongs */
ownerId!: string;
role!: string;
mime!: string;
title!: string;
type?: keyof typeof attachmentRoleToNoteTypeMapping;
position?: number;
blobId?: string;
isProtected?: boolean;
dateModified?: string;
utcDateScheduledForErasureSince?: string | null;
/** optionally added to the entity */
contentLength?: number;
isDecrypted?: boolean;
constructor(row: AttachmentRow) {
super();
this.updateFromRow(row);
this.decrypt();
}
updateFromRow(row: AttachmentRow): void {
if (!row.ownerId?.trim()) {
throw new Error("'ownerId' must be given to initialize a Attachment entity");
} else if (!row.role?.trim()) {
@@ -35,43 +69,21 @@ class BAttachment extends AbstractBeccaEntity {
throw new Error("'title' must be given to initialize a Attachment entity");
}
/** @type {string} */
this.attachmentId = row.attachmentId;
/**
* either noteId or revisionId to which this attachment belongs
* @type {string}
*/
this.ownerId = row.ownerId;
/** @type {string} */
this.role = row.role;
/** @type {string} */
this.mime = row.mime;
/** @type {string} */
this.title = row.title;
/** @type {int} */
this.position = row.position;
/** @type {string} */
this.blobId = row.blobId;
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
this.dateModified = row.dateModified;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @type {string} */
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
/**
* optionally added to the entity
* @type {int}
*/
this.contentLength = row.contentLength;
this.decrypt();
}
/** @returns {BAttachment} */
copy() {
copy(): BAttachment {
return new BAttachment({
ownerId: this.ownerId,
role: this.role,
@@ -82,14 +94,13 @@ class BAttachment extends AbstractBeccaEntity {
});
}
/** @returns {BNote} */
getNote() {
getNote(): BNote {
return this.becca.notes[this.ownerId];
}
/** @returns {boolean} true if the note has string content (not binary) */
hasStringContent() {
return utils.isStringNote(this.type, this.mime);
/** @returns true if the note has string content (not binary) */
hasStringContent(): boolean {
return this.type !== undefined && utils.isStringNote(this.type, this.mime);
}
isContentAvailable() {
@@ -110,33 +121,26 @@ class BAttachment extends AbstractBeccaEntity {
if (!this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
try {
this.title = protectedSessionService.decryptString(this.title);
this.title = protectedSessionService.decryptString(this.title) || "";
this.isDecrypted = true;
}
catch (e) {
catch (e: any) {
log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`);
}
}
}
/** @returns {string|Buffer} */
getContent() {
return this._getContent();
getContent(): Buffer {
return this._getContent() as Buffer;
}
/**
* @param content
* @param {object} [opts]
* @param {object} [opts.forceSave=false] - will also save this BAttachment entity
* @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload
*/
setContent(content, opts) {
setContent(content: string | Buffer, opts?: ContentOpts) {
this._setContent(content, opts);
}
/** @returns {{note: BNote, branch: BBranch}} */
convertToNote() {
if (this.type === 'search') {
convertToNote(): { note: BNote, branch: BBranch } {
// TODO: can this ever be "search"?
if (this.type as string === 'search') {
throw new Error(`Note of type search cannot have child notes`);
}
@@ -153,12 +157,12 @@ class BAttachment extends AbstractBeccaEntity {
throw new Error(`Cannot convert protected attachment outside of protected session`);
}
const noteService = require('../../services/notes.js');
const noteService = require('../../services/notes');
const { note, branch } = noteService.createNewNote({
parentNoteId: this.ownerId,
title: this.title,
type: attachmentRoleToNoteTypeMapping[this.role],
type: (attachmentRoleToNoteTypeMapping as any)[this.role],
mime: this.mime,
content: this.getContent(),
isProtected: this.isProtected
@@ -170,6 +174,11 @@ class BAttachment extends AbstractBeccaEntity {
if (this.role === 'image' && parentNote.type === 'text') {
const origContent = parentNote.getContent();
if (typeof origContent !== "string") {
throw new Error(`Note with ID '${note.noteId} has a text type but non-string content.`);
}
const oldAttachmentUrl = `api/attachments/${this.attachmentId}/image/`;
const newNoteUrl = `api/images/${note.noteId}/`;
@@ -195,9 +204,9 @@ class BAttachment extends AbstractBeccaEntity {
super.beforeSaving();
if (this.position === undefined || this.position === null) {
this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0)
FROM attachments
WHERE ownerId = ?`, [this.noteId]);
this.position = 10 + sql.getValue<number>(`SELECT COALESCE(MAX(position), 0)
FROM attachments
WHERE ownerId = ?`, [this.noteId]);
}
this.dateModified = dateUtils.localNowDateTime();
@@ -210,7 +219,7 @@ class BAttachment extends AbstractBeccaEntity {
ownerId: this.ownerId,
role: this.role,
mime: this.mime,
title: this.title,
title: this.title || undefined,
position: this.position,
blobId: this.blobId,
isProtected: !!this.isProtected,
@@ -228,7 +237,7 @@ class BAttachment extends AbstractBeccaEntity {
if (pojo.isProtected) {
if (this.isDecrypted) {
pojo.title = protectedSessionService.encrypt(pojo.title);
pojo.title = protectedSessionService.encrypt(pojo.title || "") || undefined;
}
else {
// updating protected note outside of protected session means we will keep original ciphertexts
@@ -240,4 +249,4 @@ class BAttachment extends AbstractBeccaEntity {
}
}
module.exports = BAttachment;
export = BAttachment;

View File

@@ -1,30 +1,34 @@
"use strict";
const BNote = require('./bnote.js');
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
const sql = require('../../services/sql.js');
const dateUtils = require('../../services/date_utils.js');
const promotedAttributeDefinitionParser = require('../../services/promoted_attribute_definition_parser.js');
const {sanitizeAttributeName} = require('../../services/sanitize_attribute_name.js');
import BNote = require('./bnote');
import AbstractBeccaEntity = require('./abstract_becca_entity');
import dateUtils = require('../../services/date_utils');
import promotedAttributeDefinitionParser = require('../../services/promoted_attribute_definition_parser');
import sanitizeAttributeName = require('../../services/sanitize_attribute_name');
import { AttributeRow, AttributeType } from './rows';
/**
* There are currently only two types of attributes, labels or relations.
* @typedef {"label" | "relation"} AttributeType
*/
interface SavingOpts {
skipValidation?: boolean;
}
/**
* Attribute is an abstract concept which has two real uses - label (key - value pair)
* and relation (representing named relationship between source and target note)
*
* @extends AbstractBeccaEntity
*/
class BAttribute extends AbstractBeccaEntity {
class BAttribute extends AbstractBeccaEntity<BAttribute> {
static get entityName() { return "attributes"; }
static get primaryKeyName() { return "attributeId"; }
static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable"]; }
constructor(row) {
attributeId!: string;
noteId!: string;
type!: AttributeType;
name!: string;
position!: number;
value!: string;
isInheritable!: boolean;
constructor(row?: AttributeRow) {
super();
if (!row) {
@@ -35,7 +39,7 @@ class BAttribute extends AbstractBeccaEntity {
this.init();
}
updateFromRow(row) {
updateFromRow(row: AttributeRow) {
this.update([
row.attributeId,
row.noteId,
@@ -48,22 +52,14 @@ class BAttribute extends AbstractBeccaEntity {
]);
}
update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]) {
/** @type {string} */
update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]: any) {
this.attributeId = attributeId;
/** @type {string} */
this.noteId = noteId;
/** @type {AttributeType} */
this.type = type;
/** @type {string} */
this.name = name;
/** @type {int} */
this.position = position;
/** @type {string} */
this.value = value || "";
/** @type {boolean} */
this.isInheritable = !!isInheritable;
/** @type {string} */
this.utcDateModified = utcDateModified;
return this;
@@ -129,9 +125,6 @@ class BAttribute extends AbstractBeccaEntity {
}
}
/**
* @returns {BNote|null}
*/
getNote() {
const note = this.becca.getNote(this.noteId);
@@ -142,9 +135,6 @@ class BAttribute extends AbstractBeccaEntity {
return note;
}
/**
* @returns {BNote|null}
*/
getTargetNote() {
if (this.type !== 'relation') {
throw new Error(`Attribute '${this.attributeId}' is not a relation.`);
@@ -157,9 +147,6 @@ class BAttribute extends AbstractBeccaEntity {
return this.becca.getNote(this.value);
}
/**
* @returns {boolean}
*/
isDefinition() {
return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:'));
}
@@ -182,12 +169,12 @@ class BAttribute extends AbstractBeccaEntity {
return !(this.attributeId in this.becca.attributes);
}
beforeSaving(opts = {}) {
beforeSaving(opts: SavingOpts = {}) {
if (!opts.skipValidation) {
this.validate();
}
this.name = sanitizeAttributeName(this.name);
this.name = sanitizeAttributeName.sanitizeAttributeName(this.name);
if (!this.value) {
// null value isn't allowed
@@ -226,7 +213,7 @@ class BAttribute extends AbstractBeccaEntity {
};
}
createClone(type, name, value, isInheritable) {
createClone(type: AttributeType, name: string, value: string, isInheritable?: boolean) {
return new BAttribute({
noteId: this.noteId,
type: type,
@@ -239,4 +226,4 @@ class BAttribute extends AbstractBeccaEntity {
}
}
module.exports = BAttribute;
export = BAttribute;

View File

@@ -1,25 +1,35 @@
class BBlob {
import AbstractBeccaEntity = require("./abstract_becca_entity");
import { BlobRow } from "./rows";
// TODO: Why this does not extend the abstract becca?
class BBlob extends AbstractBeccaEntity<BBlob> {
static get entityName() { return "blobs"; }
static get primaryKeyName() { return "blobId"; }
static get hashedProperties() { return ["blobId", "content"]; }
constructor(row) {
/** @type {string} */
blobId!: string;
content!: string | Buffer;
contentLength!: number;
dateModified!: string;
utcDateModified!: string;
constructor(row: BlobRow) {
super();
this.updateFromRow(row);
}
updateFromRow(row: BlobRow): void {
this.blobId = row.blobId;
/** @type {string|Buffer} */
this.content = row.content;
/** @type {int} */
this.contentLength = row.contentLength;
/** @type {string} */
this.dateModified = row.dateModified;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
}
getPojo() {
return {
blobId: this.blobId,
content: this.content,
content: this.content || null,
contentLength: this.contentLength,
dateModified: this.dateModified,
utcDateModified: this.utcDateModified
@@ -27,4 +37,4 @@ class BBlob {
}
}
module.exports = BBlob;
export = BBlob;

View File

@@ -1,12 +1,13 @@
"use strict";
const BNote = require('./bnote.js');
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
const dateUtils = require('../../services/date_utils.js');
const utils = require('../../services/utils.js');
const TaskContext = require('../../services/task_context.js');
const cls = require('../../services/cls.js');
const log = require('../../services/log.js');
import BNote = require('./bnote');
import AbstractBeccaEntity = require('./abstract_becca_entity');
import dateUtils = require('../../services/date_utils');
import utils = require('../../services/utils');
import TaskContext = require('../../services/task_context');
import cls = require('../../services/cls');
import log = require('../../services/log');
import { BranchRow } from './rows';
/**
* Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
@@ -14,16 +15,22 @@ const log = require('../../services/log.js');
*
* Note that you should not rely on the branch's identity, since it can change easily with a note's move.
* Always check noteId instead.
*
* @extends AbstractBeccaEntity
*/
class BBranch extends AbstractBeccaEntity {
class BBranch extends AbstractBeccaEntity<BBranch> {
static get entityName() { return "branches"; }
static get primaryKeyName() { return "branchId"; }
// notePosition is not part of hash because it would produce a lot of updates in case of reordering
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "prefix"]; }
constructor(row) {
branchId?: string;
noteId!: string;
parentNoteId!: string;
prefix!: string | null;
notePosition!: number;
isExpanded!: boolean;
utcDateModified?: string;
constructor(row?: BranchRow) {
super();
if (!row) {
@@ -34,7 +41,7 @@ class BBranch extends AbstractBeccaEntity {
this.init();
}
updateFromRow(row) {
updateFromRow(row: BranchRow) {
this.update([
row.branchId,
row.noteId,
@@ -46,20 +53,13 @@ class BBranch extends AbstractBeccaEntity {
]);
}
update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]) {
/** @type {string} */
update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]: any) {
this.branchId = branchId;
/** @type {string} */
this.noteId = noteId;
/** @type {string} */
this.parentNoteId = parentNoteId;
/** @type {string|null} */
this.prefix = prefix;
/** @type {int} */
this.notePosition = notePosition;
/** @type {boolean} */
this.isExpanded = !!isExpanded;
/** @type {string} */
this.utcDateModified = utcDateModified;
return this;
@@ -83,18 +83,18 @@ class BBranch extends AbstractBeccaEntity {
}
const parentNote = this.parentNote;
if (!childNote.parents.includes(parentNote)) {
childNote.parents.push(parentNote);
}
if (!parentNote.children.includes(childNote)) {
parentNote.children.push(childNote);
if (parentNote) {
if (!childNote.parents.includes(parentNote)) {
childNote.parents.push(parentNote);
}
if (!parentNote.children.includes(childNote)) {
parentNote.children.push(childNote);
}
}
}
/** @returns {BNote} */
get childNote() {
get childNote(): BNote {
if (!(this.noteId in this.becca.notes)) {
// entities can come out of order in sync/import, create skeleton which will be filled later
this.becca.addNote(this.noteId, new BNote({noteId: this.noteId}));
@@ -103,13 +103,12 @@ class BBranch extends AbstractBeccaEntity {
return this.becca.notes[this.noteId];
}
/** @returns {BNote} */
getNote() {
getNote(): BNote {
return this.childNote;
}
/** @returns {BNote|undefined} - root branch will have undefined parent, all other branches have to have a parent note */
get parentNote() {
/** @returns root branch will have undefined parent, all other branches have to have a parent note */
get parentNote(): BNote | undefined {
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 BNote({noteId: this.parentNoteId}));
@@ -119,7 +118,7 @@ class BBranch extends AbstractBeccaEntity {
}
get isDeleted() {
return !(this.branchId in this.becca.branches);
return (this.branchId == undefined || !(this.branchId in this.becca.branches));
}
/**
@@ -128,8 +127,6 @@ class BBranch extends AbstractBeccaEntity {
* An example is shared or bookmarked clones - they are created automatically and exist for technical reasons,
* not as user-intended actions. From user perspective, they don't count as real clones and for the purpose
* of deletion should not act as a clone.
*
* @returns {boolean}
*/
get isWeak() {
return ['_share', '_lbBookmarks'].includes(this.parentNoteId);
@@ -138,12 +135,11 @@ class BBranch extends AbstractBeccaEntity {
/**
* Delete a branch. If this is a last note's branch, delete the note as well.
*
* @param {string} [deleteId] - optional delete identified
* @param {TaskContext} [taskContext]
* @param deleteId - optional delete identified
*
* @returns {boolean} - true if note has been deleted, false otherwise
* @returns true if note has been deleted, false otherwise
*/
deleteBranch(deleteId, taskContext) {
deleteBranch(deleteId?: string, taskContext?: TaskContext): boolean {
if (!deleteId) {
deleteId = utils.randomString(10);
}
@@ -161,7 +157,7 @@ class BBranch extends AbstractBeccaEntity {
if (parentBranches.length === 1 && parentBranches[0] === this) {
// needs to be run before branches and attributes are deleted and thus attached relations disappear
const handlers = require('../../services/handlers.js');
const handlers = require('../../services/handlers');
handlers.runAttachedRelations(note, 'runOnNoteDeletion', note);
}
}
@@ -182,7 +178,9 @@ class BBranch extends AbstractBeccaEntity {
}
for (const childBranch of note.getChildBranches()) {
childBranch.deleteBranch(deleteId, taskContext);
if (childBranch) {
childBranch.deleteBranch(deleteId, taskContext);
}
}
// first delete children and then parent - this will show up better in recent changes
@@ -222,11 +220,17 @@ class BBranch extends AbstractBeccaEntity {
if (this.notePosition === undefined || this.notePosition === null) {
let maxNotePos = 0;
for (const childBranch of this.parentNote.getChildBranches()) {
if (maxNotePos < childBranch.notePosition
&& childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last
) {
maxNotePos = childBranch.notePosition;
if (this.parentNote) {
for (const childBranch of this.parentNote.getChildBranches()) {
if (!childBranch) {
continue;
}
if (maxNotePos < childBranch.notePosition
&& childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last
) {
maxNotePos = childBranch.notePosition;
}
}
}
@@ -261,17 +265,19 @@ class BBranch extends AbstractBeccaEntity {
};
}
createClone(parentNoteId, notePosition) {
createClone(parentNoteId: string, notePosition?: number) {
const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId);
if (existingBranch) {
existingBranch.notePosition = notePosition;
if (notePosition) {
existingBranch.notePosition = notePosition;
}
return existingBranch;
} else {
return new BBranch({
noteId: this.noteId,
parentNoteId: parentNoteId,
notePosition: notePosition,
notePosition: notePosition || null,
prefix: this.prefix,
isExpanded: this.isExpanded
});
@@ -279,4 +285,4 @@ class BBranch extends AbstractBeccaEntity {
}
}
module.exports = BBranch;
export = BBranch;

View File

@@ -1,7 +1,9 @@
"use strict";
const dateUtils = require('../../services/date_utils.js');
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
import { EtapiTokenRow } from "./rows";
import dateUtils = require('../../services/date_utils');
import AbstractBeccaEntity = require('./abstract_becca_entity');
/**
* EtapiToken is an entity representing token used to authenticate against Trilium REST API from client applications.
@@ -11,15 +13,18 @@ const AbstractBeccaEntity = require('./abstract_becca_entity.js');
*
* The format user is presented with is "<etapiTokenId>_<tokenHash>". This is also called "authToken" to distinguish it
* from tokenHash and token.
*
* @extends AbstractBeccaEntity
*/
class BEtapiToken extends AbstractBeccaEntity {
class BEtapiToken extends AbstractBeccaEntity<BEtapiToken> {
static get entityName() { return "etapi_tokens"; }
static get primaryKeyName() { return "etapiTokenId"; }
static get hashedProperties() { return ["etapiTokenId", "name", "tokenHash", "utcDateCreated", "utcDateModified", "isDeleted"]; }
constructor(row) {
etapiTokenId?: string;
name!: string;
tokenHash!: string;
private _isDeleted?: boolean;
constructor(row?: EtapiTokenRow) {
super();
if (!row) {
@@ -30,19 +35,17 @@ class BEtapiToken extends AbstractBeccaEntity {
this.init();
}
updateFromRow(row) {
/** @type {string} */
get isDeleted() {
return !!this._isDeleted;
}
updateFromRow(row: EtapiTokenRow) {
this.etapiTokenId = row.etapiTokenId;
/** @type {string} */
this.name = row.name;
/** @type {string} */
this.tokenHash = row.tokenHash;
/** @type {string} */
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
/** @type {string} */
this.utcDateModified = row.utcDateModified || this.utcDateCreated;
/** @type {boolean} */
this.isDeleted = !!row.isDeleted;
this._isDeleted = !!row.isDeleted;
if (this.etapiTokenId) {
this.becca.etapiTokens[this.etapiTokenId] = this;
@@ -71,8 +74,10 @@ class BEtapiToken extends AbstractBeccaEntity {
super.beforeSaving();
this.becca.etapiTokens[this.etapiTokenId] = this;
if (this.etapiTokenId) {
this.becca.etapiTokens[this.etapiTokenId] = this;
}
}
}
module.exports = BEtapiToken;
export = BEtapiToken;

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,34 @@
"use strict";
const dateUtils = require('../../services/date_utils.js');
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
import dateUtils = require('../../services/date_utils');
import AbstractBeccaEntity = require('./abstract_becca_entity');
import { OptionRow } from './rows';
/**
* Option represents a name-value pair, either directly configurable by the user or some system property.
*
* @extends AbstractBeccaEntity
*/
class BOption extends AbstractBeccaEntity {
class BOption extends AbstractBeccaEntity<BOption> {
static get entityName() { return "options"; }
static get primaryKeyName() { return "name"; }
static get hashedProperties() { return ["name", "value"]; }
constructor(row) {
name!: string;
value!: string;
isSynced!: boolean;
constructor(row?: OptionRow) {
super();
this.updateFromRow(row);
if (row) {
this.updateFromRow(row);
}
this.becca.options[this.name] = this;
}
updateFromRow(row) {
/** @type {string} */
updateFromRow(row: OptionRow) {
this.name = row.name;
/** @type {string} */
this.value = row.value;
/** @type {boolean} */
this.isSynced = !!row.isSynced;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
}
@@ -47,4 +48,4 @@ class BOption extends AbstractBeccaEntity {
}
}
module.exports = BOption;
export = BOption;

View File

@@ -1,25 +1,31 @@
"use strict";
const dateUtils = require('../../services/date_utils.js');
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
import { RecentNoteRow } from "./rows";
import dateUtils = require('../../services/date_utils');
import AbstractBeccaEntity = require('./abstract_becca_entity');
/**
* RecentNote represents recently visited note.
*
* @extends AbstractBeccaEntity
*/
class BRecentNote extends AbstractBeccaEntity {
class BRecentNote extends AbstractBeccaEntity<BRecentNote> {
static get entityName() { return "recent_notes"; }
static get primaryKeyName() { return "noteId"; }
static get hashedProperties() { return ["noteId", "notePath"]; }
constructor(row) {
noteId!: string;
notePath!: string;
utcDateCreated!: string;
constructor(row: RecentNoteRow) {
super();
/** @type {string} */
this.updateFromRow(row);
}
updateFromRow(row: RecentNoteRow): void {
this.noteId = row.noteId;
/** @type {string} */
this.notePath = row.notePath;
/** @type {string} */
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
}
@@ -32,4 +38,4 @@ class BRecentNote extends AbstractBeccaEntity {
}
}
module.exports = BRecentNote;
export = BRecentNote;

View File

@@ -1,68 +1,79 @@
"use strict";
const protectedSessionService = require('../../services/protected_session.js');
const utils = require('../../services/utils.js');
const dateUtils = require('../../services/date_utils.js');
const becca = require('../becca.js');
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
const sql = require('../../services/sql.js');
const BAttachment = require('./battachment.js');
import protectedSessionService = require('../../services/protected_session');
import utils = require('../../services/utils');
import dateUtils = require('../../services/date_utils');
import becca = require('../becca');
import AbstractBeccaEntity = require('./abstract_becca_entity');
import sql = require('../../services/sql');
import BAttachment = require('./battachment');
import { AttachmentRow, RevisionRow } from './rows';
interface ContentOpts {
/** will also save this BRevision entity */
forceSave?: boolean;
}
interface GetByIdOpts {
includeContentLength?: boolean;
}
/**
* Revision represents a snapshot of note's title and content at some point in the past.
* It's used for seamless note versioning.
*
* @extends AbstractBeccaEntity
*/
class BRevision extends AbstractBeccaEntity {
class BRevision extends AbstractBeccaEntity<BRevision> {
static get entityName() { return "revisions"; }
static get primaryKeyName() { return "revisionId"; }
static get hashedProperties() { return ["revisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated",
"utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; }
constructor(row, titleDecrypted = false) {
revisionId?: string;
noteId!: string;
type!: string;
mime!: string;
isProtected!: boolean;
title!: string;
blobId?: string;
dateLastEdited?: string;
dateCreated!: string;
utcDateLastEdited?: string;
utcDateCreated!: string;
contentLength?: number;
content?: string | Buffer;
constructor(row: RevisionRow, titleDecrypted = false) {
super();
/** @type {string} */
this.revisionId = row.revisionId;
/** @type {string} */
this.noteId = row.noteId;
/** @type {string} */
this.type = row.type;
/** @type {string} */
this.mime = row.mime;
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
this.title = row.title;
/** @type {string} */
this.blobId = row.blobId;
/** @type {string} */
this.dateLastEdited = row.dateLastEdited;
/** @type {string} */
this.dateCreated = row.dateCreated;
/** @type {string} */
this.utcDateLastEdited = row.utcDateLastEdited;
/** @type {string} */
this.utcDateCreated = row.utcDateCreated;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @type {int} */
this.contentLength = row.contentLength;
this.updateFromRow(row);
if (this.isProtected && !titleDecrypted) {
this.title = protectedSessionService.isProtectedSessionAvailable()
? protectedSessionService.decryptString(this.title)
: "[protected]";
const decryptedTitle = protectedSessionService.isProtectedSessionAvailable() ? protectedSessionService.decryptString(this.title) : null;
this.title = decryptedTitle || "[protected]";
}
}
updateFromRow(row: RevisionRow) {
this.revisionId = row.revisionId;
this.noteId = row.noteId;
this.type = row.type;
this.mime = row.mime;
this.isProtected = !!row.isProtected;
this.title = row.title;
this.blobId = row.blobId;
this.dateLastEdited = row.dateLastEdited;
this.dateCreated = row.dateCreated;
this.utcDateLastEdited = row.utcDateLastEdited;
this.utcDateCreated = row.utcDateCreated;
this.utcDateModified = row.utcDateModified;
this.contentLength = row.contentLength;
}
getNote() {
return becca.notes[this.noteId];
}
/** @returns {boolean} true if the note has string content (not binary) */
hasStringContent() {
/** @returns true if the note has string content (not binary) */
hasStringContent(): boolean {
return utils.isStringNote(this.type, this.mime);
}
@@ -80,27 +91,24 @@ class BRevision extends AbstractBeccaEntity {
*
* This is the same approach as is used for Note's content.
*/
/** @returns {string|Buffer} */
getContent() {
getContent(): string | Buffer {
return this._getContent();
}
/**
* @returns {*}
* @throws Error in case of invalid JSON */
getJsonContent() {
getJsonContent(): {} | null {
const content = this.getContent();
if (!content || !content.trim()) {
if (!content || typeof content !== "string" || !content.trim()) {
return null;
}
return JSON.parse(content);
}
/** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
getJsonContentSafely() {
/** @returns valid object or null if the content cannot be parsed as JSON */
getJsonContentSafely(): {} | null {
try {
return this.getJsonContent();
}
@@ -109,18 +117,12 @@ class BRevision extends AbstractBeccaEntity {
}
}
/**
* @param content
* @param {object} [opts]
* @param {object} [opts.forceSave=false] - will also save this BRevision entity
*/
setContent(content, opts) {
setContent(content: string | Buffer, opts: ContentOpts = {}) {
this._setContent(content, opts);
}
/** @returns {BAttachment[]} */
getAttachments() {
return sql.getRows(`
getAttachments(): BAttachment[] {
return sql.getRows<AttachmentRow>(`
SELECT attachments.*
FROM attachments
WHERE ownerId = ?
@@ -128,8 +130,7 @@ class BRevision extends AbstractBeccaEntity {
.map(row => new BAttachment(row));
}
/** @returns {BAttachment|null} */
getAttachmentById(attachmentId, opts = {}) {
getAttachmentById(attachmentId: String, opts: GetByIdOpts = {}): BAttachment | null {
opts.includeContentLength = !!opts.includeContentLength;
const query = opts.includeContentLength
@@ -139,13 +140,12 @@ class BRevision extends AbstractBeccaEntity {
WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
: `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
return sql.getRows(query, [this.revisionId, attachmentId])
return sql.getRows<AttachmentRow>(query, [this.revisionId, attachmentId])
.map(row => new BAttachment(row))[0];
}
/** @returns {BAttachment[]} */
getAttachmentsByRole(role) {
return sql.getRows(`
getAttachmentsByRole(role: string): BAttachment[] {
return sql.getRows<AttachmentRow>(`
SELECT attachments.*
FROM attachments
WHERE ownerId = ?
@@ -155,12 +155,18 @@ class BRevision extends AbstractBeccaEntity {
.map(row => new BAttachment(row));
}
/** @returns {BAttachment} */
getAttachmentByTitle(title) {
getAttachmentByTitle(title: string): BAttachment {
// cannot use SQL to filter by title since it can be encrypted
return this.getAttachments().filter(attachment => attachment.title === title)[0];
}
/**
* Revisions are not soft-deletable, they are immediately hard-deleted (erased).
*/
eraseRevision() {
require("../../services/erase.js").eraseRevisions([this.revisionId]);
}
beforeSaving() {
super.beforeSaving();
@@ -174,7 +180,7 @@ class BRevision extends AbstractBeccaEntity {
type: this.type,
mime: this.mime,
isProtected: this.isProtected,
title: this.title,
title: this.title || undefined,
blobId: this.blobId,
dateLastEdited: this.dateLastEdited,
dateCreated: this.dateCreated,
@@ -193,7 +199,7 @@ class BRevision extends AbstractBeccaEntity {
if (pojo.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
pojo.title = protectedSessionService.encrypt(this.title);
pojo.title = protectedSessionService.encrypt(this.title) || undefined;
}
else {
// updating protected note outside of protected session means we will keep original ciphertexts
@@ -205,4 +211,4 @@ class BRevision extends AbstractBeccaEntity {
}
}
module.exports = BRevision;
export = BRevision;

111
src/becca/entities/rows.ts Normal file
View File

@@ -0,0 +1,111 @@
// TODO: Booleans should probably be numbers instead (as SQLite does not have booleans.);
export interface AttachmentRow {
attachmentId?: string;
ownerId?: string;
role: string;
mime: string;
title: string;
position?: number;
blobId?: string;
isProtected?: boolean;
dateModified?: string;
utcDateModified?: string;
utcDateScheduledForErasureSince?: string;
contentLength?: number;
content?: Buffer | string;
}
export interface RevisionRow {
revisionId?: string;
noteId: string;
type: string;
mime: string;
isProtected?: boolean;
title: string;
blobId?: string;
dateLastEdited?: string;
dateCreated: string;
utcDateLastEdited?: string;
utcDateCreated: string;
utcDateModified: string;
contentLength?: number;
}
export interface RecentNoteRow {
noteId: string;
notePath: string;
utcDateCreated?: string;
}
export interface OptionRow {
name: string;
value: string;
isSynced: boolean;
utcDateModified: string;
}
export interface EtapiTokenRow {
etapiTokenId?: string;
name: string;
tokenHash: string;
utcDateCreated?: string;
utcDateModified?: string;
isDeleted?: boolean;
}
export interface BlobRow {
blobId: string;
content: string | Buffer;
contentLength: number;
dateModified: string;
utcDateModified: string;
}
export type AttributeType = "label" | "relation" | "label-definition" | "relation-definition";
export interface AttributeRow {
attributeId?: string;
noteId?: string;
type: AttributeType;
name: string;
position?: number;
value?: string;
isInheritable?: boolean;
utcDateModified?: string;
}
export interface BranchRow {
branchId?: string;
noteId: string;
parentNoteId: string;
prefix?: string | null;
notePosition?: number | null;
isExpanded?: boolean;
isDeleted?: boolean;
utcDateModified?: string;
}
/**
* There are many different Note types, some of which are entirely opaque to the
* end user. Those types should be used only for checking against, they are
* not for direct use.
*/
export const ALLOWED_NOTE_TYPES = [ "file", "image", "search", "noteMap", "launcher", "doc", "contentWidget", "text", "relationMap", "render", "canvas", "mermaid", "book", "webView", "code" ] as const;
export type NoteType = typeof ALLOWED_NOTE_TYPES[number];
export interface NoteRow {
noteId: string;
deleteId: string;
title: string;
type: NoteType;
mime: string;
isProtected: boolean;
isDeleted: boolean;
blobId: string;
dateCreated: string;
dateModified: string;
utcDateCreated: string;
utcDateModified: string;
content?: string | Buffer;
}

View File

@@ -1,33 +0,0 @@
const BAttachment = require('./entities/battachment.js');
const BAttribute = require('./entities/battribute.js');
const BBlob = require('./entities/bblob.js');
const BBranch = require('./entities/bbranch.js');
const BEtapiToken = require('./entities/betapi_token.js');
const BNote = require('./entities/bnote.js');
const BOption = require('./entities/boption.js');
const BRecentNote = require('./entities/brecent_note.js');
const BRevision = require('./entities/brevision.js');
const ENTITY_NAME_TO_ENTITY = {
"attachments": BAttachment,
"attributes": BAttribute,
"blobs": BBlob,
"branches": BBranch,
"etapi_tokens": BEtapiToken,
"notes": BNote,
"options": BOption,
"recent_notes": BRecentNote,
"revisions": BRevision
};
function getEntityFromEntityName(entityName) {
if (!(entityName in ENTITY_NAME_TO_ENTITY)) {
throw new Error(`Entity for table '${entityName}' not found!`);
}
return ENTITY_NAME_TO_ENTITY[entityName];
}
module.exports = {
getEntityFromEntityName
};

View File

@@ -0,0 +1,37 @@
import { ConstructorData } from './becca-interface';
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
import BAttachment = require('./entities/battachment');
import BAttribute = require('./entities/battribute');
import BBlob = require('./entities/bblob');
import BBranch = require('./entities/bbranch');
import BEtapiToken = require('./entities/betapi_token');
import BNote = require('./entities/bnote');
import BOption = require('./entities/boption');
import BRecentNote = require('./entities/brecent_note');
import BRevision = require('./entities/brevision');
type EntityClass = new (row?: any) => AbstractBeccaEntity<any>;
const ENTITY_NAME_TO_ENTITY: Record<string, ConstructorData<any> & EntityClass> = {
"attachments": BAttachment,
"attributes": BAttribute,
"blobs": BBlob,
"branches": BBranch,
"etapi_tokens": BEtapiToken,
"notes": BNote,
"options": BOption,
"recent_notes": BRecentNote,
"revisions": BRevision
};
function getEntityFromEntityName(entityName: keyof typeof ENTITY_NAME_TO_ENTITY) {
if (!(entityName in ENTITY_NAME_TO_ENTITY)) {
throw new Error(`Entity for table '${entityName}' not found!`);
}
return ENTITY_NAME_TO_ENTITY[entityName];
}
export = {
getEntityFromEntityName
};

View File

@@ -1,8 +1,9 @@
const becca = require('./becca.js');
const log = require('../services/log.js');
const beccaService = require('./becca_service.js');
const dateUtils = require('../services/date_utils.js');
const {JSDOM} = require("jsdom");
import becca = require('./becca');
import log = require('../services/log');
import beccaService = require('./becca_service');
import dateUtils = require('../services/date_utils');
import { JSDOM } from "jsdom";
import BNote = require('./entities/bnote');
const DEBUG = false;
@@ -32,21 +33,25 @@ const IGNORED_ATTR_NAMES = [
"pageurl",
];
function filterUrlValue(value) {
interface DateLimits {
minDate: string;
minExcludedDate: string;
maxExcludedDate: string;
maxDate: string;
}
function filterUrlValue(value: string) {
return value
.replace(/https?:\/\//ig, "")
.replace(/www.js\./ig, "")
.replace(/(\.net|\.com|\.org|\.info|\.edu)/ig, "");
}
/**
* @param {BNote} note
*/
function buildRewardMap(note) {
function buildRewardMap(note: BNote) {
// Need to use Map instead of object: https://github.com/zadam/trilium/issues/1895
const map = new Map();
function addToRewardMap(text, rewardFactor) {
function addToRewardMap(text: string | undefined | null, rewardFactor: number) {
if (!text) {
return;
}
@@ -126,7 +131,7 @@ function buildRewardMap(note) {
const content = note.getContent();
const dom = new JSDOM(content);
function addHeadingsToRewardMap(elName, rewardFactor) {
const addHeadingsToRewardMap = (elName: string, rewardFactor: number) => {
for (const el of dom.window.document.querySelectorAll(elName)) {
addToRewardMap(el.textContent, rewardFactor);
}
@@ -146,9 +151,9 @@ function buildRewardMap(note) {
return map;
}
const mimeCache = {};
const mimeCache: Record<string, string> = {};
function trimMime(mime) {
function trimMime(mime: string) {
if (!mime || mime === 'text/html') {
return;
}
@@ -173,7 +178,7 @@ function trimMime(mime) {
return mimeCache[mime];
}
function buildDateLimits(baseNote) {
function buildDateLimits(baseNote: BNote): DateLimits {
const dateCreatedTs = dateUtils.parseDateTime(baseNote.utcDateCreated).getTime();
return {
@@ -193,7 +198,7 @@ const WORD_BLACKLIST = [
"than", "then", "and", "either", "or", "neither", "nor", "both", "also"
];
function splitToWords(text) {
function splitToWords(text: string) {
let words = wordCache.get(text);
if (!words) {
@@ -221,13 +226,13 @@ function splitToWords(text) {
* includeNoteLink and imageLink relation mean that notes are clearly related, but so clearly
* that it doesn't actually need to be shown to the user.
*/
function hasConnectingRelation(sourceNote, targetNote) {
function hasConnectingRelation(sourceNote: BNote, targetNote: BNote) {
return sourceNote.getAttributes().find(attr => attr.type === 'relation'
&& ['includenotelink', 'imagelink'].includes(attr.name)
&& attr.value === targetNote.noteId);
}
async function findSimilarNotes(noteId) {
async function findSimilarNotes(noteId: string) {
const results = [];
let i = 0;
@@ -237,23 +242,23 @@ async function findSimilarNotes(noteId) {
return [];
}
let dateLimits;
let dateLimits: DateLimits;
try {
dateLimits = buildDateLimits(baseNote);
}
catch (e) {
catch (e: any) {
throw new Error(`Date limits failed with ${e.message}, entity: ${JSON.stringify(baseNote.getPojo())}`);
}
const rewardMap = buildRewardMap(baseNote);
let ancestorRewardCache = {};
let ancestorRewardCache: Record<string, number> = {};
const ancestorNoteIds = new Set(baseNote.getAncestors().map(note => note.noteId));
ancestorNoteIds.add(baseNote.noteId);
let displayRewards = false;
function gatherRewards(text, factor = 1) {
function gatherRewards(text?: string | null, factor: number = 1) {
if (!text) {
return 0;
}
@@ -279,7 +284,7 @@ async function findSimilarNotes(noteId) {
return counter;
}
function gatherAncestorRewards(note) {
function gatherAncestorRewards(note?: BNote) {
if (!note || ancestorNoteIds.has(note.noteId)) {
return 0;
}
@@ -311,7 +316,7 @@ async function findSimilarNotes(noteId) {
return ancestorRewardCache[note.noteId];
}
function computeScore(candidateNote) {
function computeScore(candidateNote: BNote) {
let score = gatherRewards(trimMime(candidateNote.mime))
+ gatherAncestorRewards(candidateNote);
@@ -451,11 +456,11 @@ async function findSimilarNotes(noteId) {
* see https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/
*/
function setImmediatePromise() {
return new Promise((resolve) => {
return new Promise<void>((resolve) => {
setTimeout(() => resolve(), 0);
});
}
module.exports = {
export = {
findSimilarNotes
};

View File

@@ -1,7 +0,0 @@
class NotFoundError {
constructor(message) {
this.message = message;
}
}
module.exports = NotFoundError;

View File

@@ -0,0 +1,9 @@
class NotFoundError {
message: string;
constructor(message: string) {
this.message = message;
}
}
export = NotFoundError;

View File

@@ -1,7 +0,0 @@
class ValidationError {
constructor(message) {
this.message = message;
}
}
module.exports = ValidationError;

View File

@@ -0,0 +1,9 @@
class ValidationError {
message: string;
constructor(message: string) {
this.message = message;
}
}
export = ValidationError;

View File

@@ -1,12 +0,0 @@
const appInfo = require('../services/app_info.js');
const eu = require('./etapi_utils.js');
function register(router) {
eu.route(router, 'get', '/etapi/app-info', (req, res, next) => {
res.status(200).json(appInfo);
});
}
module.exports = {
register
};

13
src/etapi/app_info.ts Normal file
View File

@@ -0,0 +1,13 @@
import { Router } from 'express';
import appInfo = require('../services/app_info');
import eu = require('./etapi_utils');
function register(router: Router) {
eu.route(router, 'get', '/etapi/app-info', (req, res, next) => {
res.status(200).json(appInfo);
});
}
export = {
register
};

View File

@@ -1,11 +1,13 @@
const becca = require('../becca/becca.js');
const eu = require('./etapi_utils.js');
const mappers = require('./mappers.js');
const v = require('./validators.js');
const utils = require('../services/utils.js');
import becca = require('../becca/becca');
import eu = require('./etapi_utils');
import mappers = require('./mappers');
import v = require('./validators');
import utils = require('../services/utils');
import { Router } from 'express';
import { AttachmentRow } from '../becca/entities/rows';
function register(router) {
const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT = {
function register(router: Router) {
const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT: ValidatorMap = {
'ownerId': [v.notNull, v.isNoteId],
'role': [v.notNull, v.isString],
'mime': [v.notNull, v.isString],
@@ -14,18 +16,21 @@ function register(router) {
'content': [v.isString],
};
eu.route(router, 'post' ,'/etapi/attachments', (req, res, next) => {
const params = {};
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT);
eu.route(router, 'post', '/etapi/attachments', (req, res, next) => {
const _params: Partial<AttachmentRow> = {};
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT);
const params = _params as AttachmentRow;
try {
if (!params.ownerId) {
throw new Error("Missing owner ID.");
}
const note = becca.getNoteOrThrow(params.ownerId);
const attachment = note.saveAttachment(params);
res.status(201).json(mappers.mapAttachmentToPojo(attachment));
}
catch (e) {
catch (e: any) {
throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message);
}
});
@@ -43,7 +48,7 @@ function register(router) {
'position': [v.notNull, v.isInteger],
};
eu.route(router, 'patch' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
eu.route(router, 'patch', '/etapi/attachments/:attachmentId', (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
if (attachment.isProtected) {
@@ -85,7 +90,7 @@ function register(router) {
return res.sendStatus(204);
});
eu.route(router, 'delete' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
eu.route(router, 'delete', '/etapi/attachments/:attachmentId', (req, res, next) => {
const attachment = becca.getAttachment(req.params.attachmentId);
if (!attachment) {
@@ -98,6 +103,6 @@ function register(router) {
});
}
module.exports = {
export = {
register
};

View File

@@ -1,17 +1,19 @@
const becca = require('../becca/becca.js');
const eu = require('./etapi_utils.js');
const mappers = require('./mappers.js');
const attributeService = require('../services/attributes.js');
const v = require('./validators.js');
import becca = require('../becca/becca');
import eu = require('./etapi_utils');
import mappers = require('./mappers');
import attributeService = require('../services/attributes');
import v = require('./validators');
import { Router } from 'express';
import { AttributeRow } from '../becca/entities/rows';
function register(router) {
function register(router: Router) {
eu.route(router, 'get', '/etapi/attributes/:attributeId', (req, res, next) => {
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
res.json(mappers.mapAttributeToPojo(attribute));
});
const ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE = {
const ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE: ValidatorMap = {
'attributeId': [v.mandatory, v.notNull, v.isValidEntityId],
'noteId': [v.mandatory, v.notNull, v.isNoteId],
'type': [v.mandatory, v.notNull, v.isAttributeType],
@@ -21,21 +23,21 @@ function register(router) {
'position': [v.notNull, v.isInteger]
};
eu.route(router, 'post' ,'/etapi/attributes', (req, res, next) => {
eu.route(router, 'post', '/etapi/attributes', (req, res, next) => {
if (req.body.type === 'relation') {
eu.getAndCheckNote(req.body.value);
}
const params = {};
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE);
const _params = {};
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE);
const params: AttributeRow = _params as AttributeRow;
try {
const attr = attributeService.createAttribute(params);
res.status(201).json(mappers.mapAttributeToPojo(attr));
}
catch (e) {
catch (e: any) {
throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message);
}
});
@@ -49,7 +51,7 @@ function register(router) {
'position': [v.notNull, v.isInteger]
};
eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => {
eu.route(router, 'patch', '/etapi/attributes/:attributeId', (req, res, next) => {
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
if (attribute.type === 'label') {
@@ -65,7 +67,7 @@ function register(router) {
res.json(mappers.mapAttributeToPojo(attribute));
});
eu.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => {
eu.route(router, 'delete', '/etapi/attributes/:attributeId', (req, res, next) => {
const attribute = becca.getAttribute(req.params.attributeId);
if (!attribute) {
@@ -78,6 +80,6 @@ function register(router) {
});
}
module.exports = {
export = {
register
};

View File

@@ -1,9 +1,10 @@
const becca = require('../becca/becca.js');
const eu = require('./etapi_utils.js');
const passwordEncryptionService = require('../services/encryption/password_encryption.js');
const etapiTokenService = require('../services/etapi_tokens.js');
import becca = require('../becca/becca');
import eu = require('./etapi_utils');
import passwordEncryptionService = require('../services/encryption/password_encryption');
import etapiTokenService = require('../services/etapi_tokens');
import { RequestHandler, Router } from 'express';
function register(router, loginMiddleware) {
function register(router: Router, loginMiddleware: RequestHandler[]) {
eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', loginMiddleware, (req, res, next) => {
const {password, tokenName} = req.body;
@@ -38,6 +39,6 @@ function register(router, loginMiddleware) {
});
}
module.exports = {
export = {
register
}

View File

@@ -1,7 +1,9 @@
const eu = require('./etapi_utils.js');
const backupService = require('../services/backup.js');
import { Router } from "express";
function register(router) {
import eu = require('./etapi_utils');
import backupService = require('../services/backup');
function register(router: Router) {
eu.route(router, 'put', '/etapi/backup/:backupName', async (req, res, next) => {
await backupService.backupNow(req.params.backupName);
@@ -9,6 +11,6 @@ function register(router) {
});
}
module.exports = {
export = {
register
};

View File

@@ -1,11 +1,14 @@
const becca = require('../becca/becca.js');
const eu = require('./etapi_utils.js');
const mappers = require('./mappers.js');
const BBranch = require('../becca/entities/bbranch.js');
const entityChangesService = require('../services/entity_changes.js');
const v = require('./validators.js');
import { Router } from "express";
function register(router) {
import becca = require('../becca/becca');
import eu = require('./etapi_utils');
import mappers = require('./mappers');
import BBranch = require('../becca/entities/bbranch');
import entityChangesService = require('../services/entity_changes');
import v = require('./validators');
import { BranchRow } from "../becca/entities/rows";
function register(router: Router) {
eu.route(router, 'get', '/etapi/branches/:branchId', (req, res, next) => {
const branch = eu.getAndCheckBranch(req.params.branchId);
@@ -20,17 +23,17 @@ function register(router) {
'isExpanded': [v.notNull, v.isBoolean]
};
eu.route(router, 'post' ,'/etapi/branches', (req, res, next) => {
const params = {};
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH);
eu.route(router, 'post', '/etapi/branches', (req, res, next) => {
const _params = {};
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH);
const params: BranchRow = _params as BranchRow;
const existing = becca.getBranchFromChildAndParent(params.noteId, params.parentNoteId);
if (existing) {
existing.notePosition = params.notePosition;
existing.prefix = params.prefix;
existing.isExpanded = params.isExpanded;
existing.notePosition = params.notePosition as number;
existing.prefix = params.prefix as string;
existing.isExpanded = params.isExpanded as boolean;
existing.save();
return res.status(200).json(mappers.mapBranchToPojo(existing));
@@ -39,7 +42,7 @@ function register(router) {
const branch = new BBranch(params).save();
res.status(201).json(mappers.mapBranchToPojo(branch));
} catch (e) {
} catch (e: any) {
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
}
}
@@ -51,7 +54,7 @@ function register(router) {
'isExpanded': [v.notNull, v.isBoolean]
};
eu.route(router, 'patch' ,'/etapi/branches/:branchId', (req, res, next) => {
eu.route(router, 'patch', '/etapi/branches/:branchId', (req, res, next) => {
const branch = eu.getAndCheckBranch(req.params.branchId);
eu.validateAndPatch(branch, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
@@ -60,7 +63,7 @@ function register(router) {
res.json(mappers.mapBranchToPojo(branch));
});
eu.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => {
eu.route(router, 'delete', '/etapi/branches/:branchId', (req, res, next) => {
const branch = becca.getBranch(req.params.branchId);
if (!branch) {
@@ -72,7 +75,7 @@ function register(router) {
res.sendStatus(204);
});
eu.route(router, 'post' ,'/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => {
eu.route(router, 'post', '/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => {
eu.getAndCheckNote(req.params.parentNoteId);
entityChangesService.putNoteReorderingEntityChange(req.params.parentNoteId, "etapi");
@@ -81,6 +84,6 @@ function register(router) {
});
}
module.exports = {
export = {
register
};

View File

@@ -0,0 +1,3 @@
type ValidatorFunc = (obj: unknown) => (string | undefined);
type ValidatorMap = Record<string, ValidatorFunc[]>;

View File

@@ -1,24 +1,33 @@
const cls = require('../services/cls.js');
const sql = require('../services/sql.js');
const log = require('../services/log.js');
const becca = require('../becca/becca.js');
const etapiTokenService = require('../services/etapi_tokens.js');
const config = require('../services/config.js');
import cls = require('../services/cls');
import sql = require('../services/sql');
import log = require('../services/log');
import becca = require('../becca/becca');
import etapiTokenService = require('../services/etapi_tokens');
import config = require('../services/config');
import { NextFunction, Request, RequestHandler, Response, Router } from 'express';
import { AppRequest, AppRequestHandler } from '../routes/route-interface';
const GENERIC_CODE = "GENERIC";
type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head";
const noAuthentication = config.General && config.General.noAuthentication === true;
class EtapiError extends Error {
constructor(statusCode, code, message) {
super();
statusCode: number;
code: string;
constructor(statusCode: number, code: string, message: string) {
super(message);
// Set the prototype explicitly.
Object.setPrototypeOf(this, EtapiError.prototype);
this.statusCode = statusCode;
this.code = code;
this.message = message;
}
}
function sendError(res, statusCode, code, message) {
function sendError(res: Response, statusCode: number, code: string, message: string) {
return res
.set('Content-Type', 'application/json')
.status(statusCode)
@@ -29,7 +38,7 @@ function sendError(res, statusCode, code, message) {
}));
}
function checkEtapiAuth(req, res, next) {
function checkEtapiAuth(req: Request, res: Response, next: NextFunction) {
if (noAuthentication || etapiTokenService.isValidAuthHeader(req.headers.authorization)) {
next();
}
@@ -38,7 +47,7 @@ function checkEtapiAuth(req, res, next) {
}
}
function processRequest(req, res, routeHandler, next, method, path) {
function processRequest(req: Request, res: Response, routeHandler: AppRequestHandler, next: NextFunction, method: string, path: string) {
try {
cls.namespace.bindEmitter(req);
cls.namespace.bindEmitter(res);
@@ -47,11 +56,11 @@ function processRequest(req, res, routeHandler, next, method, path) {
cls.set('componentId', "etapi");
cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
const cb = () => routeHandler(req, res, next);
const cb = () => routeHandler(req as AppRequest, res, next);
return sql.transactional(cb);
});
} catch (e) {
} catch (e: any) {
log.error(`${method} ${path} threw exception ${e.message} with stacktrace: ${e.stack}`);
if (e instanceof EtapiError) {
@@ -62,15 +71,15 @@ function processRequest(req, res, routeHandler, next, method, path) {
}
}
function route(router, method, path, routeHandler) {
router[method](path, checkEtapiAuth, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
function route(router: Router, method: HttpMethod, path: string, routeHandler: AppRequestHandler) {
router[method](path, checkEtapiAuth, (req: Request, res: Response, next: NextFunction) => 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 NOT_AUTHENTICATED_ROUTE(router: Router, method: HttpMethod, path: string, middleware: RequestHandler[], routeHandler: RequestHandler) {
router[method](path, ...middleware, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
}
function getAndCheckNote(noteId) {
function getAndCheckNote(noteId: string) {
const note = becca.getNote(noteId);
if (note) {
@@ -81,7 +90,7 @@ function getAndCheckNote(noteId) {
}
}
function getAndCheckAttachment(attachmentId) {
function getAndCheckAttachment(attachmentId: string) {
const attachment = becca.getAttachment(attachmentId, {includeContentLength: true});
if (attachment) {
@@ -92,7 +101,7 @@ function getAndCheckAttachment(attachmentId) {
}
}
function getAndCheckBranch(branchId) {
function getAndCheckBranch(branchId: string) {
const branch = becca.getBranch(branchId);
if (branch) {
@@ -103,7 +112,7 @@ function getAndCheckBranch(branchId) {
}
}
function getAndCheckAttribute(attributeId) {
function getAndCheckAttribute(attributeId: string) {
const attribute = becca.getAttribute(attributeId);
if (attribute) {
@@ -114,7 +123,7 @@ function getAndCheckAttribute(attributeId) {
}
}
function validateAndPatch(target, source, allowedProperties) {
function validateAndPatch(target: any, source: any, allowedProperties: ValidatorMap) {
for (const key of Object.keys(source)) {
if (!(key in allowedProperties)) {
throw new EtapiError(400, "PROPERTY_NOT_ALLOWED", `Property '${key}' is not allowed for this method.`);
@@ -136,7 +145,7 @@ function validateAndPatch(target, source, allowedProperties) {
}
}
module.exports = {
export = {
EtapiError,
sendError,
route,

View File

@@ -1,5 +1,9 @@
/** @param {BNote} note */
function mapNoteToPojo(note) {
import BAttachment = require("../becca/entities/battachment");
import BAttribute = require("../becca/entities/battribute");
import BBranch = require("../becca/entities/bbranch");
import BNote = require("../becca/entities/bnote");
function mapNoteToPojo(note: BNote) {
return {
noteId: note.noteId,
isProtected: note.isProtected,
@@ -19,8 +23,7 @@ function mapNoteToPojo(note) {
};
}
/** @param {BBranch} branch */
function mapBranchToPojo(branch) {
function mapBranchToPojo(branch: BBranch) {
return {
branchId: branch.branchId,
noteId: branch.noteId,
@@ -32,8 +35,7 @@ function mapBranchToPojo(branch) {
};
}
/** @param {BAttribute} attr */
function mapAttributeToPojo(attr) {
function mapAttributeToPojo(attr: BAttribute) {
return {
attributeId: attr.attributeId,
noteId: attr.noteId,
@@ -46,8 +48,7 @@ function mapAttributeToPojo(attr) {
};
}
/** @param {BAttachment} attachment */
function mapAttachmentToPojo(attachment) {
function mapAttachmentToPojo(attachment: BAttachment) {
return {
attachmentId: attachment.attachmentId,
ownerId: attachment.ownerId,
@@ -63,7 +64,7 @@ function mapAttachmentToPojo(attachment) {
};
}
module.exports = {
export = {
mapNoteToPojo,
mapBranchToPojo,
mapAttributeToPojo,

View File

@@ -1,20 +1,26 @@
const becca = require('../becca/becca.js');
const utils = require('../services/utils.js');
const eu = require('./etapi_utils.js');
const mappers = require('./mappers.js');
const noteService = require('../services/notes.js');
const TaskContext = require('../services/task_context.js');
const v = require('./validators.js');
const searchService = require('../services/search/services/search.js');
const SearchContext = require('../services/search/search_context.js');
const zipExportService = require('../services/export/zip.js');
const zipImportService = require('../services/import/zip.js');
import becca = require('../becca/becca');
import utils = require('../services/utils');
import eu = require('./etapi_utils');
import mappers = require('./mappers');
import noteService = require('../services/notes');
import TaskContext = require('../services/task_context');
import v = require('./validators');
import searchService = require('../services/search/services/search');
import SearchContext = require('../services/search/search_context');
import zipExportService = require('../services/export/zip');
import zipImportService = require('../services/import/zip');
import { Router } from 'express';
import { AppRequest } from '../routes/route-interface';
import { ParsedQs } from 'qs';
import { NoteParams } from '../services/note-interface';
import BNote = require('../becca/entities/bnote');
import { SearchParams } from '../services/search/services/types';
function register(router) {
function register(router: Router) {
eu.route(router, 'get', '/etapi/notes', (req, res, next) => {
const {search} = req.query;
const { search } = req.query;
if (!search?.trim()) {
if (typeof search !== "string" || !search?.trim()) {
throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory.");
}
@@ -24,8 +30,8 @@ function register(router) {
const searchResults = searchService.findResultsWithQuery(search, searchContext);
const foundNotes = searchResults.map(sr => becca.notes[sr.noteId]);
const resp = {
results: foundNotes.map(note => mappers.mapNoteToPojo(note))
const resp: any = {
results: foundNotes.map(note => mappers.mapNoteToPojo(note)),
};
if (searchContext.debugInfo) {
@@ -41,7 +47,7 @@ function register(router) {
res.json(mappers.mapNoteToPojo(note));
});
const ALLOWED_PROPERTIES_FOR_CREATE_NOTE = {
const ALLOWED_PROPERTIES_FOR_CREATE_NOTE: ValidatorMap = {
'parentNoteId': [v.mandatory, v.notNull, v.isNoteId],
'title': [v.mandatory, v.notNull, v.isString],
'type': [v.mandatory, v.notNull, v.isNoteType],
@@ -55,10 +61,10 @@ function register(router) {
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
};
eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => {
const params = {};
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE);
eu.route(router, 'post', '/etapi/create-note', (req, res, next) => {
const _params = {};
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE);
const params = _params as NoteParams;
try {
const resp = noteService.createNewNote(params);
@@ -68,7 +74,7 @@ function register(router) {
branch: mappers.mapBranchToPojo(resp.branch)
});
}
catch (e) {
catch (e: any) {
return eu.sendError(res, 500, eu.GENERIC_CODE, e.message);
}
});
@@ -81,7 +87,7 @@ function register(router) {
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
};
eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => {
eu.route(router, 'patch', '/etapi/notes/:noteId', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
if (note.isProtected) {
@@ -94,8 +100,8 @@ function register(router) {
res.json(mappers.mapNoteToPojo(note));
});
eu.route(router, 'delete' ,'/etapi/notes/:noteId', (req, res, next) => {
const {noteId} = req.params;
eu.route(router, 'delete', '/etapi/notes/:noteId', (req, res, next) => {
const { noteId } = req.params;
const note = becca.getNote(noteId);
@@ -139,11 +145,11 @@ function register(router) {
return res.sendStatus(204);
});
eu.route(router, 'get' ,'/etapi/notes/:noteId/export', (req, res, next) => {
eu.route(router, 'get', '/etapi/notes/:noteId/export', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const format = req.query.format || "html";
if (!["html", "markdown"].includes(format)) {
if (typeof format !== "string" || !["html", "markdown"].includes(format)) {
throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`);
}
@@ -153,10 +159,10 @@ function register(router) {
// (e.g. branchIds are not seen in UI), that we export "note export" instead.
const branch = note.getParentBranches()[0];
zipExportService.exportToZip(taskContext, branch, format, res);
zipExportService.exportToZip(taskContext, branch, format as "html" | "markdown", res);
});
eu.route(router, 'post' ,'/etapi/notes/:noteId/import', (req, res, next) => {
eu.route(router, 'post', '/etapi/notes/:noteId/import', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const taskContext = new TaskContext('no-progress-reporting');
@@ -168,7 +174,7 @@ function register(router) {
}); // we need better error handling here, async errors won't be properly processed.
});
eu.route(router, 'post' ,'/etapi/notes/:noteId/revision', (req, res, next) => {
eu.route(router, 'post', '/etapi/notes/:noteId/revision', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
note.saveRevision();
@@ -178,7 +184,7 @@ function register(router) {
eu.route(router, 'get', '/etapi/notes/:noteId/attachments', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const attachments = note.getAttachments({includeContentLength: true})
const attachments = note.getAttachments({ includeContentLength: true })
res.json(
attachments.map(attachment => mappers.mapAttachmentToPojo(attachment))
@@ -186,23 +192,24 @@ function register(router) {
});
}
function parseSearchParams(req) {
const rawSearchParams = {
function parseSearchParams(req: AppRequest) {
const rawSearchParams: SearchParams = {
fastSearch: parseBoolean(req.query, 'fastSearch'),
includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'),
ancestorNoteId: req.query['ancestorNoteId'],
ancestorDepth: req.query['ancestorDepth'], // e.g. "eq5"
orderBy: req.query['orderBy'],
orderDirection: parseOrderDirection(req.query, 'orderDirection'),
ancestorNoteId: parseString(req.query['ancestorNoteId']),
ancestorDepth: parseString(req.query['ancestorDepth']), // e.g. "eq5"
orderBy: parseString(req.query['orderBy']),
// TODO: Check why the order direction was provided as a number, but it's a string everywhere else.
orderDirection: parseOrderDirection(req.query, 'orderDirection') as unknown as string,
limit: parseInteger(req.query, 'limit'),
debug: parseBoolean(req.query, 'debug')
};
const searchParams = {};
const searchParams: SearchParams = {};
for (const paramName of Object.keys(rawSearchParams)) {
for (const paramName of Object.keys(rawSearchParams) as (keyof SearchParams)[]) {
if (rawSearchParams[paramName] !== undefined) {
searchParams[paramName] = rawSearchParams[paramName];
(searchParams as any)[paramName] = rawSearchParams[paramName];
}
}
@@ -211,7 +218,15 @@ function parseSearchParams(req) {
const SEARCH_PARAM_ERROR = "SEARCH_PARAM_VALIDATION_ERROR";
function parseBoolean(obj, name) {
function parseString(value: string | ParsedQs | string[] | ParsedQs[] | undefined): string | undefined {
if (typeof value === "string") {
return value;
}
return undefined;
}
function parseBoolean(obj: any, name: string) {
if (!(name in obj)) {
return undefined;
}
@@ -223,7 +238,7 @@ function parseBoolean(obj, name) {
return obj[name] === 'true';
}
function parseOrderDirection(obj, name) {
function parseOrderDirection(obj: any, name: string) {
if (!(name in obj)) {
return undefined;
}
@@ -237,7 +252,7 @@ function parseOrderDirection(obj, name) {
return integer;
}
function parseInteger(obj, name) {
function parseInteger(obj: any, name: string) {
if (!(name in obj)) {
return undefined;
}
@@ -251,6 +266,6 @@ function parseInteger(obj, name) {
return integer;
}
module.exports = {
export = {
register
};

View File

@@ -1,10 +1,12 @@
const fs = require('fs');
const path = require('path');
import { Router } from "express";
import fs = require('fs');
import path = require('path');
const specPath = path.join(__dirname, 'etapi.openapi.yaml');
let spec = null;
let spec: string | null = null;
function register(router) {
function register(router: Router) {
router.get('/etapi/etapi.openapi.yaml', (req, res, next) => {
if (!spec) {
spec = fs.readFileSync(specPath, 'utf8');
@@ -15,6 +17,6 @@ function register(router) {
});
}
module.exports = {
export = {
register
};

View File

@@ -1,13 +1,14 @@
const specialNotesService = require('../services/special_notes.js');
const dateNotesService = require('../services/date_notes.js');
const eu = require('./etapi_utils.js');
const mappers = require('./mappers.js');
import specialNotesService = require('../services/special_notes');
import dateNotesService = require('../services/date_notes');
import eu = require('./etapi_utils');
import mappers = require('./mappers');
import { Router } from 'express';
const getDateInvalidError = date => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
const getMonthInvalidError = month => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
const getYearInvalidError = year => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
const getDateInvalidError = (date: string) => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
const getMonthInvalidError = (month: string)=> new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
const getYearInvalidError = (year: string) => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
function isValidDate(date) {
function isValidDate(date: string) {
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) {
return false;
}
@@ -15,9 +16,9 @@ function isValidDate(date) {
return !!Date.parse(date);
}
function register(router) {
function register(router: Router) {
eu.route(router, 'get', '/etapi/inbox/:date', (req, res, next) => {
const {date} = req.params;
const { date } = req.params;
if (!isValidDate(date)) {
throw getDateInvalidError(date);
@@ -28,7 +29,7 @@ function register(router) {
});
eu.route(router, 'get', '/etapi/calendar/days/:date', (req, res, next) => {
const {date} = req.params;
const { date } = req.params;
if (!isValidDate(date)) {
throw getDateInvalidError(date);
@@ -39,7 +40,7 @@ function register(router) {
});
eu.route(router, 'get', '/etapi/calendar/weeks/:date', (req, res, next) => {
const {date} = req.params;
const { date } = req.params;
if (!isValidDate(date)) {
throw getDateInvalidError(date);
@@ -50,7 +51,7 @@ function register(router) {
});
eu.route(router, 'get', '/etapi/calendar/months/:month', (req, res, next) => {
const {month} = req.params;
const { month } = req.params;
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
throw getMonthInvalidError(month);
@@ -61,7 +62,7 @@ function register(router) {
});
eu.route(router, 'get', '/etapi/calendar/years/:year', (req, res, next) => {
const {year} = req.params;
const { year } = req.params;
if (!/[0-9]{4}/.test(year)) {
throw getYearInvalidError(year);
@@ -72,6 +73,6 @@ function register(router) {
});
}
module.exports = {
export = {
register
};

View File

@@ -1,19 +1,19 @@
const noteTypeService = require('../services/note_types.js');
const dateUtils = require('../services/date_utils.js');
import noteTypeService = require('../services/note_types');
import dateUtils = require('../services/date_utils');
function mandatory(obj) {
if (obj === undefined ) {
function mandatory(obj: unknown) {
if (obj === undefined) {
return `mandatory, but not set`;
}
}
function notNull(obj) {
function notNull(obj: unknown) {
if (obj === null) {
return `cannot be null`;
}
}
function isString(obj) {
function isString(obj: unknown) {
if (obj === undefined || obj === null) {
return;
}
@@ -23,23 +23,23 @@ function isString(obj) {
}
}
function isLocalDateTime(obj) {
if (obj === undefined || obj === null) {
function isLocalDateTime(obj: unknown) {
if (typeof obj !== "string") {
return;
}
return dateUtils.validateLocalDateTime(obj);
}
function isUtcDateTime(obj) {
if (obj === undefined || obj === null) {
function isUtcDateTime(obj: unknown) {
if (typeof obj !== "string") {
return;
}
return dateUtils.validateUtcDateTime(obj);
}
function isBoolean(obj) {
function isBoolean(obj: unknown) {
if (obj === undefined || obj === null) {
return;
}
@@ -49,7 +49,7 @@ function isBoolean(obj) {
}
}
function isInteger(obj) {
function isInteger(obj: unknown) {
if (obj === undefined || obj === null) {
return;
}
@@ -59,12 +59,12 @@ function isInteger(obj) {
}
}
function isNoteId(obj) {
function isNoteId(obj: unknown) {
if (obj === undefined || obj === null) {
return;
}
const becca = require('../becca/becca.js');
const becca = require('../becca/becca');
if (typeof obj !== 'string') {
return `'${obj}' is not a valid noteId`;
@@ -75,29 +75,29 @@ function isNoteId(obj) {
}
}
function isNoteType(obj) {
function isNoteType(obj: unknown) {
if (obj === undefined || obj === null) {
return;
}
const noteTypes = noteTypeService.getNoteTypeNames();
if (!noteTypes.includes(obj)) {
if (typeof obj !== "string" || !noteTypes.includes(obj)) {
return `'${obj}' is not a valid note type, allowed types are: ${noteTypes.join(", ")}`;
}
}
function isAttributeType(obj) {
function isAttributeType(obj: unknown) {
if (obj === undefined || obj === null) {
return;
}
if (!['label', 'relation'].includes(obj)) {
if (typeof obj !== "string" || !['label', 'relation'].includes(obj)) {
return `'${obj}' is not a valid attribute type, allowed types are: label, relation`;
}
}
function isValidEntityId(obj) {
function isValidEntityId(obj: unknown) {
if (obj === undefined || obj === null) {
return;
}
@@ -107,7 +107,7 @@ function isValidEntityId(obj) {
}
}
module.exports = {
export = {
mandatory,
notNull,
isString,

View File

@@ -645,7 +645,7 @@ export default class TabManager extends Component {
const titleFragments = [
// it helps to navigate in history if note title is included in the title
await activeNoteContext.getNavigationTitle(),
"Trilium Notes"
"TriliumNext Notes"
].filter(Boolean);
document.title = titleFragments.join(" - ");

View File

@@ -50,10 +50,10 @@ export default class TreeContextMenu {
{ title: 'Open in a new tab <kbd>Ctrl+Click</kbd>', command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
{ title: 'Open in a new split', command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "bx bx-plus",
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter", {removeDeprecatedTypes: true}) : null,
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
enabled: insertNoteAfterEnabled && noSelectedNotes },
{ title: 'Insert child note <kbd data-command="createNoteInto"></kbd>', command: "insertChildNote", uiIcon: "bx bx-plus",
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote", {removeDeprecatedTypes: true}) : null,
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
enabled: notSearch && noSelectedNotes },
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash",
enabled: isNotRoot && !isHoisted && parentNotSearch },

View File

@@ -4,6 +4,7 @@ import froca from "./froca.js";
import attributeRenderer from "./attribute_renderer.js";
import libraryLoader from "./library_loader.js";
import treeService from "./tree.js";
import utils from "./utils.js";
const TPL = `
<div class="note-list">
@@ -215,7 +216,11 @@ class NoteListRenderer {
if (highlightedTokens.length > 0) {
await libraryLoader.requireLibrary(libraryLoader.MARKJS);
this.highlightRegex = new RegExp(highlightedTokens.join("|"), 'gi');
const regex = highlightedTokens
.map(token => utils.escapeRegExp(token))
.join("|");
this.highlightRegex = new RegExp(regex, 'gi');
} else {
this.highlightRegex = null;
}

View File

@@ -1,21 +1,19 @@
import server from "./server.js";
import froca from "./froca.js";
async function getNoteTypeItems(command, opts = {}) {
const removeDeprecatedTypes = !!opts.removeDeprecatedTypes;
async function getNoteTypeItems(command) {
const items = [
{ title: "Text", command: command, type: "text", uiIcon: "bx bx-note" },
{ title: "Code", command: command, type: "code", uiIcon: "bx bx-code" },
{ title: "Saved Search", command: command, type: "search", uiIcon: "bx bx-file-find" },
{ title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bx-map-alt", deprecated: true },
{ title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bx-map-alt" },
{ title: "Note Map", command: command, type: "noteMap", uiIcon: "bx bx-map-alt" },
{ title: "Render Note", command: command, type: "render", uiIcon: "bx bx-extension" },
{ title: "Book", command: command, type: "book", uiIcon: "bx bx-book" },
{ title: "Mermaid Diagram", command: command, type: "mermaid", uiIcon: "bx bx-selection" },
{ title: "Canvas", command: command, type: "canvas", uiIcon: "bx bx-pen" },
{ title: "Web View", command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
].filter(item => !removeDeprecatedTypes || !item.deprecated);
];
const templateNoteIds = await server.get("search-templates");
const templateNotes = await froca.getNotes(templateNoteIds);

View File

@@ -487,12 +487,14 @@ function areObjectsEqual () {
}
function copyHtmlToClipboard(content) {
const clipboardItem = new ClipboardItem({
'text/html': new Blob([content], {type: 'text/html'}),
'text/plain': new Blob([content], {type: 'text/plain'})
});
navigator.clipboard.write([clipboardItem]);
function listener(e) {
e.clipboardData.setData("text/html", content);
e.clipboardData.setData("text/plain", content);
e.preventDefault();
}
document.addEventListener("copy", listener);
document.execCommand("copy");
document.removeEventListener("copy", listener);
}
/**

View File

@@ -204,7 +204,7 @@ const TPL = `
<li class="dropdown-item show-about-dialog-button">
<span class="bx bx-empty"></span>
About Trilium Notes
About TriliumNext Notes
</li>
<li class="dropdown-item update-to-latest-version-button" data-trigger-command="downloadLatestVersion">

View File

@@ -7,7 +7,7 @@ const TPL = `
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">About Trilium Notes</h5>
<h5 class="modal-title mr-auto">About TriliumNext Notes</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>

View File

@@ -1,10 +1,7 @@
import libraryLoader from "../../services/library_loader.js";
import TypeWidget from "./type_widget.js";
import libraryLoader from '../../services/library_loader.js';
import TypeWidget from './type_widget.js';
import utils from '../../services/utils.js';
import linkService from '../../services/link.js';
import debounce from "../../services/debounce.js";
const {sleep} = utils;
const TPL = `
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
@@ -105,8 +102,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
this.SCENE_VERSION_INITIAL = -1; // -1 indicates that it is fresh. excalidraw scene version is always >0
this.SCENE_VERSION_ERROR = -2; // -2 indicates error
// config
this.DEBOUNCE_TIME_ONCHANGEHANDLER = 750; // ms
// ensure that assets are loaded from trilium
window.EXCALIDRAW_ASSET_PATH = `${window.location.origin}/node_modules/@excalidraw/excalidraw/dist/`;
@@ -115,16 +110,10 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
this.currentSceneVersion = this.SCENE_VERSION_INITIAL;
// will be overwritten
this.excalidrawRef;
this.$render;
this.$widget;
this.reactHandlers; // used to control react state
// binds
this.createExcalidrawReactApp = this.createExcalidrawReactApp.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
this.isNewSceneVersion = this.isNewSceneVersion.bind(this);
this.libraryChanged = false;
}
@@ -155,7 +144,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
const renderElement = this.$render.get(0);
ReactDOM.unmountComponentAtNode(renderElement);
ReactDOM.render(React.createElement(this.createExcalidrawReactApp), renderElement);
const root = ReactDOM.createRoot(renderElement);
root.render(React.createElement(() => this.createExcalidrawReactApp()));
});
return this.$widget;
@@ -179,9 +169,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
const blob = await note.getBlob();
// before we load content into excalidraw, make sure excalidraw has loaded
while (!this.excalidrawRef?.current) {
console.log("excalidrawRef not yet loaded, sleep 200ms...");
await sleep(200);
while (!this.excalidrawApi) {
console.log("excalidrawApi not yet loaded, sleep 200ms...");
await utils.sleep(200);
}
/**
@@ -199,7 +189,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
collaborators: []
};
this.excalidrawRef.current.updateScene(sceneData);
this.excalidrawApi.updateScene(sceneData);
}
else if (blob.content) {
// load saved content into excalidraw canvas
@@ -246,9 +236,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
fileArray.push(file);
}
this.excalidrawRef.current.updateScene(sceneData);
this.excalidrawRef.current.addFiles(fileArray);
this.excalidrawRef.current.history.clear();
this.excalidrawApi.updateScene(sceneData);
this.excalidrawApi.addFiles(fileArray);
this.excalidrawApi.history.clear();
}
Promise.all(
@@ -261,7 +251,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
}
const libraryItems = blobs.map(blob => blob.getJsonContentSafely()).filter(item => !!item);
this.excalidrawRef.current.updateLibrary({libraryItems, merge: false});
this.excalidrawApi.updateLibrary({libraryItems, merge: false});
});
// set initial scene version
@@ -275,17 +265,17 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
* this is automatically called after this.saveData();
*/
async getData() {
const elements = this.excalidrawRef.current.getSceneElements();
const appState = this.excalidrawRef.current.getAppState();
const elements = this.excalidrawApi.getSceneElements();
const appState = this.excalidrawApi.getAppState();
/**
* A file is not deleted, even though removed from canvas. Therefore, we only keep
* files that are referenced by an element. Maybe this will change with a new excalidraw version?
*/
const files = this.excalidrawRef.current.getFiles();
const files = this.excalidrawApi.getFiles();
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
const svg = await window.ExcalidrawLib.exportToSvg({
const svg = await ExcalidrawLib.exportToSvg({
elements,
appState,
exportPadding: 5, // 5 px padding
@@ -321,7 +311,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
// this.libraryChanged is unset in dataSaved()
// there's no separate method to get library items, so have to abuse this one
const libraryItems = await this.excalidrawRef.current.updateLibrary({merge: true});
const libraryItems = await this.excalidrawApi.updateLibrary({merge: true});
let position = 10;
@@ -379,9 +369,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
createExcalidrawReactApp() {
const React = window.React;
const { Excalidraw } = window.ExcalidrawLib;
const excalidrawRef = React.useRef(null);
this.excalidrawRef = excalidrawRef;
const excalidrawWrapperRef = React.useRef(null);
this.excalidrawWrapperRef = excalidrawWrapperRef;
const [dimensions, setDimensions] = React.useState({
@@ -439,7 +426,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
React.createElement(Excalidraw, {
// this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu
theme: this.themeStyle,
ref: excalidrawRef,
excalidrawAPI: api => { this.excalidrawApi = api; },
width: dimensions.width,
height: dimensions.height,
onPaste: (data, event) => {
@@ -450,7 +437,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
this.saveData();
},
onChange: debounce(this.onChangeHandler, this.DEBOUNCE_TIME_ONCHANGEHANDLER),
onChange: () => this.onChangeHandler(),
viewModeEnabled: false,
zenModeEnabled: false,
gridModeEnabled: false,
@@ -483,8 +470,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
}
getSceneVersion() {
if (this.excalidrawRef) {
const elements = this.excalidrawRef.current.getSceneElements();
if (this.excalidrawApi) {
const elements = this.excalidrawApi.getSceneElements();
return window.ExcalidrawLib.getSceneVersion(elements);
} else {
return this.SCENE_VERSION_ERROR;

View File

@@ -184,8 +184,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
}
async loadMapData() {
toastService.showMessage("Relation Map has been deprecated since Trilium 0.63 and will be removed in a future version. Migrate your content to some other note type (e.g. canvas) as soon as possible.", 5000);
this.mapData = {
notes: [],
// it is important to have this exact value here so that initial transform is the same as this

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