Compare commits

..

483 Commits

Author SHA1 Message Date
Elian Doran
6734d765c9 Bump to 0.90.11-beta 2024-11-11 19:48:50 +02:00
Elian Doran
470594b1c7 Merge pull request #573 from TriliumNext/perfectra1n-patch-2
Update README to mention MacOS command fix
2024-11-11 19:42:56 +02:00
Elian Doran
782d34566d Merge pull request #577 from hasecilu/i18n/Spanish_mini
i18n: Update Spanish translations, 100%
2024-11-11 19:41:32 +02:00
hasecilu
1b2a772612 i18n: Update Spanish translations, 100% 2024-11-11 10:35:54 -06:00
Elian Doran
46218d6ab4 Merge pull request #574 from TriliumNext/fix-version-update-check
More reliably check for version updates
2024-11-11 00:47:51 +02:00
perf3ct
1d2366fa06 fix "click to download" button 2024-11-10 17:18:26 +00:00
perf3ct
0acba0eac4 add docstring for func 2024-11-09 22:23:02 +00:00
perf3ct
48d53e276e more reliably check for version numbers 2024-11-09 22:16:00 +00:00
Elian Doran
47baa02bca i18n: Translate 100% of Romanian 2024-11-09 23:34:18 +02:00
Elian Doran
bc35c3c641 i18n: Remove some German-only messages 2024-11-09 23:31:45 +02:00
Jon Fuller
790b87f23f Update README to mention MacOS command fix 2024-11-09 13:28:45 -08:00
Elian Doran
48ba15ad88 i18n: Fix incorrect IDs for German 2024-11-09 23:25:34 +02:00
Elian Doran
cda28cfd65 Merge pull request #561 from j13055/develop
added german translation
2024-11-09 23:17:23 +02:00
Elian Doran
7ffe145481 Merge pull request #569 from TriliumNext/perfectra1n-patch-2
Update README.md for incremented sync version
2024-11-09 23:14:53 +02:00
Elian Doran
ac2bca790b Fix duplicate title for Trilium toolbar item (fixes #525) 2024-11-09 23:12:10 +02:00
Elian Doran
774966e640 client: Allow more link protocols (fixes #122) 2024-11-09 23:06:26 +02:00
Elian Doran
81310d33b0 Merge pull request #571 from TriliumNext/feature/classic_editor
Classic editor for text notes (with fixed toolbar)
2024-11-09 22:40:38 +02:00
Jon Fuller
34e6430977 Update README.md
Co-authored-by: Elian Doran <contact@eliandoran.me>
2024-11-09 12:30:52 -08:00
Elian Doran
15b4eacdca client: Change design of editor settings slightly 2024-11-09 21:35:37 +02:00
Elian Doran
7c342aed9e client: Use translations for editor settings 2024-11-09 21:34:09 +02:00
Elian Doran
8c69d47aed client,server: Implement shortcut for toggle classic editor toolbar 2024-11-09 18:36:38 +02:00
Elian Doran
f88d3220b5 client: Repair attribute editor 2024-11-09 18:09:05 +02:00
Elian Doran
70a98a3d33 client: Use refactored version of CKEditor 2024-11-09 15:40:14 +02:00
Elian Doran
6e0a10cf2c client: Hide ribbon tab when classic editor is off 2024-11-09 14:54:04 +02:00
Elian Doran
c421e75f55 client: Respect editor type choice 2024-11-09 14:49:05 +02:00
Elian Doran
89420eafa3 client: Set up ui for selecting editor UI 2024-11-09 14:33:20 +02:00
Elian Doran
7a70fc14b3 server: Set up editor type option 2024-11-09 14:33:14 +02:00
Elian Doran
d2008e7e5f client: Use different method to highlight disabled buttons 2024-11-09 14:15:03 +02:00
Elian Doran
745c9846a6 client: Use better method to expose CK watchdog 2024-11-09 14:13:08 +02:00
Elian Doran
3972bb2ecf client: Use build of CKEditor containing both types 2024-11-09 14:11:15 +02:00
Elian Doran
06262adf91 client: Use translation for classic toolbar title 2024-11-09 13:40:13 +02:00
Elian Doran
5771060b57 client: Reorganize classic toolbar 2024-11-09 13:39:24 +02:00
Elian Doran
6a11f9c073 client: Add some JSDoc 2024-11-09 10:46:12 +02:00
Elian Doran
85ee7def84 client: Improve loading feel for classic toolbar 2024-11-09 10:37:14 +02:00
Elian Doran
b88f0e0109 client: Hide ribbon for non text or read-only notes 2024-11-09 10:33:45 +02:00
Elian Doran
787aa6f5a6 client: Remove background for decoupled editor 2024-11-09 09:56:25 +02:00
Elian Doran
4f39188198 client: Use decoupled CKEditor 2024-11-09 09:43:37 +02:00
Elian Doran
dd6e762dab client: Activate ribbon toolbar by default 2024-11-09 09:19:38 +02:00
Elian Doran
48bc9204ac client: Create empty toolbar ribbon 2024-11-09 09:18:59 +02:00
Elian Doran
918f425e1f client: Group options for classic editor 2024-11-09 09:12:46 +02:00
Elian Doran
821af8dc11 client: Integrate block toolbar into classic options 2024-11-09 08:29:58 +02:00
Elian Doran
44734435ea client: Remove block toolbar in classic mode 2024-11-09 00:32:26 +02:00
Elian Doran
01c53b6d9f client: Use same config as bubble editor for classic 2024-11-09 00:21:27 +02:00
Elian Doran
9a5de0d4c8 client: Basic integration of classic editor w/ no attribute editor 2024-11-09 00:15:19 +02:00
Elian Doran
5116bddc5f client: Group image align buttons in CKEditor 2024-11-08 23:44:52 +02:00
Elian Doran
92aa671ec7 client: Support inline images in CKEditor (fixes #531) 2024-11-08 23:29:56 +02:00
Elian Doran
1f4d09f6f0 client: Patch CKEditor to fix IME (fixes #568)
See https://github.com/ckeditor/ckeditor5/pull/16289
2024-11-08 22:49:07 +02:00
Elian Doran
29e83b97e6 client: Fix rendering notes if hljs is not loaded 2024-11-08 21:50:22 +02:00
Jon Fuller
18de0857b3 Update README.md for incremented sync version 2024-11-08 10:43:45 -08:00
Elian Doran
2048a30aa5 Merge pull request #547 from TriliumNext/smaller-container
Make the container smaller
2024-11-08 19:20:48 +02:00
Elian Doran
78017e4d36 client: Improve classic toolbar layout on mobile 2024-11-08 00:26:20 +02:00
Elian Doran
35fe5845a3 client: Fix classic editor on mobile 2024-11-08 00:20:51 +02:00
Elian Doran
1261bdbb29 client: Use correct background for code note preview 2024-11-07 23:58:10 +02:00
Elian Doran
91fa1a6cb1 client: Add syntax highlight for code note previews 2024-11-07 23:53:02 +02:00
Elian Doran
1816fcd3ac client: force-graph: 1.45.0 -> 1.46.0 2024-11-07 23:11:22 +02:00
Elian Doran
d13044b972 client: mind-elixir: 4.3.0 -> 4.3.1 2024-11-07 23:09:59 +02:00
Elian Doran
f5205fdd30 electron: Fix code block theme loading in dev mode 2024-11-07 23:09:53 +02:00
Elian Doran
930b8e0ce2 Merge pull request #555 from rom1dep/mouse_scroll_dir
fix: mouse scroll wheel direction for zoom level
2024-11-07 22:25:24 +02:00
Elian Doran
b5988ba7c2 Merge pull request #559 from TriliumNext/siriusxt-test
Make attachments open in a new tab/browser
2024-11-07 22:22:42 +02:00
j13055
75e2ceed5d added german translation 2024-11-06 13:52:23 +01:00
SiriusXT
d2ee3738a2 Make attachments open in a new tab/browser 2024-11-06 10:02:42 +08:00
perf3ct
8a548f6589 also update the Alpine Dockerfile 2024-11-05 16:41:00 +00:00
perf3ct
0859a955b1 Results in a much smaller container 2024-11-04 17:38:05 -08:00
Elian Doran
a02146df17 server: Fix loading of code block theme on server builds 2024-11-05 02:58:21 +02:00
Elian Doran
a6385557b5 Merge pull request #545 from TriliumNext/latest-is-stable-container
Explicitly manage the "latest" tag, and have it point to the same tag as "stable"
2024-11-05 02:41:03 +02:00
Elian Doran
00aebfcdf0 Merge pull request #530 from Potjoe-97/patch-1
Patch fr translation
2024-11-05 02:38:53 +02:00
Elian Doran
c6b3ace807 client: mind-elixir: 4.2.4 -> 4.3.0 2024-11-05 02:33:12 +02:00
Elian Doran
6799544950 Update package-lock.json 2024-11-05 02:31:42 +02:00
Elian Doran
da1cf4d6ed Bump to 0.90.10-beta 2024-11-04 17:24:30 +02:00
Romain DEP.
21cfb64f83 fix: mouse scroll wheel direction 2024-11-03 23:01:01 +01:00
Adorian Doran
dd7c2084fa client: apply grouping to the MIME type list 2024-11-03 15:43:33 +02:00
Adorian Doran
4f5d874028 client: Use a multiple column layout for the MIME type listing 2024-11-03 15:42:13 +02:00
Potjoe-97
80e6276d31 Merge branch 'develop' into patch-1 2024-11-03 10:48:00 +01:00
Potjoe-97
0192060ad2 Update fr server.json : all strings translated 2024-11-03 10:44:56 +01:00
Potjoe-97
e41ff54c0d Update translation.json : all strings translated 2024-11-03 10:43:15 +01:00
perf3ct
bdece7216f have the latest tag be the same as stable tag
get rid of this annoying default "latest" tag useage

to squash

to squash, I love whitespace

don't need to verify
2024-11-02 21:51:06 +00:00
Elian Doran
611fb90a52 Merge pull request #544 from hasecilu/i18n/Spanish_update
i18n: Update Spanish translations
2024-11-02 21:45:41 +02:00
hasecilu
75e554d86b i18n: Update Spanish translations 2024-11-02 13:09:44 -06:00
Elian Doran
0db1a63cef client: Fix sync error toast 2024-11-02 19:02:26 +02:00
Elian Doran
4ffc6f716c client: Enable syntax highlighting in print 2024-11-02 16:40:33 +02:00
Elian Doran
fa3200ba8f electron: Fix docnotes not rendering 2024-11-02 16:11:59 +02:00
Elian Doran
bff9bedc44 i18n: Translate sync messages 2024-11-02 15:43:16 +02:00
Elian Doran
f8777b0de1 server: Fix path on dev environment 2024-11-02 15:01:58 +02:00
Adorian Doran
48e6c1a33d client: Properly align of the "Override theme fonts" checkbox 2024-11-02 14:34:55 +02:00
Adorian Doran
4c43ac5bdd client: Use a shadowless box for printed code blocks 2024-11-02 14:29:20 +02:00
Elian Doran
45ccc7562e client: Fix error in toast due to missing import 2024-11-02 12:19:17 +02:00
Elian Doran
e72eb5f27c electron: Fix asset path on forge build 2024-11-02 11:49:33 +02:00
Elian Doran
d1404492a7 build: Use shorter special version moniker
Some builds fail in the CI because the extra part of the version is limited to 20 chars.
2024-11-02 11:04:16 +02:00
Elian Doran
238c9c6f0d build: Fix updating nightly version for desktop builds 2024-11-02 10:43:42 +02:00
Elian Doran
443f02a78e client,server: i18next: 23.16.2 -> 23.16.4 2024-11-02 10:24:53 +02:00
Elian Doran
5fbd052138 build: Update tooling dependencies 2024-11-02 10:23:46 +02:00
Elian Doran
bc84a71929 client: mermaid: 11.3.0 -> 11.4.0 2024-11-02 10:22:22 +02:00
Elian Doran
a514a51fff client: mind-elixir: 4.2.3 -> 4.2.4 2024-11-02 10:18:58 +02:00
Elian Doran
24022834e2 db: Update demo section on code blocks 2024-11-02 10:15:22 +02:00
Elian Doran
9fdc84d91f build: Update nightly version for server as well 2024-11-02 09:53:36 +02:00
Elian Doran
9c27672794 build: Update nightly version to avoid caching issues 2024-11-02 09:47:31 +02:00
Elian Doran
f37fa3723b Merge pull request #526 from TriliumNext/feature/syntax_highlight
Basic syntax highlight support for code blocks
2024-11-02 01:46:02 +02:00
Elian Doran
b14065d442 server: Address self-review 2024-11-02 01:42:25 +02:00
Elian Doran
1554e25283 server: Add documentation for code_block_theme 2024-11-02 01:39:35 +02:00
Elian Doran
4e945583a1 server: Add some documentation 2024-11-02 00:55:45 +02:00
Elian Doran
92c588dc98 server: Implement color theme migration based on existing theme 2024-11-02 00:39:22 +02:00
Elian Doran
5c66e3fd04 server: Initialize code block theme for old databases as well 2024-11-02 00:20:27 +02:00
Elian Doran
e508313f21 electron: Fix deprecation warning 2024-11-01 23:42:32 +02:00
Elian Doran
df3f51d1f3 electron: Fix loading of highlight.js 2024-11-01 23:42:23 +02:00
Elian Doran
0a6815e448 Merge remote-tracking branch 'origin/develop' into feature/syntax_highlight 2024-11-01 23:20:12 +02:00
Elian Doran
293db6962e Merge branch 'develop' of ssh://github.com/TriliumNext/Notes into develop 2024-11-01 23:14:41 +02:00
Elian Doran
eb05c5b919 Merge pull request #534 from TriliumNext/AutomaticallyShowRecentNotes
Automatically trigger autocomplete on focus.
2024-11-01 20:37:39 +02:00
Elian Doran
bbaed45f6b client: Fix scrolling in empty tab search list after constraining height 2024-11-01 20:23:46 +02:00
Elian Doran
aa7d7b3afd client: Add borders to empty tab search list 2024-11-01 20:20:53 +02:00
Elian Doran
fc4797d04f Merge pull request #541 from TriliumNext/export_file_name
Crop fileName  and prevent cutting into the extension.
2024-11-01 19:44:31 +02:00
Elian Doran
c2baa4b752 server: Add comment to clarify use of regex 2024-11-01 19:43:39 +02:00
Elian Doran
faeefc75ba Merge pull request #542 from TriliumNext/close_tabs
close right tabs
2024-11-01 19:07:55 +02:00
Elian Doran
d0904c1051 client: Change translation for closing tabs to the right 2024-11-01 19:05:56 +02:00
Elian Doran
11a82e62f1 client: Change layout of tab context menu slightly 2024-11-01 19:03:06 +02:00
SiriusXT
7b24f7e332 close right tabs 2024-11-01 22:01:46 +08:00
SiriusXT
7f17f93767 Crop fileName and prevent cutting into the extension. 2024-11-01 21:43:09 +08:00
SiriusXT
cdd5a17fce Make note-detail-empty always display autocompletion. 2024-11-01 15:30:31 +08:00
SiriusXT
dbca50d9b0 Make note-detail-empty always display autocompletion. 2024-11-01 14:45:49 +08:00
Elian Doran
57a86c75d8 i18n: Fix single Romanian translation 2024-10-31 23:54:50 +02:00
Elian Doran
9e3c1b46cd client: Don't load syntax highlighter when not needed 2024-10-31 22:47:34 +02:00
Elian Doran
00209ec77a client: Apply syntax highlight to included notes 2024-10-31 22:18:00 +02:00
Elian Doran
dfa4f3cd84 client: Apply syntax highlight to note preview 2024-10-31 22:14:54 +02:00
Elian Doran
3af29a78dc client: Refactor syntax highlighting for read-only text into service 2024-10-31 22:11:59 +02:00
Elian Doran
4d783f1879 client: Fix color theme leak when deactivating highlighting 2024-10-31 21:45:06 +02:00
Elian Doran
c3e10b2b76 client: Remove syntax highlight in preview when disabled 2024-10-31 21:33:00 +02:00
Elian Doran
f57ab4b9f0 client: Fix word wrap preview being in reverse 2024-10-31 21:29:01 +02:00
Elian Doran
a690155d7e client: Improve group for no theme 2024-10-31 21:17:40 +02:00
Elian Doran
cc0b3db424 client: Translate dark/light color theme groups 2024-10-31 21:00:48 +02:00
Elian Doran
ae60f8c842 client: Group color themes by dark/light 2024-10-31 20:54:33 +02:00
Elian Doran
90dffdc6ed client: Enable preview for word wrap 2024-10-31 20:18:02 +02:00
Elian Doran
ac13291744 client,server: Allow disabling syntax highlight 2024-10-31 18:03:52 +02:00
Elian Doran
bbc038f254 Merge remote-tracking branch 'origin/develop' into feature/syntax_highlight 2024-10-31 17:48:49 +02:00
Elian Doran
f8df3a6933 client: Fix crash for some unhandled rejections 2024-10-31 17:48:33 +02:00
Elian Doran
b10e2d9ec4 Update README to add a few shields 2024-10-31 14:00:14 +02:00
SiriusXT
2387bbd17f Automatically trigger autocomplete on focus. 2024-10-30 22:30:40 +08:00
Adorian Doran
f13d88c3c0 Add a background color transition for the code sample 2024-10-29 18:46:55 +02:00
Adorian Doran
2459bbf341 Improve the layout of the "Word wrapping" checkbox 2024-10-29 18:39:14 +02:00
Adorian Doran
60426ea487 Fix word-wrapping 2024-10-29 12:57:15 +02:00
Adorian Doran
b112cb609f Tweak the padding of the language badges 2024-10-29 01:55:29 +02:00
Adorian Doran
b9ebc66122 Customize the scrollbar in code boxes for WebKit-based browsers 2024-10-29 01:30:08 +02:00
Adorian Doran
2f4ed92346 Prevent the language badge to be scrolled in code boxes 2024-10-29 01:07:24 +02:00
Adorian Doran
d3d001d8ea Tweak (again) the shadow of code blocks 2024-10-28 23:52:45 +02:00
Adorian Doran
70cee7dbf6 Tweak the shadow of code blocks 2024-10-28 23:44:40 +02:00
Adorian Doran
36fde2b03d Tweak the language badge of code blocks 2024-10-28 23:29:53 +02:00
Potjoe-97
88d8f57697 Merge pull request #3 from Potjoe-97/patch-2
Update translation.json
2024-10-28 16:03:33 +01:00
Potjoe-97
b7e254975f Update translation.json 2024-10-28 16:02:17 +01:00
Potjoe-97
97b2ba2da1 Update server.json 2024-10-28 16:01:04 +01:00
Adorian Doran
bda8173932 Improve the sample code 2024-10-28 16:08:46 +02:00
Adorian Doran
48f9f072b4 Format theme names 2024-10-28 16:07:52 +02:00
Elian Doran
9c55203ea0 client: Add credits 2024-10-28 00:05:43 +02:00
Elian Doran
dbb5e0e971 server: Add friendlier names for color themes 2024-10-27 23:46:03 +02:00
Elian Doran
b8eb09b46b server: Refactor code block theme search into own service 2024-10-27 23:12:55 +02:00
Elian Doran
5682b2d819 client: Translate word wrapping 2024-10-27 22:57:34 +02:00
Elian Doran
5109c07e9c client: Toggle word wrapping for code blocks 2024-10-27 22:51:24 +02:00
Elian Doran
b8569ea243 client, server: Create option to control word wrapping for code blocks 2024-10-27 21:51:56 +02:00
Elian Doran
52bc28def7 client: Rename section to CodeBlockOptions 2024-10-27 21:42:40 +02:00
Elian Doran
e65d4cdfbf client: Rename endpoint to codeblock-themes 2024-10-27 21:40:22 +02:00
Elian Doran
96b9042559 client: Rename option to codeBlockTheme 2024-10-27 21:39:50 +02:00
Elian Doran
e68d070320 client: Set up localization for syntax highlighting section 2024-10-27 21:27:35 +02:00
Elian Doran
ef5f2c680b client: Rephrase theme section 2024-10-27 21:19:27 +02:00
Elian Doran
6717b1b4ae client: Rephrase section 2024-10-27 21:15:51 +02:00
Elian Doran
41e3163595 client: Fix flicker of font selection 2024-10-27 21:03:13 +02:00
Elian Doran
514653fb50 client: Fix flicker of preview 2024-10-27 20:22:23 +02:00
Elian Doran
e843f1adc1 client: Fix background of preview 2024-10-27 20:19:53 +02:00
Elian Doran
83f5b47c99 client: Set up simple preview for syntax highlight 2024-10-27 20:18:44 +02:00
Elian Doran
2fdff29067 client: Apply syntax highlight in real-time 2024-10-27 20:08:12 +02:00
Elian Doran
0d270cbeb6 client: Use 3px shadow for dark theme 2024-10-27 20:01:08 +02:00
Elian Doran
f947a039b9 client: Apply background to read-only code blocks as well 2024-10-27 19:58:00 +02:00
Elian Doran
d2235a185b client: Improve style for code blocks 2024-10-27 19:54:05 +02:00
Elian Doran
87bc142552 client: Fix foreground color 2024-10-27 19:43:48 +02:00
Elian Doran
1a25f60264 client: Fix background color 2024-10-27 19:41:28 +02:00
Elian Doran
fe4dbae079 client: Apply highlighting theme on refresh 2024-10-27 17:41:37 +02:00
Elian Doran
e1ae014b74 server: Remove dashes from syntax theme name 2024-10-27 17:25:05 +02:00
Elian Doran
7952a5a81e client: Fix order of options 2024-10-27 12:54:40 +02:00
Elian Doran
60b6f7df89 client: Allow switching theme 2024-10-27 12:54:06 +02:00
Elian Doran
7354fb5b4a client,server: List syntax highlighting themes 2024-10-27 12:41:53 +02:00
Elian Doran
1fb0b74f76 client: Use same mechanism for read-only notes 2024-10-27 12:15:32 +02:00
Elian Doran
9e3b915612 client: Use translation for auto-detect 2024-10-27 11:47:36 +02:00
Elian Doran
7505db220e client: Implement auto syntax highlighting 2024-10-27 11:46:19 +02:00
Elian Doran
a3932376f3 client: Add Javadoc for newly introduced methods 2024-10-27 11:32:54 +02:00
Elian Doran
3a609d54ab client: Fix highlighting for JavaScript 2024-10-27 11:21:08 +02:00
Elian Doran
c4bd4eb440 client: Respect user language selection for editor 2024-10-27 11:18:36 +02:00
Elian Doran
e931df721d client: Fix duplication when requesting scripts 2024-10-27 10:48:50 +02:00
Elian Doran
1e9324c303 client: Support custom language types for highlight 2024-10-27 10:39:31 +02:00
Elian Doran
6c4513fb2e client: Enable syntax highlight for read-only notes 2024-10-27 08:52:34 +02:00
Elian Doran
c7e1362105 Merge branch 'develop' into feature/syntax_highlight 2024-10-26 23:39:49 +03:00
Elian Doran
acf37f9327 client: Fix error when duplicating note 2024-10-26 23:39:38 +03:00
Elian Doran
f80cf0aa02 Add limit to blocks highlighting 2024-10-26 23:39:18 +03:00
Elian Doran
6078620bf1 Carry over code block highlighting 2024-10-26 23:27:23 +03:00
Elian Doran
579b3f4ca0 Carry over highlighter initialization 2024-10-26 23:21:51 +03:00
Elian Doran
bf28005f46 Create dedicated file for syntax highlight 2024-10-26 23:16:24 +03:00
Elian Doran
c81b847b61 Set up highlight.js 2024-10-26 22:57:07 +03:00
Elian Doran
88cd2ac25c build: Fix duplication 2024-10-26 01:00:44 +03:00
Elian Doran
e3e6f56a88 build: Add icon.png for Linux builds (fixes #507) 2024-10-26 00:58:02 +03:00
Elian Doran
0768a2a0a3 build: Add StartupWMClass to deb build 2024-10-26 00:42:44 +03:00
Elian Doran
84d216da54 i18n: Translate missing keys for Romanian 2024-10-25 21:06:03 +03:00
Elian Doran
391f518c01 i18n: Translate search note prefix 2024-10-25 21:04:13 +03:00
Elian Doran
2324c9a13b client: Fix HTML in some toasts 2024-10-25 20:51:50 +03:00
Elian Doran
6799c44e22 client: Fix redundant toast message 2024-10-25 20:50:13 +03:00
Elian Doran
f0052d56b7 Merge pull request #520 from hasecilu/i18n/more_Spanish_translation
More Spanish translation
2024-10-25 20:49:48 +03:00
Elian Doran
53822fd47f client: Remove redundant global 2024-10-25 20:44:08 +03:00
hasecilu
b02c4b54e5 i18n: Fix source strings 2024-10-25 11:32:43 -06:00
hasecilu
27f07ee604 i18n: Update Spanish translations 2024-10-25 11:32:42 -06:00
Elian Doran
03a23d15f9 client: Fix double errors if not returning a widget 2024-10-25 20:22:29 +03:00
Elian Doran
70d55097ee client: Fix crash if note tree fails to find a child note 2024-10-25 20:15:12 +03:00
Elian Doran
560467bdba client: Log uncaught promise errors 2024-10-25 19:57:40 +03:00
Elian Doran
cb4fe4481f client: Strengthen widget rendering errors detection 2024-10-25 19:57:31 +03:00
Elian Doran
eee088316d client: Improve logging for some bundle errors 2024-10-24 20:55:36 +03:00
Elian Doran
81ca0a3776 client: Improve logging for basic sync crash 2024-10-24 18:47:16 +03:00
Elian Doran
48b0af1bba client: Stop crash if right widget crashes during render 2024-10-24 18:14:17 +03:00
Elian Doran
43ef452d44 client: Fix error when running script due to translations 2024-10-23 20:33:55 +03:00
Elian Doran
70ebf1a08f client: Fix content size for code editor 2024-10-23 20:27:36 +03:00
Elian Doran
9f6f0f5d60 server: Update locale when switching language from settings 2024-10-23 19:56:06 +03:00
Elian Doran
af67362ad6 server: Translate weekday and month names 2024-10-23 19:34:09 +03:00
Elian Doran
77550f3087 server: Fix regression due to express types 2024-10-22 20:09:42 +03:00
Elian Doran
5813282248 Prepare for 0.90.9-beta 2024-10-22 20:05:43 +03:00
Elian Doran
e77b223508 client: Update force-graph 1.43.5 -> 1.45.0 2024-10-22 20:03:05 +03:00
Elian Doran
7aafdce629 server: Update jasmine, debounce 2024-10-22 19:56:15 +03:00
Elian Doran
a2f0cb394a server: Update marked, sanitize-html to latest 2024-10-22 19:53:22 +03:00
Elian Doran
e8d1518965 build: Update TypeScript 2024-10-22 19:30:34 +03:00
Elian Doran
8b333b32af mind-elixir: 4.2.2 -> 4.2.3 2024-10-22 19:27:53 +03:00
Elian Doran
cda369ed4d server: Update express, express-rate-limit, express-session to latest 2024-10-22 19:25:47 +03:00
Elian Doran
b5bc93d794 i18next: 23.16.1 -> 23.16.2 2024-10-22 19:24:16 +03:00
Elian Doran
b96047e962 vanilla-js-wheel-zoom: 9.0.2 -> 9.0.4 2024-10-22 19:22:14 +03:00
Elian Doran
31ccbb0d23 mind-elixir: 4.2.0 -> 4.2.2 2024-10-22 19:17:52 +03:00
Elian Doran
cb9403535d i18next: 23.16.0 -> 23.16.1 2024-10-22 19:17:52 +03:00
Elian Doran
b5ee90a1d2 i18n: Translate delete/restore branch 2024-10-22 19:17:52 +03:00
Elian Doran
9ed7eb977e i18n: Translate launcher context menu 2024-10-22 19:17:52 +03:00
Elian Doran
8cc487da7c i18n: Translate confirmation popups 2024-10-22 19:17:52 +03:00
Elian Doran
ae593ea363 i18n: Translate protected session 2024-10-22 19:17:52 +03:00
Elian Doran
26e4decaec i18n: Translate toast errors 2024-10-22 19:17:52 +03:00
Elian Doran
28f6712a4f i18n: Translate toast messages 2024-10-22 19:17:52 +03:00
Elian Doran
93efce4023 server: Minimize not found logs (closes #505) 2024-10-22 19:17:52 +03:00
Elian Doran
689b3a3079 i18n: Fix capitalization of no anonymization 2024-10-22 19:17:51 +03:00
Elian Doran
4ad725842e server: Trim .htm when importing zip (closes #500) 2024-10-20 00:17:51 +03:00
Elian Doran
d4956ad3a2 client: Refactor and add documentation 2024-10-19 23:19:11 +03:00
Elian Doran
c7b7c68a05 client: Reduce code duplication for CodeMirror 2024-10-19 23:12:33 +03:00
Elian Doran
cab1d7d353 client: Set up syntax highlight in read-only code (closes #504) 2024-10-19 22:56:45 +03:00
Elian Doran
7957c6d34e client: Fix promoted attribute style regressions (closes #503) 2024-10-19 22:40:27 +03:00
Elian Doran
c18c972a57 i18n: Use variable interpolation for delete relation warning 2024-10-19 11:13:54 +03:00
Elian Doran
29a700f731 i18n: Fix duplication in delete relations count 2024-10-19 10:55:48 +03:00
Elian Doran
815eab26f6 i18n: Fix duplication in delete note count 2024-10-19 10:50:56 +03:00
Elian Doran
103da23b5a i18n: Fix strange title in Romanian 2024-10-19 10:46:28 +03:00
Elian Doran
ba1d82bc0a i18n: Fix capitalization of checkbox 2024-10-19 10:45:45 +03:00
Elian Doran
21f8a29761 Bump to v0.90.8 2024-10-19 09:44:34 +03:00
Elian Doran
f38870b27d i18next: 23.15.2 -> 23.16.0 2024-10-17 23:15:46 +03:00
Elian Doran
56a6d27240 client: mind-elixir: 4.1.5 -> 4.2.0 2024-10-17 23:12:43 +03:00
Elian Doran
38e5ef2c7d i18n: Translate some more Romanian messages 2024-10-17 22:50:20 +03:00
Elian Doran
e29d600517 Merge pull request #489 from TriliumNext/add-stable-tag-to-containers
Introduce `stable` tag on containers
2024-10-17 22:43:04 +03:00
Elian Doran
42605fbbad Merge pull request #495 from Potjoe-97/develop
i18n : Add fr translation (2/2)
2024-10-17 22:42:01 +03:00
Elian Doran
11ca427a28 Merge pull request #496 from hasecilu/i18n/Spanish_more
Continue Spanish translation
2024-10-17 22:40:34 +03:00
hasecilu
28d8088763 i18n: Create script to create PO files for translation
Script for translators
2024-10-17 13:34:40 -06:00
hasecilu
664c4789c0 i18n: Update Spanish translation 2024-10-17 12:30:36 -06:00
Potjoe-97
7c5667b457 Minor fixes 2024-10-17 15:54:42 +02:00
Potjoe-97
0afd22e196 Edited French option to display native spelling 2024-10-17 15:12:51 +02:00
Potjoe-97
b3abee71b7 Major overhaul fr translation
Corrections & Consistency
2024-10-17 13:33:28 +02:00
Elian Doran
9bd5596b2a i18n: Set up French 2024-10-16 20:29:42 +03:00
Elian Doran
e0e3c15e6e Merge pull request #493 from Potjoe-97/develop
Add french translation
2024-10-16 20:28:38 +03:00
Potjoe-97
31396264fa Corrections i18n : {} attribute detail 2024-10-16 14:52:05 +02:00
Potjoe-97
b1aada22b5 Corrections in /src/public/translations/fr 2024-10-16 11:32:34 +02:00
Elian Doran
d7eaf72a6d Merge pull request #491 from TriliumNext/feature/i18n-part6
Feature/i18n part6
2024-10-16 12:13:28 +03:00
Potjoe-97
59df442676 Corrections /src/public/translations/fr
Consistency in translations/server.json
2024-10-15 20:29:34 +02:00
Potjoe-97
9770db7f3c Consistency : "étiquette" now translated into "label" 2024-10-15 18:52:34 +02:00
Potjoe-97
8c36cea71b src/public/translations/fr : First draft 2024-10-15 18:16:39 +02:00
Potjoe-97
b03f40f1f9 Edit french translation 2024-10-15 15:48:11 +02:00
Potjoe-97
00dba7bef4 Add french translation 2024-10-15 15:20:04 +02:00
Nriver
4186f3d136 add translation for app_context.js 2024-10-15 15:46:34 +08:00
Nriver
529502524d add missing context menu translation 2024-10-15 15:24:01 +08:00
Nriver
7c518e9512 add translation for watched_file_update_status.js 2024-10-15 15:19:09 +08:00
Nriver
5e2d1bc124 add translation for toc.js 2024-10-15 15:12:09 +08:00
Nriver
7dfe6f276e update Chinese translation and synchronize with English 2024-10-15 15:05:48 +08:00
Nriver
858db68d66 add translation for tab_row.js 2024-10-15 14:51:26 +08:00
Jon Fuller
b72f46f108 Don't add stable if pushed tag has - 2024-10-14 14:06:55 -07:00
Elian Doran
83dbe0539e client: Highlight content links on hover 2024-10-14 22:47:16 +03:00
Jon Fuller
87e0cf55f1 Introduce stable tag on containers
Closes #488
2024-10-14 12:18:29 -07:00
Elian Doran
8315d5c778 Update package-lock 2024-10-14 22:13:04 +03:00
Elian Doran
61bd7dca18 client: Fix underlines for all links (closes #485) 2024-10-14 22:12:48 +03:00
Elian Doran
4faf27364f Merge branch 'develop' of ssh://github.com/TriliumNext/Notes into develop 2024-10-13 15:45:48 +03:00
Elian Doran
52a6d0b48a Bump version to 0.90.7-beta 2024-10-13 15:45:40 +03:00
Elian Doran
cddc9a7b6a i18n: Mark mind map as beta 2024-10-13 14:43:50 +03:00
Elian Doran
75d019863f server: Disable debug mode for i18n 2024-10-13 14:40:07 +03:00
Elian Doran
849a6a3aef electron: Solve server translation loading on macOS 2024-10-13 14:24:46 +03:00
Elian Doran
e7378306a2 Merge branch 'develop' of ssh://github.com/TriliumNext/Notes into develop 2024-10-13 13:52:39 +03:00
Elian Doran
1277dfc5d5 electron: Add back server translations 2024-10-13 12:31:37 +03:00
Elian Doran
ae680847dc vscode: Pin nightly GitHub Action 2024-10-13 12:28:05 +03:00
Elian Doran
a5fd57308a server: Fix translations in standalone build 2024-10-13 12:09:36 +03:00
Elian Doran
fa769df7b0 client: Fix table background on dark mode 2024-10-13 11:20:26 +03:00
Elian Doran
8136a2972e Merge pull request #465 from quantum5/pwa-icon-fix
server: fix blurry PWA icon
2024-10-13 10:51:00 +03:00
Elian Doran
8c8c3974f3 Merge pull request #480 from hasecilu/i18n/Spanish_server
i18n: Add more Spanish translations
2024-10-13 10:07:39 +03:00
hasecilu
e81bfa3693 i18n: Add more Spanish translations 2024-10-12 17:30:59 -06:00
Elian Doran
a857f4816f codemirror: 5.65.15 -> 5.65.18 2024-10-12 20:48:39 +03:00
Elian Doran
ade34f9745 client: Use CodeMirror from package.json 2024-10-12 20:41:50 +03:00
Elian Doran
fd66cb930d electron: Update electron-forge to latest 2024-10-12 20:22:18 +03:00
Elian Doran
182d9afac1 tools: Update development tools 2024-10-12 20:18:44 +03:00
Elian Doran
4e6ef0be95 i18next: 23.15.1 -> 23.15.2 + http-backend 2024-10-12 20:16:23 +03:00
Elian Doran
88961ea93f mermaid: 11.2.0 -> 11.3.0 2024-10-12 20:14:46 +03:00
Elian Doran
a3f2946a17 mind-elixir: 4.1.1 -> 4.1.5 2024-10-12 20:12:45 +03:00
Elian Doran
8ae5f9ea9b electron: Fix translations in packaged builds 2024-10-12 19:40:54 +03:00
Elian Doran
9ec2508f09 i18n: Add more Romanian translations 2024-10-12 18:53:24 +03:00
Elian Doran
bae63b08a2 server, electron: Fix crash if locale is undefined 2024-10-12 18:24:10 +03:00
Elian Doran
729a188528 docker: Fix server translation initialization (closes #439) 2024-10-12 10:53:30 +03:00
Elian Doran
2c5a5acffa server: Improve type for keyboard shortcut scope 2024-10-12 10:31:12 +03:00
Elian Doran
e45c5f429d server: Fix crash when starting server in Docker due to translations 2024-10-12 10:29:50 +03:00
Elian Doran
5c44ac5ad8 ci: Fix permissions for nightly 2024-10-11 22:15:50 +03:00
Elian Doran
b44c2f5ebf ci: Configure nightly deployment 2024-10-11 22:07:22 +03:00
Elian Doran
6e64cab2d4 Merge pull request #327 from TriliumNext/JYC333-patch-1
Create nightly release action
2024-10-11 21:56:04 +03:00
Elian Doran
9c8cf0bc09 Merge pull request #452 from TriliumNext/fix-image-copy
Use the electron Clipboard module when using "Copy image to clipboard"
2024-10-11 19:48:18 +03:00
Elian Doran
bafc556b00 server: Fix build errors 2024-10-11 19:45:54 +03:00
Elian Doran
0ec3232c81 Merge pull request #451 from jaimeferj/bugfix/parseDatesOnSort
Fixes issue #441: Dates were not parsed on sorting
2024-10-10 23:31:47 +03:00
Elian Doran
469c1ceb07 Merge pull request #458 from TriliumNext/zip-import-image-fix
Resolve MIME type not being used when creating new note correctly, upon zip import
2024-10-10 23:26:48 +03:00
Elian Doran
5e80f120c9 server: Refactor variable usage 2024-10-10 20:21:06 +03:00
Elian Doran
8faa26b663 Merge pull request #391 from SiriusXT/revisions_number_limit
Revisions number limit
2024-10-10 20:13:24 +03:00
Elian Doran
201e2fcfdb Merge pull request #472 from dousha/bugfix-math-preview
Override the z-index of Bootstrap tooltips
2024-10-10 20:04:25 +03:00
Elian Doran
5678487a16 Merge pull request #473 from TriliumNext/feature/i18n-part5
Feature/i18n part5
2024-10-10 19:13:31 +03:00
Nriver
6646e8c311 update Chinese translation and synchronize with English 2024-10-10 15:42:10 +08:00
dousha
96fc4d3280 Override the z-index of Bootstrap tooltips, making it conform to CKEditor's z-index ordering.
This change overrides the z-index of Bootstrap tooltips (calculates to 1080 in the current version) to the value assigned to CKEditor Panel Balloons (calculates to 1000 in the current version) minus 1. It also removes the original override to the CKEditor Panel Balloons which forces the autocomplete box to appear above tooltips, which is no longer needed and would interfere with math preview boxes.

This change fixes the regression to the [original issue][1] and the [math preview issue][2]. One notable effect is that tooltips will not cover menus when overlapped with one, including menus opened from the top-right corner. Further discussion and more extensive testing are encouraged.

[1]: https://github.com/zadam/trilium/issues/1272
[2]: https://github.com/TriliumNext/Notes/issues/297
2024-10-10 13:19:08 +08:00
Elian Doran
0a69189b9b i18n: Reach 100% for Romanian 2024-10-09 22:04:00 +03:00
Elian Doran
cb3ea3fb75 Merge pull request #449 from hasecilu/i18n/Spanish_pt_3_4
i18n: Continue Spanish translation, parts 3 and 4
2024-10-09 21:58:38 +03:00
Elian Doran
787a2d1957 Merge pull request #450 from hasecilu/docs/fix_links
docs: Use relative link to other README files
2024-10-09 21:56:38 +03:00
Elian Doran
89e1275dda i18n: Tweak a single message 2024-10-09 20:06:20 +03:00
Elian Doran
ddc927d617 server: Bump sync version (closes #467) 2024-10-09 20:06:13 +03:00
Quantum
d46963e496 server: fix blurry PWA icon 2024-10-08 20:32:17 -04:00
perf3ct
c37a51c6d0 use type assertions to make TS happy? 2024-10-03 22:48:47 -07:00
perf3ct
91095e8d4e detectFileTypeAndMime also returns the type, fixes #456 2024-10-03 22:24:43 -07:00
MeIchthys
d4fa7e3fd2 Add link to docs repo under contributing section 2024-10-03 23:23:43 -04:00
perf3ct
9204f0735c use fewer const 2024-09-30 19:43:44 +00:00
perf3ct
cfc32a14e0 Use the electron Clipboard module when using "Copy image to clipboard" 2024-09-28 17:41:59 -07:00
Jaime Fernández
270aa52591 Fixes issue #441: Dates were not parsed on sorting 2024-09-28 02:33:36 +02:00
hasecilu
99a5913391 docs: Use relative link to other README files
`master` branch was hardcoded despite working on `develop` branch
2024-09-27 14:38:45 -06:00
hasecilu
c3d6165aff i18n: Continue Spanish translation, parts 3 and 4
Some fixes were done thanks by using PO file with Poedit. It enhances a
lot the workflow for translators

---

Step 1: Create PO files from English and localized JSON files as source
$ json2po -t en es po-es

Step 2: Make the translation

Step 3: Recover translation from PO files to JSON file. Remember to set
the indentation to 2 spaces
$ po2json -t en po-es es

Step 4: Update localized JSON file with missing tags from English JSON
$ jq -s '.[0] * .[1]' en/translation.json \
    es/translation.json > merged.json
$ mv merged.json es/translation.json

Step 5: Remove PO files
$ rm -r po-es
2024-09-25 17:54:46 -06:00
Elian Doran
9220616bb5 Merge pull request #448 from Nriver/feature/i18n-part4
Feature/i18n part4
2024-09-25 18:54:12 +03:00
Nriver
eb9b4aee21 add translation for 6 widgets 2024-09-24 09:57:16 +08:00
Nriver
d9a7671830 update Chinese translation and synchronize with English 2024-09-24 09:28:46 +08:00
SiriusXT
353c410f0e Merge branch 'develop' into revisions_number_limit 2024-09-17 19:58:16 +08:00
Elian Doran
7ca4cddc58 Merge pull request #431 from TriliumNext/perfectra1n-patch-2
Print Docker container log, regardless of success or failure of healthcheck
2024-09-15 23:29:06 +03:00
Elian Doran
12543f762b Merge pull request #432 from TriliumNext/hide_float_button
Floating buttons can be displayed again after being closed
2024-09-15 23:21:44 +03:00
SiriusXT
ec7fabcb58 Floating buttons can be displayed again after being closed 2024-09-15 22:22:38 +08:00
Jin
5d08f2bc5a fix revision list ESC behaviour 2024-09-15 12:41:45 +02:00
SiriusXT
a557b82c83 Fixed the bug that notes in different formats cannot be restoredRevisioned 2024-09-15 12:48:01 +08:00
SiriusXT
62a884cb5a fix potential bugs that may exist in getRevisions 2024-09-15 10:51:55 +08:00
perfectra1n
88875a3375 Print Docker container log, if success or failure healthcheck 2024-09-14 12:50:25 -07:00
SiriusXT
cd547ebdaf Fix svg not previewing in revisions 2024-09-14 21:09:52 +08:00
Elian Doran
e511d6aecf client: Fix strange margins when ribbon has hidden components 2024-09-14 14:47:48 +03:00
Elian Doran
9b6148dec3 client: Translate attribute editor placeholder 2024-09-14 14:44:32 +03:00
Elian Doran
29a1aad45a Merge pull request #430 from TriliumNext/fix
Fix global menu advanced command can't click
2024-09-14 14:40:03 +03:00
SiriusXT
a9fce727d4 Fix svg not previewing in revisions 2024-09-14 19:37:25 +08:00
Elian Doran
9e85d2cdbd server: Fix missing translations in keyboard actions 2024-09-14 14:34:24 +03:00
Jin
c4f430276a fix global menu advanced command can't click 2024-09-14 11:02:23 +02:00
SiriusXT
fdf57b6cfa revisions_number_limit 2024-09-14 16:00:18 +08:00
SiriusXT
0a9e76abb7 Merge branch 'develop' into revisions_number_limit 2024-09-14 14:58:47 +08:00
SiriusXT
3e085e5cae Show revision information 2024-09-14 14:32:43 +08:00
Elian Doran
73cd54e75c client: Fix shortcuts section crashing when no translations 2024-09-13 22:58:39 +03:00
Elian Doran
28b8e59b4d electron: Translate title bar button 2024-09-13 22:56:30 +03:00
Elian Doran
51b6725b91 server,electron: Downgrade incompatible libraries for now 2024-09-13 22:54:06 +03:00
Elian Doran
93ca30fda0 build: Fix type not in dev dependencies 2024-09-13 22:46:15 +03:00
Elian Doran
8c222368de i18n: Translate some more messages in Romanian 2024-09-13 22:26:56 +03:00
Elian Doran
a3aa6b0628 client: Translate note tree 2024-09-13 22:22:16 +03:00
Elian Doran
d0061794dd client: Translate note icon 2024-09-13 22:17:22 +03:00
Elian Doran
17c8708b54 client: Translate quick search 2024-09-13 22:08:52 +03:00
Elian Doran
da0ddbb80d i18n: Translate the rest of Romanian messages 2024-09-13 21:59:19 +03:00
Elian Doran
138b4b1a95 i18n: Remove unused key 2024-09-13 21:57:23 +03:00
Elian Doran
1299072ea9 server: Update dependencies 2024-09-13 21:51:46 +03:00
Elian Doran
bb5893f989 electron: Update squirrel startup 2024-09-13 21:49:36 +03:00
Elian Doran
7b764fd7d8 client: Update mermaid, mind elixir 2024-09-13 21:48:38 +03:00
Elian Doran
ac2fc49a48 build: Update tooling dependencies 2024-09-13 21:44:57 +03:00
Elian Doran
c0abf6e2c5 Use fixed versions for all dependencies 2024-09-13 21:43:58 +03:00
Elian Doran
85ff0bac55 electron: Set up StartupWMClass to avoid missing icon 2024-09-13 21:21:29 +03:00
Elian Doran
3671b83a9c server: Disable debug logs for i18n 2024-09-13 21:16:20 +03:00
Elian Doran
6970bf4fc1 Merge pull request #428 from TriliumNext/icon_improvement
Fix strange underline for zoom button
2024-09-13 17:50:44 +03:00
SiriusXT
e5cce1b1bc Fix strange underline for zoom button 2024-09-13 22:24:19 +08:00
Elian Doran
faa8eca810 i18n: Translate a few Romanian messages 2024-09-13 14:57:05 +03:00
SiriusXT
84bf0cbae5 Merge branch 'develop' into revisions_number_limit 2024-09-13 19:46:07 +08:00
SiriusXT
a77264208e Merge branch 'develop' into icon_improvement 2024-09-13 19:15:33 +08:00
SiriusXT
ee06db8c8f Fix strange underline for zoom button 2024-09-13 19:10:44 +08:00
Elian Doran
e330b43750 Merge pull request #426 from TriliumNext/feature/i18n-part3
Feature/i18n part3
2024-09-13 10:28:26 +03:00
Nriver
f5b21498bf add translation for mermaid.js 2024-09-13 11:24:42 +08:00
Nriver
83388ecf1c add translation for 2 widgets 2024-09-13 11:10:59 +08:00
Nriver
87de631af4 sync Chinese translation with English 2024-09-13 10:53:50 +08:00
Nriver
e4271d3945 update missing translation 2024-09-13 10:29:44 +08:00
Elian Doran
65b8a2f97c Merge pull request #425 from TriliumNext/dropped-backslash
Picking up the backslash that I dropped
2024-09-13 00:04:08 +03:00
perfectra1n
0656154c35 Picking up the backslash that I dropped
I wiped it off and cheered it up, so it should be ready to go now.
2024-09-12 13:45:13 -07:00
Elian Doran
b3f682144b server: Update better-sqlite3 11.1.2 → 11.3.0 2024-09-12 23:08:22 +03:00
Elian Doran
d3c3e157c7 server: Update express 4.19.2 -> 4.21.0 2024-09-12 22:49:21 +03:00
Elian Doran
2534402157 client,server: Update i18next 23.14.0 -> 23.15.1 2024-09-12 22:47:46 +03:00
Elian Doran
ec80ba5caf build: Update jasmine from 5.1.0 to 5.3.0 2024-09-12 22:43:21 +03:00
Elian Doran
26621b6336 build: Fix test-jasmine on Windows 2024-09-12 22:42:22 +03:00
Elian Doran
d961e3cdf1 build: tsx 4.19.0 -> 4.19.1 2024-09-12 22:39:38 +03:00
Elian Doran
7ef399912b Merge branch 'develop' of https://github.com/TriliumNext/Notes into develop 2024-09-12 22:23:51 +03:00
Elian Doran
9fc789676f electron: Fix window drag not working 2024-09-12 22:23:49 +03:00
Elian Doran
f01dc3d102 Merge pull request #424 from TriliumNext/stop-sha-images
Stop tagging images with `sha*`
2024-09-12 22:23:36 +03:00
perfectra1n
501bf624cc Stop tagging images with sha* 2024-09-12 12:20:54 -07:00
Elian Doran
f8605688ab Merge pull request #423 from TriliumNext/perfectra1n-patch-1
Add explicit permissions for GITHUB_TOKEN for package access
2024-09-12 20:54:15 +03:00
Elian Doran
590a9bef2d client: Fix calendar double open on existing note 2024-09-12 20:53:31 +03:00
perfectra1n
419756d19e Add explicit permissions for GITHUB_TOKEN for package access 2024-09-12 10:42:47 -07:00
Elian Doran
93ce81b355 Merge pull request #416 from SiriusXT/highlightslits
Toc and Highlightslist improvement
2024-09-12 20:24:30 +03:00
Elian Doran
8bfa4461b0 Merge pull request #381 from TriliumNext/bootstrap
Upgrade bootstrap from v4 to v5
2024-09-12 20:09:19 +03:00
Elian Doran
648feb82f8 client: Fix close button on dark theme 2024-09-12 20:06:40 +03:00
Elian Doran
74645e12fc Merge remote-tracking branch 'origin/develop' into bootstrap 2024-09-12 20:06:31 +03:00
Elian Doran
71de1a0b55 Merge pull request #421 from TriliumNext/perfectra1n-patch-1
Force `github.repository_owner` to lowercase
2024-09-12 19:59:23 +03:00
perfectra1n
694f3cb174 Force github.repository_owner to lowercase
Ahhhh, the joys of Docker....
2024-09-12 09:54:00 -07:00
Elian Doran
a2bc5073d0 client: Fix note hint not working in calendar 2024-09-12 19:37:15 +03:00
Elian Doran
3134ef7c03 Merge pull request #402 from perfectra1n/develop
Use matrix strategy for CI/CD
2024-09-12 19:24:56 +03:00
Elian Doran
8e665e27e7 client: Fix calendar opening notes twice (closes #417) 2024-09-12 19:22:58 +03:00
Elian Doran
e6fbf62cf9 Merge pull request #414 from SiriusXT/i18n_falls_back_en
i18n Fallback to English for null characters
2024-09-12 19:03:50 +03:00
Jin
ce8b5e33da fix regressions 2024-09-12 13:55:07 +02:00
SiriusXT
b63b603d64 complete toc/highlightslist improvements 2024-09-12 19:34:23 +08:00
SiriusXT
31aa6feb0c Make toc/hightlightslist reopen after it's closed 2024-09-12 19:22:41 +08:00
SiriusXT
98c9e25124 Merge branch 'develop' into highlightslits 2024-09-12 09:37:01 +08:00
SiriusXT
bb97e1a661 Let toc and highlights_list display formulas 2024-09-12 09:36:08 +08:00
Elian Doran
a4341a5cac Merge pull request #392 from SiriusXT/Icon_improvements
Icon improvements
2024-09-11 23:56:10 +03:00
Elian Doran
fe844d4f8c client: Fix math not loading (closes #297) 2024-09-11 23:50:17 +03:00
SiriusXT
447cf60afb i18n Fallback to English for null characters 2024-09-11 15:17:21 +08:00
Elian Doran
7a11f9aaff Merge pull request #389 from SiriusXT/open_in_file_manager_of_data
Open in file manager of Trilium's data directory
2024-09-10 21:04:56 +03:00
Jin
527591f651 fix package-lock conflict 2024-09-10 00:48:52 +02:00
Jin
fca6f99870 fix checkbox problem 2024-09-10 00:45:00 +02:00
Jin
8c97f0bec7 fix some styles, add some cn translations 2024-09-10 00:45:00 +02:00
Jin
58ee801e57 other fixes 2024-09-10 00:45:00 +02:00
Jin
c1ce578018 fix all dialogs 2024-09-10 00:45:00 +02:00
Jin
464d8417f5 fix all dropdowns and tooltips, fix some translations 2024-09-10 00:45:00 +02:00
Jin
44bd008829 fix note revisions layout 2024-09-10 00:45:00 +02:00
Jin
f3b7261748 fix all launcher bar components 2024-09-10 00:45:00 +02:00
Jin
5d579fee68 Upgrade bootstrap from v4 to v5, fix global menu 2024-09-10 00:44:54 +02:00
Elian Doran
d7ab99013c client: Fix zoom interfering with mind map (closes #387) 2024-09-09 23:59:25 +03:00
Elian Doran
a3a5339048 client: Fix relative path for loading languages 2024-09-09 20:42:47 +03:00
Elian Doran
d95a23de28 Merge pull request #392 from SiriusXT/Icon_improvements
Icon improvements
2024-09-09 20:30:35 +03:00
SiriusXT
744d953822 icon improvement 2024-09-09 20:15:35 +08:00
SiriusXT
f8e5717b80 Merge branch 'develop' into revisions_number_limit 2024-09-09 14:52:31 +08:00
SiriusXT
3ad2d1a309 Merge branch 'develop' into open_in_file_manager_of_data 2024-09-09 14:50:01 +08:00
SiriusXT
7186222393 Merge branch 'develop' into Icon_improvements 2024-09-09 14:30:22 +08:00
SiriusXT
d66c07717c Merge branch 'develop' into Icon_improvements 2024-09-09 14:08:27 +08:00
Elian Doran
132dd7514a client: Update axios 1.7.2 to 1.7.7 2024-09-09 00:22:49 +03:00
Elian Doran
aca9d0f1ef server: Update ini 4.1.3 -> 5.0.0 2024-09-09 00:20:11 +03:00
Elian Doran
eabceae6f1 server: Update Marked 13.0.2 → 14.1.2 2024-09-09 00:18:15 +03:00
Elian Doran
3a5b05e5c6 client: Bump eslint to 9.10.0 2024-09-09 00:14:29 +03:00
Elian Doran
11e5f6a1c1 client: Update dayjs 1.11.12 -> 1.11.13 2024-09-09 00:10:52 +03:00
Elian Doran
bec9acdc3f client: Update jsdom 24.1.0 -> 25.0.0 2024-09-09 00:07:53 +03:00
Elian Doran
37079b7388 client: Update Mermaid to 11.1.1 2024-09-09 00:03:02 +03:00
Elian Doran
22b7bf826b electron: Bump electron-dl 2024-09-08 23:59:55 +03:00
Elian Doran
7cc71a4fdb electron: Bump electron-debug 2024-09-08 23:51:01 +03:00
Elian Doran
fb28b9d36d client: Improve max content widget fit on other languages 2024-09-08 22:20:38 +03:00
Elian Doran
720087f082 client: Translate open help page 2024-09-08 22:18:54 +03:00
Elian Doran
3bdb87d3aa client: Translate template switch 2024-09-08 22:14:51 +03:00
Elian Doran
e4066ba164 client: Translate shared switch 2024-09-08 22:11:52 +03:00
Elian Doran
a0fdaabb1f client: Translate protected switch 2024-09-08 22:04:22 +03:00
Elian Doran
38ac4318b9 client: Translate rest of note types 2024-09-08 21:54:30 +03:00
Elian Doran
e382a32ebd client: Translate note types 2024-09-08 21:48:24 +03:00
Elian Doran
8074245758 client: Translate shared info 2024-09-08 21:19:13 +03:00
Elian Doran
f26a04216a server: Translate share page 2024-09-08 20:36:25 +03:00
Elian Doran
e565d29490 server: Translate share 404 2024-09-08 20:25:38 +03:00
Elian Doran
947c50353d server: Translate here link in setup 2024-09-08 20:16:57 +03:00
Elian Doran
83851a61d5 i18n: Translate setup screen into Romanian 2024-09-08 20:02:29 +03:00
Elian Doran
893a1e3723 i18n: Translate setup screen 2024-09-08 20:00:55 +03:00
Elian Doran
6bf18b6837 i18n: Configure i18n-ally for .ejs templates 2024-09-08 19:27:19 +03:00
Elian Doran
94857d8802 server: Translate JavaScript required 2024-09-08 18:55:11 +03:00
Elian Doran
58d1e77ac1 server: Translate set password page 2024-09-08 18:28:29 +03:00
Elian Doran
1fbc65b007 electron: Fix crash on first start due to i18n 2024-09-08 18:23:06 +03:00
Elian Doran
c25c8c753d server: Translate login page 2024-09-08 18:12:16 +03:00
Elian Doran
317b7b4c59 server: Implement basic restore of language 2024-09-08 18:01:08 +03:00
Elian Doran
68042994e0 electron: Fix loading of i18n 2024-09-08 17:40:05 +03:00
Elian Doran
d00c028a0c client: Fix note actions not fitting on all languages 2024-09-08 16:38:12 +03:00
Elian Doran
a9e6c887f2 i18n: Configure extensions & nodemon 2024-09-08 16:04:14 +03:00
Elian Doran
69f9457bda i18n: Translate keyboard actions into Romanian 2024-09-08 16:04:00 +03:00
Elian Doran
0fed54f1c3 server: Translate keyboard actions 2024-09-08 15:33:06 +03:00
Elian Doran
b45fde2e5f server: Set up early initialization of i18n 2024-09-08 15:31:47 +03:00
Elian Doran
c1010a79f9 server: Initialize backend translations 2024-09-08 14:37:40 +03:00
Elian Doran
c33154d128 i18n: Translate tree context menu 2024-09-08 12:32:22 +03:00
Elian Doran
c0d613c46d i18n: Translate note map buttons 2024-09-08 11:59:24 +03:00
Elian Doran
4f7bbee769 i18n: Fix erase attachments button translation 2024-09-08 11:53:36 +03:00
Elian Doran
7a567583f2 i18n: Improve keybindings phrasing 2024-09-08 11:49:23 +03:00
Elian Doran
0ac1f071cd i18n: Tweak two Romanian translations 2024-09-08 11:48:01 +03:00
perf3ct
114e7809fe Fix CI/CD to use Matrix strategy
fix the digests missing

tweak dockerhub digests 1

Revert "tweak dockerhub digests 1"

This reverts commit 3542125d6035d2330fe1075682b046133568137d.

Revert "fix the digests missing"

This reverts commit 01954e5687549586233d73339b74e03e2182a339.

give this a shot then

add short sha

fix sha tag names
2024-09-07 22:34:57 -07:00
SiriusXT
89e073bf28 bx-map-alt->bxl-graphql 2024-09-05 15:24:37 +08:00
SiriusXT
490ae1c9fd icon improvements 2024-09-05 15:09:31 +08:00
SiriusXT
2a27383682 revision snapshots number limit 2024-09-04 14:27:33 +00:00
SiriusXT
00f8a43b0f test 2024-09-04 12:10:13 +00:00
SiriusXT
759ac4a4c8 test 2024-09-04 11:52:05 +00:00
SiriusXT
e66b865cd6 open directory 2024-09-04 11:08:04 +00:00
SiriusXT
78bfc3341b Add versioningLimit label support 2024-09-04 09:04:40 +00:00
SiriusXT
898afb7ed7 Add revision number limit 2024-09-04 08:41:17 +00:00
SiriusXT
bc4444d132 open in file manager of Trilium's data directory 2024-09-03 10:37:28 +00:00
SiriusXT
56259616cb open in file manager of Trilium's data directory 2024-09-03 10:06:01 +00:00
Jin
a50e5cc733 fix typo 2024-08-10 22:52:41 +02:00
JYC333
b790dabc8c Create nighyly release action 2024-08-10 22:38:32 +02:00
242 changed files with 10966 additions and 13449 deletions

View File

@@ -1,143 +0,0 @@
on:
workflow_dispatch:
env:
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
TEST_TAG: triliumnext/notes:test
PLATFORMS: linux/amd64
jobs:
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
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
- name: Build and export to Docker
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run the container in the background
run: docker run -d --rm --name trilium_local ${{ env.TEST_TAG }}
- name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v1
with:
container: trilium_local
wait-time: 50
require-status: running
require-healthy: true
build_docker:
name: Build Docker images
runs-on: ubuntu-latest
needs:
- test_docker
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Extract metadata (tags, labels) for GHCR image
id: ghcr-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Extract metadata (tags, labels) for DockerHub image
id: dh-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
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
- name: Log in to the GHCR container registry
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Build and push container image to GHCR
uses: docker/build-push-action@v6
id: ghcr-push
with:
file: ./Dockerfile.alpine
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.ghcr-meta.outputs.tags }}
labels: ${{ steps.ghcr-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to GHCR
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
push-to-registry: true
- name: Log in to the DockerHub container registry
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image to DockerHub
uses: docker/build-push-action@v6
id: dh-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.dh-meta.outputs.tags }}
labels: ${{ steps.dh-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to DockerHub
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.dh-push.outputs.digest }}
push-to-registry: true

View File

@@ -9,22 +9,35 @@ on:
- "bin/**"
tags:
- "v*"
workflow_dispatch:
workflow_dispatch:
env:
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
TEST_TAG: triliumnext/notes:test
PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7
IMAGE_NAME: ${{ github.repository_owner }}/notes
TEST_TAG: ${{ github.repository_owner }}/notes:test
permissions:
contents: read
packages: write
jobs:
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
strategy:
matrix:
include:
- dockerfile: Dockerfile.alpine
- dockerfile: Dockerfile
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -47,14 +60,17 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run the container in the background
run: docker run -d --rm --name trilium_local ${{ env.TEST_TAG }}
- name: Validate container run output
run: |
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --name trilium_local ${{ env.TEST_TAG }})
echo "Container ID: $CONTAINER_ID"
- name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v1
with:
@@ -63,7 +79,13 @@ jobs:
require-status: running
require-healthy: true
build_docker:
# Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
- name: Print entire log
if: always()
run: |
journalctl -u docker CONTAINER_NAME=trilium_local --no-pager
build:
name: Build Docker images
runs-on: ubuntu-latest
needs:
@@ -73,26 +95,50 @@ jobs:
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Extract metadata (tags, labels) for GHCR image
id: ghcr-meta
strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
- dockerfile: Dockerfile
platform: linux/arm64
- dockerfile: Dockerfile
platform: linux/arm/v7
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Extract metadata (tags, labels) for DockerHub image
id: dh-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
flavor: |
latest=false
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
@@ -103,50 +149,131 @@ jobs:
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Log in to the GHCR container registry
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Build and push container image to GHCR
uses: docker/build-push-action@v6
id: ghcr-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.ghcr-meta.outputs.tags }}
labels: ${{ steps.ghcr-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to GHCR
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
push-to-registry: true
- name: Log in to the DockerHub container registry
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image to DockerHub
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
id: dh-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.dh-meta.outputs.tags }}
labels: ${{ steps.dh-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to DockerHub
uses: actions/attest-build-provenance@v1
file: ${{ matrix.dockerfile }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.dh-push.outputs.digest }}
push-to-registry: true
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
name: Merge manifest lists
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=false
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
# Extract the branch or tag name from the ref
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
# Create and push the manifest list with both the branch/tag name and the commit SHA
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# If the ref is a tag, also tag the image as stable as this is part of a 'release'
# and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc...
if [[ "${GITHUB_REF}" == refs/tags/* && ! "${REF_NAME}" =~ - ]]; then
# First create stable tags
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Small delay to ensure stable tag is fully propagated
sleep 5
# Now update latest tags
docker buildx imagetools create \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
fi
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

View File

@@ -43,7 +43,7 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Update build info
run: npm run update-build-info
run: npm run update-build-info
- name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix)

129
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,129 @@
name: Nightly Release
on:
# This can be used to automatically publish nightlies at UTC nighttime
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
# This can be used to allow manually triggering nightlies from the web interface
workflow_dispatch:
env:
GITHUB_UPLOAD_URL: https://uploads.github.com/repos/TriliumNext/Notes/releases/179589950/assets{?name,label}
GITHUB_RELEASE_ID: 179589950
permissions:
contents: write
jobs:
nightly-electron:
name: Deploy nightly
strategy:
fail-fast: false
matrix:
arch: [x64, arm64]
os:
- name: macos
image: macos-latest
extension: dmg
- name: linux
image: ubuntu-latest
extension: deb
- name: windows
image: windows-latest
extension: exe
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Python for appdmg to be installed
if: ${{ matrix.os.name == 'macos' }}
run: brew install python-setuptools
- name: Install dependencies
run: npm ci
- name: Update build info
run: npm run update-build-info
- name: Update nightly version
run: npm run ci-update-nightly-version
- name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix)
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find out/make -name '*.zip' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Prepare artifacts (Windows)
if: runner.os == 'windows'
run: |
mkdir upload
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Publish artifacts
uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.zip
overwrite: true
- name: Publish installer artifacts
uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.${{ matrix.os.extension }}
overwrite: true
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip # path to archive to upload
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
- name: Deploy installer release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }} # path to archive to upload
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.${{ matrix.os.extension }} # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
nightly-server:
name: Deploy server nightly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Linux server build (x86_64)
run: |
npm run update-build-info
npm run ci-update-nightly-version
./bin/build-server.sh
- name: Prepare artifacts
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find dist -name '*.tar.xz' -print -quit)
cp "$file" "upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz"
- uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes linux server x64
path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz
overwrite: true
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
upload_url: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
asset_path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz # path to archive to upload
asset_name: TriliumNextNotes-linux-x64-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ build/
src/public/app-dist/
npm-debug.log
yarn-error.log
po-*/
*.db
!integration-tests/db/document.db

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
save-prefix = ''

View File

@@ -3,6 +3,7 @@
languageIds:
- javascript
- typescript
- html
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
# You should unescape RegEx strings in order to fit in the YAML file
@@ -25,6 +26,7 @@ scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
refactorTemplates:
- t("$1")
- ${t("$1")}
- <%= t("$1") %>
# If set to true, only enables this custom framework (will disable all built-in frameworks)

View File

@@ -5,3 +5,16 @@ reviews:
description: >-
Describes the shortcut which triggers a search within the current
page/note only
add_label.to_value:
locales:
fr:
comments:
- user:
name: Potjoe-97
email: giann@LAPTOPT490-GF
id: QXec0JUoxfGmMlpch-B1S
comment: ''
suggestion: vers la valeur
type: request_change
time: '2024-10-15T16:57:06.188Z'
resolved: true

View File

@@ -6,7 +6,8 @@
"i18n-ally.sourceLanguage": "en",
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": [
"./src/public/translations"
"./src/public/translations",
"./translations"
],
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
@@ -17,4 +18,7 @@
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"github-actions.workflows.pinned.workflows": [
".github/workflows/nightly.yml"
],
}

View File

@@ -1,7 +1,7 @@
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!!
FROM node:20.15.1-bullseye-slim
# Build stage
FROM node:20.15.1-bullseye-slim AS builder
# Configure system dependencies
# Configure build dependencies in a single layer
RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
automake \
@@ -12,49 +12,52 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
nasm \
libpng-dev \
python3 \
gosu \
&& rm -rf /var/lib/apt/lists/*
# Create app directory
WORKDIR /usr/src/app
# Bundle app source
# Copy only necessary files for build
COPY . .
COPY server-package.json package.json
# Copy TypeScript build artifacts into the original directory structure.
# Copy the healthcheck
# Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm -r build && \
rm docker_healthcheck.ts
# Install app dependencies
RUN apt-get purge -y --auto-remove \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3 \
&& rm -rf /var/lib/apt/lists/*
RUN npm install && \
rm docker_healthcheck.ts && \
npm install && \
npm run webpack && \
npm prune --omit=dev
RUN cp src/public/app/share.js src/public/app-dist/. && \
npm prune --omit=dev && \
npm cache clean --force && \
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 src/services/asset_path.ts
rm -rf src/public/app && \
rm src/services/asset_path.ts
# Some setup tools need to be kept
# Runtime stage
FROM node:20.15.1-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/* && \
rm -rf /var/cache/apt/*
# Start the application
WORKDIR /usr/src/app
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
# Configure container
EXPOSE 8080
CMD [ "./start-docker.sh" ]
HEALTHCHECK --start-period=10s CMD exec gosu node node docker_healthcheck.js

View File

@@ -1,7 +1,7 @@
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!!
FROM node:20.15.1-alpine
# Build stage
FROM node:20.15.1-alpine AS builder
# Configure system dependencies
# Configure build dependencies
RUN apk add --no-cache --virtual .build-dependencies \
autoconf \
automake \
@@ -11,43 +11,52 @@ RUN apk add --no-cache --virtual .build-dependencies \
make \
nasm \
libpng-dev \
python3
python3
# Create app directory
WORKDIR /usr/src/app
# Bundle app source
# Copy only necessary files for build
COPY . .
COPY server-package.json package.json
# Copy TypeScript build artifacts into the original directory structure.
# Copy the healthcheck
# Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm -r build && \
rm docker_healthcheck.ts
# Install app dependencies
RUN set -x && \
rm docker_healthcheck.ts && \
npm install && \
apk del .build-dependencies && \
npm run webpack && \
npm prune --omit=dev && \
npm cache clean --force && \
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 src/services/asset_path.ts
# Runtime stage
FROM node:20.15.1-alpine
# Some setup tools need to be kept
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow
# Add application user and setup proper volume permissions
WORKDIR /usr/src/app
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
# Add application user
RUN adduser -s /bin/false node; exit 0
# Start the application
# Configure container
EXPOSE 8080
CMD [ "./start-docker.sh" ]
HEALTHCHECK --start-period=10s CMD exec su-exec node node docker_healthcheck.js
HEALTHCHECK --start-period=10s CMD exec su-exec node node docker_healthcheck.js

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[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) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://triliumnext.github.io/Docs/Wiki/screenshot-tour)以快速了解:

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[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) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes es una aplicación de toma de notas jerárquicas multi-plataforma y de código libre con un enfoque en la construcción de grandes bases de conocimiento personal.

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[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) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes è un'applicazione per appunti ad organizzazione gerarchica, studiata per la costruzione di archivi di conoscenza personali di grandi dimensioni.

View File

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

View File

@@ -1,6 +1,8 @@
# TriliumNext Notes
[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) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/notes) ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/triliumnext/notes/total)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes is an open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
@@ -16,6 +18,8 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Notes instance. Just upgrade your Trilium instance to the latest version and [install TriliumNext/Notes as usual](#-installation)
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Notes/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext have their sync versions incremented.
## 💬 Discuss with us
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!
@@ -63,6 +67,16 @@ To use TriliumNext on your desktop machine (Linux, MacOS, and Windows) you have
* Currently only the latest versions of Chrome & Firefox are supported (and tested).
* (Coming Soon) TriliumNext will also be provided as a Flatpak
#### MacOS
Currently when running TriliumNext/Notes on MacOS, you may get the following error:
> Apple could not verify "TriliumNext Notes" is free of malware and may harm your Mac or compromise your privacy.
You will need to run the command on your shell to resolve the error (documented [here](https://github.com/TriliumNext/Notes/issues/329#issuecomment-2287164137)):
```bash
xattr -c "/path/to/Trilium Next.app"
```
### Mobile
To use TriliumNext on a mobile device:
@@ -82,13 +96,19 @@ You can also read [Patterns of personal knowledge base](https://triliumnext.gith
## 💻 Contribute
Clone locally and run
### Code
```shell
git clone https://github.com/TriliumNext/Notes.git
cd Notes
npm install
npm run start-server
```
### Documentation
Head on over to our [Docs repo](https://github.com/TriliumNext/Docs)
## 👏 Shoutouts
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[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) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
Trilium Notes это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://triliumnext.github.io/Docs/Wiki/screenshot-tour):

View File

@@ -22,13 +22,14 @@ rm -r $PKG_DIR/node/lib/node_modules/npm
rm -r $PKG_DIR/node/include/node
rm -r $PKG_DIR/node_modules/electron*
rm -r $PKG_DIR/electron.js
rm -r $PKG_DIR/electron*.js
printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh
printf "#!/bin/sh\n./node/bin/node src/main" > $PKG_DIR/trilium.sh
chmod 755 $PKG_DIR/trilium.sh
cp bin/tpl/anonymize-database.sql $PKG_DIR/
cp -r translations $PKG_DIR/
cp -r dump-db $PKG_DIR/
rm -rf $PKG_DIR/dump-db/node_modules

View File

@@ -35,7 +35,7 @@ const copy = async () => {
await fs.copy(file, path.join(DEST_DIR, file));
}
const dirsToCopy = ["images", "libraries", "db"];
const dirsToCopy = ["images", "libraries", "translations", "db"];
for (const dir of dirsToCopy) {
log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR, dir));
@@ -47,6 +47,15 @@ const copy = async () => {
await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir)));
}
/**
* Directories to be copied relative to the project root into <resource_dir>/src/public/app-dist.
*/
const publicDirsToCopy = [ "./src/public/app/doc_notes" ];
const PUBLIC_DIR = path.join(DEST_DIR, "src", "public", "app-dist");
for (const dir of publicDirsToCopy) {
await fs.copy(dir, path.join(PUBLIC_DIR, path.basename(dir)));
}
const nodeModulesFile = [
"node_modules/react/umd/react.production.min.js",
"node_modules/react/umd/react.development.js",
@@ -55,6 +64,7 @@ const copy = async () => {
"node_modules/katex/dist/katex.min.js",
"node_modules/katex/dist/contrib/mhchem.min.js",
"node_modules/katex/dist/contrib/auto-render.min.js",
"node_modules/@highlightjs/cdn-assets/highlight.min.js"
];
for (const file of nodeModulesFile) {
@@ -89,7 +99,9 @@ const copy = async () => {
"node_modules/codemirror/addon/",
"node_modules/codemirror/mode/",
"node_modules/codemirror/keymap/",
"node_modules/mind-elixir/dist/"
"node_modules/mind-elixir/dist/",
"node_modules/@highlightjs/cdn-assets/languages",
"node_modules/@highlightjs/cdn-assets/styles"
];
for (const folder of nodeModulesFolder) {

View File

@@ -37,11 +37,11 @@ for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample
done
# Patch package.json main
sed -i 's/.\/dist\/electron.js/electron.js/g' "$DIR/package.json"
sed -i 's/.\/dist\/electron-main.js/electron-main.js/g' "$DIR/package.json"
script_dir=$(realpath $(dirname $0))
cp -R "$script_dir/../build/src" "$DIR"
cp "$script_dir/../build/electron.js" "$DIR"
cp "$script_dir/../build/electron-main.js" "$DIR"
# run in subshell (so we return to original dir)
(cd $DIR && npm install --omit=dev)

View File

@@ -1,15 +0,0 @@
{
"src": "dist/trilium-linux-x64",
"dest": "dist/",
"compression": "xz",
"name": "trilium",
"productName": "Trilium Notes",
"genericName": "Note taker",
"description": "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.",
"sections": "misc",
"maintainer": "zadam.apps@gmail.com",
"homepage": "https://github.com/zadam/trilium",
"bin": "trilium",
"icon": "dist/trilium-linux-x64/icon.png",
"categories": [ "Office" ]
}

View File

@@ -0,0 +1,12 @@
[Desktop Entry]
<% if (productName) { %>Name=<%= productName %>
<% } %><% if (description) { %>Comment=<%= description %>
<% } %><% if (genericName) { %>GenericName=<%= genericName %>
<% } %><% if (name) { %>Exec=<%= name %> %U
Icon=<%= name %>
<% } %>Type=Application
StartupNotify=true
<% if (productName) { %>StartupWMClass=<%= productName %>
<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>;
<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>;
<% } %>

98
bin/translation.sh Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# --------------------------------------------------------------------------------------------------
#
# Create PO files to make easier the labor of translation.
#
# Info:
# https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
# https://docs.translatehouse.org/projects/translate-toolkit/en/latest/commands/json2po.html
#
# Dependencies:
# jq
# translate-toolkit
# python-wcwidth
#
# Created by @hasecilu
#
# --------------------------------------------------------------------------------------------------
stats() {
# Print the number of existing strings on the JSON files for each locale
s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/en/server.json" | wc -l)
c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/en/translation.json" | wc -l)
echo "|locale |server strings |client strings |"
echo "|-------|---------------|---------------|"
echo "| en | ${s} | ${c} |"
for locale in "${locales[@]}"; do
s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/${locale}/server.json" | wc -l)
c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/${locale}/translation.json" | wc -l)
echo "| ${locale} | ${s} | ${c} |"
done
}
help() {
echo -e "\nDescription:"
echo -e "\tCreate PO files to make easier the labor of translation"
echo -e "\nUsage:"
echo -e "\t./translation.sh [--stats] [--update <OPT_LOCALE>] [--update2 <OPT_LOCALE>]"
echo -e "\nFlags:"
echo -e " --clear\n\tClear all po-* directories"
echo -e " --stats\n\tPrint the number of existing strings on the JSON files for each locale"
echo -e " --update <LOCALE>\n\tUpdate PO files from English and localized JSON files as source"
echo -e " --update2 <LOCALE>\n\tRecover translation from PO files to localized JSON files"
}
# Main function ------------------------------------------------------------------------------------
# Get script directory to set file path relative to it
file_path="$(
cd -- "$(dirname "${0}")" >/dev/null 2>&1 || exit
pwd -P
)"
paths=("${file_path}/../translations/" "${file_path}/../src/public/translations/")
locales=(cn es fr ro)
if [ $# -eq 1 ]; then
if [ "$1" == "--clear" ]; then
for path in "${paths[@]}"; do
for locale in "${locales[@]}"; do
[ -d "${path}/po-${locale}" ] && rm -r "${path}/po-${locale}"
done
done
elif [ "$1" == "--stats" ]; then
stats
elif [ "$1" == "--update" ]; then
# Update PO files from English and localized JSON files as source
for path in "${paths[@]}"; do
for locale in "${locales[@]}"; do
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
done
done
elif [ "$1" == "--update2" ]; then
# Recover translation from PO files to localized JSON files
for path in "${paths[@]}"; do
for locale in "${locales[@]}"; do
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
done
done
else
help
fi
elif [ $# -eq 2 ]; then
if [ "$1" == "--update" ]; then
locale="$2"
for path in "${paths[@]}"; do
json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}"
done
elif [ "$1" == "--update2" ]; then
locale="$2"
for path in "${paths[@]}"; do
po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}"
done
else
help
fi
else
help
fi

View File

@@ -0,0 +1,50 @@
/**
* @module
*
* The nightly version works uses the version described in `package.json`, just like any release.
* The problem with this approach is that production builds have a very aggressive cache, and
* usually running the nightly with this cached version of the application will mean that the
* user might run into module not found errors or styling errors caused by an old cache.
*
* This script is supposed to be run in the CI, which will update locally the version field of
* `package.json` to contain the date. For example, `0.90.9-beta` will become `0.90.9-test-YYMMDD-HHMMSS`.
*
*/
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import fs from "fs";
function processVersion(version) {
// Remove the beta suffix if any.
version = version.replace("-beta", "");
// Add the nightly suffix, plus the date.
const referenceDate = new Date()
.toISOString()
.substring(2, 19)
.replace(/[-:]*/g, "")
.replace("T", "-");
version = `${version}-test-${referenceDate}`;
return version;
}
function main() {
const scriptDir = dirname(fileURLToPath(import.meta.url));
const packageJsonPath = join(scriptDir, "..", "package.json");
// Read the version from package.json and process it.
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
const currentVersion = packageJson.version;
const adjustedVersion = processVersion(currentVersion);
console.log("Current version is", currentVersion);
console.log("Adjusted version is", adjustedVersion);
// Write the adjusted version back in.
packageJson.version = adjustedVersion;
const formattedJson = JSON.stringify(packageJson, null, 4);
fs.writeFileSync(packageJsonPath, formattedJson);
}
main();

Binary file not shown.

View File

@@ -42,9 +42,9 @@ const NOTE_TYPE_ICONS = {
"code": "bx bx-code",
"render": "bx bx-extension",
"search": "bx bx-file-find",
"relationMap": "bx bx-map-alt",
"relationMap": "bx bxs-network-chart",
"book": "bx bx-book",
"noteMap": "bx bx-map-alt",
"noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection",
"canvas": "bx bx-pen",
"webView": "bx bx-globe-alt",
@@ -570,7 +570,7 @@ class FNote {
return workspaceIconClass;
}
else if (this.noteId === 'root') {
return "bx bx-chevrons-right";
return "bx bx-home-alt-2";
}
if (this.noteId === '_share') {
return "bx bx-share-alt";

4
electron-main.ts Normal file
View File

@@ -0,0 +1,4 @@
import { initializeTranslations } from "./src/services/i18n.js";
await initializeTranslations();
await import("./electron.js")

View File

@@ -70,4 +70,4 @@ electron.app.on("will-quit", () => {
// this is to disable electron warning spam in the dev console (local development only)
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
await import('./src/www.js');
await import('./src/main.js');

View File

@@ -10,17 +10,31 @@ module.exports = {
overwrite: true,
asar: true,
icon: "./images/app-icons/icon",
extraResource: getExtraResourcesForPlatform(),
extraResource: [
// Moved to root
...getExtraResourcesForPlatform(),
// Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS)
"translations/",
"node_modules/@highlightjs/cdn-assets/styles"
],
afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => {
const extraResources = getExtraResourcesForPlatform();
for (const resource of extraResources) {
const baseName = path.basename(resource);
let sourcePath;
if (platform === 'darwin') {
sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', path.basename(resource));
sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', baseName);
} else {
sourcePath = path.join(buildPath, 'resources', path.basename(resource));
sourcePath = path.join(buildPath, 'resources', baseName);
}
let destPath;
if (baseName !== "256x256.png") {
destPath = path.join(buildPath, baseName);
} else {
destPath = path.join(buildPath, "icon.png");
}
const destPath = path.join(buildPath, path.basename(resource));
// Copy files from resources folder to root
fs.move(sourcePath, destPath)
@@ -38,6 +52,7 @@ module.exports = {
config: {
options: {
icon: "./images/app-icons/png/128x128.png",
desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs")
}
}
},
@@ -75,7 +90,10 @@ module.exports = {
function getExtraResourcesForPlatform() {
let resources = ['dump-db/', './bin/tpl/anonymize-database.sql']
let resources = [
'dump-db/',
'./bin/tpl/anonymize-database.sql'
];
const scripts = ['trilium-portable', 'trilium-safe-mode', 'trilium-no-cert-check']
switch (process.platform) {
case 'win32':
@@ -86,6 +104,7 @@ function getExtraResourcesForPlatform() {
case 'darwin':
break;
case 'linux':
resources.push("images/app-icons/png/256x256.png")
for (const script of scripts) {
resources.push(`./bin/tpl/${script}.sh`)
}

View File

@@ -526,16 +526,19 @@
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre {
padding: 1em;
color: hsl(0, 0%, 20.8%);
background: hsla(0, 0%, 78%, 0.3);
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
text-align: left;
direction: ltr;
tab-size: 4;
white-space: pre-wrap;
font-style: normal;
min-width: 200px;
border: 0px;
border-radius: 6px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
}
.ck-content pre:not(.hljs) {
color: hsl(0, 0%, 20.8%);
background: hsla(0, 0%, 78%, 0.3);
}
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre code {

49
libraries/ckeditor/ckeditor.d.ts vendored Normal file
View File

@@ -0,0 +1,49 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import { DecoupledEditor as DecoupledEditorBase } from '@ckeditor/ckeditor5-editor-decoupled';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { Alignment } from '@ckeditor/ckeditor5-alignment';
import { FontSize, FontFamily, FontColor, FontBackgroundColor } from '@ckeditor/ckeditor5-font';
import { CKFinderUploadAdapter } from '@ckeditor/ckeditor5-adapter-ckfinder';
import { Autoformat } from '@ckeditor/ckeditor5-autoformat';
import { Bold, Italic, Strikethrough, Underline } from '@ckeditor/ckeditor5-basic-styles';
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
import { CKBox } from '@ckeditor/ckeditor5-ckbox';
import { CKFinder } from '@ckeditor/ckeditor5-ckfinder';
import { EasyImage } from '@ckeditor/ckeditor5-easy-image';
import { Heading } from '@ckeditor/ckeditor5-heading';
import { Image, ImageCaption, ImageResize, ImageStyle, ImageToolbar, ImageUpload, PictureEditing } from '@ckeditor/ckeditor5-image';
import { Indent, IndentBlock } from '@ckeditor/ckeditor5-indent';
import { Link } from '@ckeditor/ckeditor5-link';
import { List, ListProperties } from '@ckeditor/ckeditor5-list';
import { MediaEmbed } from '@ckeditor/ckeditor5-media-embed';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office';
import { Table, TableToolbar } from '@ckeditor/ckeditor5-table';
import { TextTransformation } from '@ckeditor/ckeditor5-typing';
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
export default class DecoupledEditor extends DecoupledEditorBase {
static builtinPlugins: (typeof TextTransformation | typeof Essentials | typeof Alignment | typeof FontBackgroundColor | typeof FontColor | typeof FontFamily | typeof FontSize | typeof CKFinderUploadAdapter | typeof Paragraph | typeof Heading | typeof Autoformat | typeof Bold | typeof Italic | typeof Strikethrough | typeof Underline | typeof BlockQuote | typeof Image | typeof ImageCaption | typeof ImageResize | typeof ImageStyle | typeof ImageToolbar | typeof ImageUpload | typeof CloudServices | typeof CKBox | typeof CKFinder | typeof EasyImage | typeof List | typeof ListProperties | typeof Indent | typeof IndentBlock | typeof Link | typeof MediaEmbed | typeof PasteFromOffice | typeof Table | typeof TableToolbar | typeof PictureEditing)[];
static defaultConfig: {
toolbar: {
items: string[];
};
image: {
resizeUnit: "px";
toolbar: string[];
};
table: {
contentToolbar: string[];
};
list: {
properties: {
styles: boolean;
startIndex: boolean;
reversed: boolean;
};
};
language: string;
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,10 @@
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
"verbose": false,
"exec": "tsx",
"watch": ["src/"],
"watch": [
"src/",
"translations/"
],
"signal": "SIGTERM",
"env": {
"NODE_ENV": "development"

2547
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,9 @@
"name": "trilium",
"productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.90.6-beta",
"version": "0.90.11-beta",
"license": "AGPL-3.0-only",
"main": "./dist/electron.js",
"main": "./dist/electron-main.js",
"author": {
"name": "TriliumNext Notes Team",
"email": "contact@eliandoran.me",
@@ -12,7 +12,7 @@
},
"copyright": "",
"bin": {
"trilium": "src/www.js"
"trilium": "src/main.js"
},
"repository": {
"type": "git",
@@ -20,12 +20,12 @@
},
"type": "module",
"scripts": {
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
"start-server-safe": "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",
"start-test-server": "npm run switch-server; rimraf ./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",
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-test-server": "npm run switch-server; rimraf ./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/main.ts",
"qstart-server": "npm run switch-server && npm run start-server",
"start-electron": "npm run prepare-dist && 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": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.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 switch-electron && npm run start-electron",
"switch-server": "rimraf ./node_modules/better-sqlite3 && npm install",
@@ -34,7 +34,7 @@
"build-frontend-docs": "rimraf ./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": "cross-env node --import ./loader-register.js node_modules/webpack/bin/webpack.js -c webpack.config.ts",
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/.bin/jasmine",
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/jasmine/bin/jasmine.js",
"test-es6": "tsx -r esm spec-es6/attribute_parser.spec.ts",
"test": "npm run test-jasmine && npm run test-es6",
"start-electron-forge": "npm run prepare-dist && electron-forge start",
@@ -43,155 +43,158 @@
"prepare-dist": "rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
"update-build-info": "tsx bin/update-build-info.ts",
"errors": "tsc --watch --noEmit",
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
"generate-document": "cross-env nodemon src/tools/generate_document.ts 1000"
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"generate-document": "cross-env nodemon src/tools/generate_document.ts 1000",
"ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts"
},
"dependencies": {
"@braintree/sanitize-url": "^7.1.0",
"@braintree/sanitize-url": "7.1.0",
"@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "^0.17.6",
"@types/electron-squirrel-startup": "^1.0.2",
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
"autocomplete.js": "^0.38.1",
"axios": "^1.7.2",
"better-sqlite3": "^11.1.2",
"bootstrap": "^4.6.2",
"@excalidraw/excalidraw": "0.17.6",
"@highlightjs/cdn-assets": "11.10.0",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"autocomplete.js": "0.38.1",
"axios": "1.7.7",
"better-sqlite3": "11.3.0",
"bootstrap": "5.3.3",
"boxicons": "2.1.4",
"chokidar": "3.6.0",
"cls-hooked": "4.2.2",
"codemirror": "^5.65.17",
"codemirror": "5.65.18",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"cookie-parser": "1.4.7",
"csurf": "1.11.0",
"dayjs": "^1.11.12",
"dayjs": "1.11.13",
"dayjs-plugin-utc": "0.1.2",
"debounce": "^2.1.0",
"ejs": "^3.1.10",
"electron-debug": "3.2.0",
"electron-dl": "3.5.2",
"electron-squirrel-startup": "^1.0.0",
"debounce": "2.2.0",
"ejs": "3.1.10",
"electron-debug": "4.0.1",
"electron-dl": "4.0.0",
"electron-squirrel-startup": "1.0.1",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"eslint": "^9.9.0",
"express": "^4.19.2",
"eslint": "9.14.0",
"express": "4.21.1",
"express-partial-content": "1.0.2",
"express-rate-limit": "^7.3.1",
"express-session": "1.18.0",
"force-graph": "1.43.5",
"express-rate-limit": "7.4.1",
"express-session": "1.18.1",
"force-graph": "1.46.0",
"fs-extra": "11.2.0",
"helmet": "7.1.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "^7.0.5",
"i18next": "^23.14.0",
"i18next-http-backend": "^2.6.1",
"https-proxy-agent": "7.0.5",
"i18next": "23.16.4",
"i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.2",
"image-type": "4.1.0",
"ini": "^4.1.3",
"ini": "5.0.0",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.22.12",
"joplin-turndown-plugin-gfm": "1.0.12",
"jquery": "3.7.1",
"jquery-hotkeys": "0.2.2",
"jquery.fancytree": "^2.38.3",
"jsdom": "^24.1.0",
"jsplumb": "^2.15.6",
"katex": "^0.16.11",
"knockout": "^3.5.1",
"mark.js": "^8.11.1",
"marked": "^13.0.2",
"mermaid": "^10.9.1",
"jquery.fancytree": "2.38.3",
"jsdom": "25.0.0",
"jsplumb": "2.15.6",
"katex": "0.16.11",
"knockout": "3.5.1",
"mark.js": "8.11.1",
"marked": "14.1.3",
"mermaid": "11.4.0",
"mime-types": "2.1.35",
"mind-elixir": "^4.0.5",
"mind-elixir": "4.3.1",
"multer": "1.4.5-lts.1",
"node-abi": "^3.65.0",
"node-abi": "3.67.0",
"normalize-strings": "1.1.1",
"normalize.css": "^8.0.1",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"print-this": "2.0.0",
"rand-token": "1.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"request": "2.88.2",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "^2.13.0",
"sax": "^1.4.1",
"semver": "^7.6.3",
"sanitize-html": "2.13.1",
"sax": "1.4.1",
"semver": "7.6.3",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "^0.5.21",
"source-map-support": "0.5.21",
"split.js": "1.6.5",
"stream-throttle": "0.1.3",
"striptags": "3.2.0",
"tmp": "0.2.3",
"tree-kill": "1.2.2",
"turndown": "^7.2.0",
"turndown": "7.2.0",
"unescape": "1.0.1",
"vanilla-js-wheel-zoom": "^9.0.2",
"ws": "^8.18.0",
"vanilla-js-wheel-zoom": "9.0.4",
"ws": "8.18.0",
"xml2js": "0.6.2",
"yauzl": "^3.1.3"
"yauzl": "3.1.3"
},
"devDependencies": {
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-dmg": "^7.4.0",
"@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-zip": "^7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@playwright/test": "^1.46.0",
"@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/debounce": "^1.2.4",
"@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/jasmine": "^5.1.4",
"@types/jsdom": "^21.1.6",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.12",
"@types/node": "^22.5.2",
"@types/safe-compare": "^1.1.2",
"@types/sanitize-html": "^2.13.0",
"@types/sax": "^1.2.7",
"@types/semver": "^7.5.8",
"@types/serve-favicon": "^2.5.7",
"@types/session-file-store": "^1.2.5",
"@types/source-map-support": "^0.5.10",
"@types/stream-throttle": "^0.1.4",
"@types/tmp": "^0.2.6",
"@types/turndown": "^5.0.5",
"@types/ws": "^8.5.12",
"@types/xml2js": "^0.4.14",
"@electron-forge/cli": "7.5.0",
"@electron-forge/maker-deb": "7.5.0",
"@electron-forge/maker-dmg": "7.5.0",
"@electron-forge/maker-squirrel": "7.5.0",
"@electron-forge/maker-zip": "7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "7.5.0",
"@playwright/test": "1.48.2",
"@types/archiver": "6.0.2",
"@types/better-sqlite3": "7.6.11",
"@types/cls-hooked": "4.3.8",
"@types/compression": "1.7.5",
"@types/cookie-parser": "1.4.7",
"@types/csurf": "1.11.5",
"@types/debounce": "1.2.4",
"@types/ejs": "3.1.5",
"@types/electron-squirrel-startup": "1.0.2",
"@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.1",
"@types/jasmine": "5.1.4",
"@types/jsdom": "21.1.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.7.8",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
"@types/semver": "7.5.8",
"@types/serve-favicon": "2.5.7",
"@types/session-file-store": "1.2.5",
"@types/source-map-support": "0.5.10",
"@types/stream-throttle": "0.1.4",
"@types/tmp": "0.2.6",
"@types/turndown": "5.0.5",
"@types/ws": "8.5.12",
"@types/xml2js": "0.4.14",
"cross-env": "7.0.3",
"electron": "^31.2.1",
"electron": "31.3.1",
"electron-packager": "17.1.2",
"electron-rebuild": "3.2.9",
"esm": "3.2.25",
"iconsur": "^1.7.0",
"jasmine": "5.1.0",
"jsdoc": "^4.0.3",
"iconsur": "1.7.0",
"jasmine": "5.4.0",
"jsdoc": "4.0.3",
"lorem-ipsum": "2.0.8",
"nodemon": "^3.1.4",
"nodemon": "3.1.7",
"rcedit": "4.0.1",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"tslib": "^2.7.0",
"tsx": "^4.19.0",
"typescript": "^5.5.4",
"webpack": "^5.93.0",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
"tslib": "2.8.1",
"tsx": "4.19.2",
"typescript": "5.6.3",
"webpack": "5.96.1",
"webpack-cli": "5.1.4"
}
}

View File

@@ -14,6 +14,7 @@ import custom from "./routes/custom.js";
import error_handlers from "./routes/error_handlers.js";
import { startScheduledCleanup } from "./services/erase.js";
import sql_init from "./services/sql_init.js";
import { t } from "i18next";
await import('./services/handlers.js');
await import('./becca/becca_loader.js');
@@ -29,6 +30,11 @@ sql_init.initializeDb();
app.set('views', path.join(scriptDir, 'views'));
app.set('view engine', 'ejs');
app.use((req, res, next) => {
res.locals.t = t;
return next();
});
if (!utils.isElectron()) {
app.use(compression()); // HTTP compression
}
@@ -47,6 +53,7 @@ app.use(cookieParser());
app.use(express.static(path.join(scriptDir, 'public/root')));
app.use(`/manifest.webmanifest`, express.static(path.join(scriptDir, 'public/manifest.webmanifest')));
app.use(`/robots.txt`, express.static(path.join(scriptDir, 'public/robots.txt')));
app.use(`/icon.png`, express.static(path.join(scriptDir, 'public/icon.png')));
app.use(sessionParser);
app.use(favicon(`${scriptDir}/../images/app-icons/icon.ico`));

View File

@@ -13,11 +13,13 @@ import cls from "../services/cls.js";
import entityConstructor from "../becca/entity_constructor.js";
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows.js';
import AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
import options_init from "../services/options_init.js";
import ws from "../services/ws.js";
const beccaLoaded = new Promise<void>(async (res, rej) => {
const sqlInit = (await import("../services/sql_init.js")).default;
// We have to import async since options init requires keyboard actions which require translations.
const options_init = (await import("../services/options_init.js")).default;
sqlInit.dbReady.then(() => {
cls.init(() => {
load();

View File

@@ -3,6 +3,8 @@
import protectedSessionService from "../../services/protected_session.js";
import log from "../../services/log.js";
import sql from "../../services/sql.js";
import optionService from "../../services/options.js";
import eraseService from "../../services/erase.js";
import utils from "../../services/utils.js";
import dateUtils from "../../services/date_utils.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
@@ -68,7 +70,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
/** set during the deletion operation, before it is completed (removed from becca completely). */
isBeingDeleted!: boolean;
isDecrypted!: boolean;
ownedAttributes!: BAttribute[];
parentBranches!: BBranch[];
parents!: BNote[];
@@ -455,8 +457,8 @@ class BNote extends AbstractBeccaEntity<BNote> {
return this.getAttributes().find(
attr => attr.name.toLowerCase() === name
&& (!value || attr.value.toLowerCase() === value)
&& attr.type === type);
&& (!value || attr.value.toLowerCase() === value)
&& attr.type === type);
}
getRelationTarget(name: string) {
@@ -1107,7 +1109,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
}
getRevisions(): BRevision[] {
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ? ORDER BY revisions.utcDateCreated ASC", [this.noteId])
.map(row => new BRevision(row));
}
@@ -1612,10 +1614,31 @@ class BNote extends AbstractBeccaEntity<BNote> {
revision.setContent(noteContent);
this.eraseExcessRevisionSnapshots()
return revision;
});
}
// Limit the number of Snapshots to revisionSnapshotNumberLimit
// Delete older Snapshots that exceed the limit
eraseExcessRevisionSnapshots() {
// lable has a higher priority
let revisionSnapshotNumberLimit = parseInt(this.getLabelValue("versioningLimit") ?? "");
if (!Number.isInteger(revisionSnapshotNumberLimit)) {
revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit'));
}
if (revisionSnapshotNumberLimit >= 0) {
const revisions = this.getRevisions();
if (revisions.length - revisionSnapshotNumberLimit > 0) {
const revisionIds = revisions
.slice(0, revisions.length - revisionSnapshotNumberLimit)
.map(revision => revision.revisionId)
.filter((id): id is string => id !== undefined);
eraseService.eraseRevisions(revisionIds);
}
}
}
/**
* @param matchBy - choose by which property we detect if to update an existing attachment.
* Supported values are either 'attachmentId' (default) or 'title'

View File

@@ -38,9 +38,18 @@ export interface RecentNoteRow {
utcDateCreated?: string;
}
/**
* Database representation of an option.
*
* Options are key-value pairs that are used to store information such as user preferences (for example
* the current theme, sync server information), but also information about the state of the application).
*/
export interface OptionRow {
/** The name of the option. */
name: string;
/** The value of the option. */
value: string;
/** `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme). */
isSynced: boolean;
utcDateModified?: string;
}

13
src/main.ts Normal file
View File

@@ -0,0 +1,13 @@
/*
* Make sure not to import any modules that depend on localized messages via i18next here, as the initializations
* are loaded later and will result in an empty string.
*/
import { initializeTranslations } from "./services/i18n.js";
async function startApplication() {
await import("./www.js");
}
await initializeTranslations();
await startApplication();

View File

@@ -13,7 +13,7 @@ import MobileScreenSwitcherExecutor from "./mobile_screen_switcher.js";
import MainTreeExecutors from "./main_tree_executors.js";
import toast from "../services/toast.js";
import ShortcutComponent from "./shortcut_component.js";
import { initLocale } from "../services/i18n.js";
import { t, initLocale } from "../services/i18n.js";
class AppContext extends Component {
constructor(isMainWindow) {
@@ -33,11 +33,11 @@ class AppContext extends Component {
await initLocale();
}
setLayout(layout) {
setLayout(layout) {
this.layout = layout;
}
async start() {
async start() {
this.initComponents();
this.renderWidgets();
@@ -151,7 +151,7 @@ $(window).on('beforeunload', () => {
if (!component.beforeUnloadEvent()) {
console.log(`Component ${component.componentId} is not finished saving its state.`);
toast.showMessage("Please wait for a couple of seconds for the save to finish, then you can try again.", 10000);
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
allSaved = false;
}

View File

@@ -9,6 +9,7 @@ import ws from "../services/ws.js";
import bundleService from "../services/bundle.js";
import froca from "../services/froca.js";
import linkService from "../services/link.js";
import { t } from "../services/i18n.js";
export default class Entrypoints extends Component {
constructor() {
@@ -172,13 +173,13 @@ export default class Entrypoints extends Component {
const resp = await server.post(`sql/execute/${note.noteId}`);
if (!resp.success) {
toastService.showError(`Error occurred while executing SQL query: ${resp.error}`);
toastService.showError(t("entrypoints.sql-error", { message: resp.error }));
}
await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results});
}
toastService.showMessage("Note executed");
toastService.showMessage(t("entrypoints.note-executed"));
}
hideAllPopups() {
@@ -200,6 +201,6 @@ export default class Entrypoints extends Component {
await server.post(`notes/${noteId}/revision`);
toastService.showMessage("Note revision has been created.");
toastService.showMessage(t("entrypoints.note-revision-created"));
}
}

View File

@@ -551,7 +551,7 @@ export default class TabManager extends Component {
await this.removeNoteContext(ntxIdToRemove);
}
}
async closeOtherTabsCommand({ntxId}) {
for (const ntxIdToRemove of this.mainNoteContexts.map(nc => nc.ntxId)) {
if (ntxIdToRemove !== ntxId) {
@@ -560,6 +560,18 @@ export default class TabManager extends Component {
}
}
async closeRightTabsCommand({ntxId}) {
const ntxIds = this.mainNoteContexts.map(nc => nc.ntxId);
const index = ntxIds.indexOf(ntxId);
if (index !== -1) {
const idsToRemove = ntxIds.slice(index + 1);
for (const ntxIdToRemove of idsToRemove) {
await this.removeNoteContext(ntxIdToRemove);
}
}
}
async closeTabCommand({ntxId}) {
await this.removeNoteContext(ntxId);
}

View File

@@ -16,7 +16,7 @@ class ZoomComponent extends Component {
window.addEventListener("wheel", event => {
if (event.ctrlKey) {
this.setZoomFactorAndSave(this.getCurrentZoom() + event.deltaY * 0.001);
this.setZoomFactorAndSave(this.getCurrentZoom() - event.deltaY * 0.001);
}
});
}
@@ -56,7 +56,7 @@ class ZoomComponent extends Component {
zoomResetEvent() {
this.setZoomFactorAndSave(1);
}
setZoomFactorAndSaveEvent({zoomFactor}) {
this.setZoomFactorAndSave(zoomFactor);
}

View File

@@ -9,9 +9,9 @@ import electronContextMenu from "./menus/electron_context_menu.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
await appContext.earlyInit();
bundleService.getWidgetBundlesByParent().then(async widgetBundles => {
await appContext.earlyInit();
// A dynamic import is required for layouts since they initialize components which require translations.
const DesktopLayout = (await import("./layouts/desktop_layout.js")).default;

View File

@@ -14,9 +14,9 @@ const NOTE_TYPE_ICONS = {
"code": "bx bx-code",
"render": "bx bx-extension",
"search": "bx bx-file-find",
"relationMap": "bx bx-map-alt",
"relationMap": "bx bxs-network-chart",
"book": "bx bx-book",
"noteMap": "bx bx-map-alt",
"noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection",
"canvas": "bx bx-pen",
"webView": "bx bx-globe-alt",
@@ -543,7 +543,7 @@ class FNote {
return workspaceIconClass;
}
else if (this.noteId === 'root') {
return "bx bx-chevrons-right";
return "bx bx-home-alt-2";
}
if (this.noteId === '_share') {
return "bx bx-share-alt";

View File

@@ -38,6 +38,8 @@ import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import EditButton from "../widgets/buttons/edit_button.js";
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
import MermaidWidget from "../widgets/mermaid.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
@@ -80,6 +82,7 @@ import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js";
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
export default class DesktopLayout {
constructor(customWidgets) {
@@ -138,6 +141,7 @@ export default class DesktopLayout {
// the order of the widgets matter. Some of these want to "activate" themselves
// when visible. When this happens to multiple of them, the first one "wins".
// promoted attributes should always win.
.ribbon(new ClassicEditorToolbar())
.ribbon(new PromotedAttributesWidget())
.ribbon(new ScriptExecutorWidget())
.ribbon(new SearchDefinitionWidget())
@@ -160,6 +164,8 @@ export default class DesktopLayout {
.child(new WatchedFileUpdateStatusWidget())
.child(new FloatingButtons()
.child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new CopyImageReferenceButton())

View File

@@ -23,6 +23,7 @@ import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.js";
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
const MOBILE_CSS = `
<style>
@@ -167,6 +168,7 @@ export default class MobileLayout {
.child(new NoteListWidget())
.child(new FilePropertiesWidget().css('font-size','smaller'))
)
.child(new ClassicEditorToolbar())
)
.child(new ProtectedSessionPasswordDialog())
.child(new ConfirmDialog())

View File

@@ -22,15 +22,31 @@ function setupContextMenu($image) {
command: "copyImageReferenceToClipboard",
uiIcon: "bx bx-empty"
},
{title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty"},
{ title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty" },
],
selectMenuItemHandler: ({command}) => {
selectMenuItemHandler: async ({ command }) => {
if (command === 'copyImageReferenceToClipboard') {
imageService.copyImageReferenceToClipboard($image);
} else if (command === 'copyImageToClipboard') {
const webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
utils.dynamicRequire('electron');
webContents.copyImageAt(e.pageX, e.pageY);
try {
const nativeImage = utils.dynamicRequire('electron').nativeImage;
const clipboard = utils.dynamicRequire('electron').clipboard;
const response = await fetch(
$image.attr('src')
);
const blob = await response.blob();
clipboard.writeImage(
nativeImage.createFromBuffer(
Buffer.from(
await blob.arrayBuffer()
)
)
);
} catch (error) {
console.error('Failed to copy image to clipboard:', error);
}
} else {
throw new Error(`Unrecognized command '${command}'`);
}
@@ -41,4 +57,4 @@ function setupContextMenu($image) {
export default {
setupContextMenu
};
};

View File

@@ -3,6 +3,7 @@ import froca from "../services/froca.js";
import contextMenu from "./context_menu.js";
import dialogService from "../services/dialog.js";
import server from "../services/server.js";
import { t } from '../services/i18n.js';
export default class LauncherContextMenu {
/**
@@ -33,29 +34,27 @@ export default class LauncherContextMenu {
const isAvailableItem = parentNoteId === '_lbAvailableLaunchers';
const isItem = isVisibleItem || isAvailableItem;
const canBeDeleted = !note.noteId.startsWith("_"); // fixed notes can't be deleted
const canBeReset = !canBeDeleted && note.isLaunchBarConfig();;
const canBeReset = !canBeDeleted && note.isLaunchBarConfig();
return [
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a script launcher', command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a custom widget', command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: 'Add spacer', command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-note-launcher"), command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-script-launcher"), command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-custom-widget"), command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-spacer"), command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: "----" } : null,
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted },
{ title: 'Reset', command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset},
{ title: `${t("launcher_context_menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted },
{ title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset},
{ title: "----" },
isAvailableItem ? { title: 'Move to visible launchers', command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
isVisibleItem ? { title: 'Move to available launchers', command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
{ title: `Duplicate launcher <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
{ title: `${t("launcher_context_menu.duplicate-launcher")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
enabled: isItem }
].filter(row => row !== null);
}
async selectMenuItemHandler({command}) {
if (command === 'resetLauncher') {
const confirmed = await dialogService.confirm(`Do you really want to reset "${this.node.title}"?
All data / settings in this note (and its children) will be lost
and the launcher will be returned to its original location.`);
const confirmed = await dialogService.confirm(t("launcher_context_menu.reset_launcher_confirm", { title: this.node.title }));
if (confirmed) {
await server.post(`special-notes/launchers/${this.node.data.noteId}/reset`);

View File

@@ -8,6 +8,7 @@ import noteTypesService from "../services/note_types.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
import dialogService from "../services/dialog.js";
import { t } from "../services/i18n.js";
export default class TreeContextMenu {
/**
@@ -48,55 +49,55 @@ export default class TreeContextMenu {
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
return [
{ 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",
{ title: `${t("tree-context-menu.open-in-a-new-tab")} <kbd>Ctrl+Click</kbd>`, command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.insert-note-after")} <kbd data-command="createNoteAfter"></kbd>`, command: "insertNoteAfter", uiIcon: "bx bx-plus",
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions },
{ title: 'Insert child note <kbd data-command="createNoteInto"></kbd>', command: "insertChildNote", uiIcon: "bx bx-plus",
{ title: `${t("tree-context-menu.insert-child-note")} <kbd data-command="createNoteInto"></kbd>`, command: "insertChildNote", uiIcon: "bx bx-plus",
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
enabled: notSearch && noSelectedNotes && notOptions },
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash",
{ title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash",
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions },
{ title: "----" },
{ title: 'Search in subtree <kbd data-command="searchInSubtree"></kbd>', command: "searchInSubtree", uiIcon: "bx bx-search",
{ title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`, command: "searchInSubtree", uiIcon: "bx bx-search",
enabled: notSearch && noSelectedNotes },
isHoisted ? null : { title: 'Hoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
!isHoisted || !isNotRoot ? null : { title: 'Unhoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
{ title: 'Edit branch prefix <kbd data-command="editBranchPrefix"></kbd>', command: "editBranchPrefix", uiIcon: "bx bx-empty",
isHoisted ? null : { title: `${t("tree-context-menu.hoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
!isHoisted || !isNotRoot ? null : { title: `${t("tree-context-menu.unhoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
{ title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`, command: "editBranchPrefix", uiIcon: "bx bx-empty",
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions },
{ title: "Advanced", uiIcon: "bx bx-empty", enabled: true, items: [
{ title: 'Expand subtree <kbd data-command="expandSubtree"></kbd>', command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ title: 'Sort by ... <kbd data-command="sortChildNotes"></kbd>', command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
{ title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
{ title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
{ title: 'Copy note path to clipboard', command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
{ title: t("tree-context-menu.advanced"), uiIcon: "bx bx-empty", enabled: true, items: [
{ title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`, command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
] },
{ title: "----" },
{ title: "Protect subtree", command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
{ title: "Unprotect subtree", command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
{ title: "----" },
{ title: 'Copy / clone <kbd data-command="copyNotesToClipboard"></kbd>', command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
{ title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
enabled: isNotRoot && !isHoisted },
{ title: 'Clone to ... <kbd data-command="cloneNotesTo"></kbd>', command: "cloneNotesTo", uiIcon: "bx bx-empty",
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-empty",
enabled: isNotRoot && !isHoisted },
{ title: 'Cut <kbd data-command="cutNotesToClipboard"></kbd>', command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
{ title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`, command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
enabled: isNotRoot && !isHoisted && parentNotSearch },
{ title: 'Move to ... <kbd data-command="moveNotesTo"></kbd>', command: "moveNotesTo", uiIcon: "bx bx-empty",
{ title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`, command: "moveNotesTo", uiIcon: "bx bx-empty",
enabled: isNotRoot && !isHoisted && parentNotSearch },
{ title: 'Paste into <kbd data-command="pasteNotesFromClipboard"></kbd>', command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
{ title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`, command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
{ title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
{ title: t("tree-context-menu.paste-after"), command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
{ title: `Duplicate subtree <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
{ title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions },
{ title: "----" },
{ title: "Export", command: "exportNote", uiIcon: "bx bx-empty",
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-empty",
enabled: notSearch && noSelectedNotes && notOptions },
{ title: "Import into note", command: "importIntoNote", uiIcon: "bx bx-empty",
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-empty",
enabled: notSearch && noSelectedNotes && notOptions },
{ title: "Apply bulk actions", command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
enabled: true }
].filter(row => row !== null);
}
@@ -135,7 +136,7 @@ export default class TreeContextMenu {
this.treeWidget.triggerCommand("openNewNoteSplit", {ntxId, notePath});
}
else if (command === 'convertNoteToAttachment') {
if (!await dialogService.confirm(`Are you sure you want to convert note selected notes into attachments of their parent notes?`)) {
if (!await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm"))) {
return;
}
@@ -153,7 +154,7 @@ export default class TreeContextMenu {
}
}
toastService.showMessage(`${converted} notes have been converted to attachments.`);
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
}
else if (command === 'copyNotePathToClipboard') {
navigator.clipboard.writeText('#' + notePath);

View File

@@ -5,6 +5,7 @@ import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js";
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import { t } from './i18n.js';
async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
branchIdsToMove = filterRootNote(branchIdsToMove);
@@ -13,7 +14,7 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
const beforeBranch = froca.getBranch(beforeBranchId);
if (['root', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(beforeBranch.noteId)) {
toastService.showError('Cannot move notes here.');
toastService.showError(t("branches.cannot-move-notes-here"));
return;
}
@@ -42,7 +43,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
];
if (forbiddenNoteIds.includes(afterNote.noteId)) {
toastService.showError('Cannot move notes here.');
toastService.showError(t("branches.cannot-move-notes-here"));
return;
}
@@ -62,7 +63,7 @@ async function moveToParentNote(branchIdsToMove, newParentBranchId) {
const newParentBranch = froca.getBranch(newParentBranchId);
if (newParentBranch.noteId === '_lbRoot') {
toastService.showError('Cannot move notes here.');
toastService.showError(t("branches.cannot-move-notes-here"));
return;
}
@@ -192,7 +193,7 @@ function filterRootNote(branchIds) {
function makeToast(id, message) {
return {
id: id,
title: "Delete status",
title: t("branches.delete-status"),
message: message,
icon: "trash"
};
@@ -207,9 +208,9 @@ ws.subscribeToMessages(async message => {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'taskProgressCount') {
toastService.showPersistent(makeToast(message.taskId, `Delete notes in progress: ${message.progressCount}`));
toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount })));
} else if (message.type === 'taskSucceeded') {
const toast = makeToast(message.taskId, "Delete finished successfully.");
const toast = makeToast(message.taskId, t("branches.delete-finished-successfully"));
toast.closeAfter = 5000;
toastService.showPersistent(toast);
@@ -225,9 +226,9 @@ ws.subscribeToMessages(async message => {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'taskProgressCount') {
toastService.showPersistent(makeToast(message.taskId, `Undeleting notes in progress: ${message.progressCount}`));
toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount })));
} else if (message.type === 'taskSucceeded') {
const toast = makeToast(message.taskId, "Undeleting notes finished successfully.");
const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully"));
toast.closeAfter = 5000;
toastService.showPersistent(toast);

View File

@@ -3,6 +3,7 @@ import server from "./server.js";
import toastService from "./toast.js";
import froca from "./froca.js";
import utils from "./utils.js";
import { t } from "./i18n.js";
async function getAndExecuteBundle(noteId, originEntity = null, script = null, params = null) {
const bundle = await server.post(`script/bundle/${noteId}`, {
@@ -75,9 +76,23 @@ async function getWidgetBundlesByParent() {
try {
widget = await executeBundle(bundle);
widgetsByParent.add(widget);
}
catch (e) {
if (widget) {
widget._noteId = bundle.noteId;
widgetsByParent.add(widget);
}
} catch (e) {
const noteId = bundle.noteId;
const note = await froca.getNote(noteId);
toastService.showPersistent({
title: t("toast.bundle-error.title"),
icon: "alert",
message: t("toast.bundle-error.message", {
id: noteId,
title: note.title,
message: e.message
})
});
logError("Widget initialization failed: ", e);
continue;
}

View File

@@ -3,6 +3,7 @@ import toastService from "./toast.js";
import froca from "./froca.js";
import linkService from "./link.js";
import utils from "./utils.js";
import { t } from "./i18n.js";
let clipboardBranchIds = [];
let clipboardMode = null;
@@ -78,7 +79,7 @@ async function copy(branchIds) {
clipboard.writeHTML(links.join(', '));
}
toastService.showMessage("Note(s) have been copied into clipboard.");
toastService.showMessage(t("clipboard.copied"));
}
function cut(branchIds) {
@@ -87,7 +88,7 @@ function cut(branchIds) {
if (clipboardBranchIds.length > 0) {
clipboardMode = 'cut';
toastService.showMessage("Note(s) have been cut into clipboard.");
toastService.showMessage(t("clipboard.cut"));
}
}

View File

@@ -10,6 +10,8 @@ import treeService from "./tree.js";
import FNote from "../entities/fnote.js";
import FAttachment from "../entities/fattachment.js";
import imageContextMenuService from "../menus/image_context_menu.js";
import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js";
import mime_types from "./mime_types.js";
let idCounter = 1;
@@ -105,16 +107,25 @@ async function renderText(note, $renderedContent) {
for (const el of referenceLinks) {
await linkService.loadReferenceLinkTitle($(el));
}
await applySyntaxHighlight($renderedContent);
} else {
await renderChildrenList($renderedContent, note);
}
}
/** @param {FNote} note */
/**
* Renders a code note, by displaying its content and applying syntax highlighting based on the selected MIME type.
*
* @param {FNote} note
*/
async function renderCode(note, $renderedContent) {
const blob = await note.getBlob();
$renderedContent.append($("<pre>").text(blob.content));
const $codeBlock = $("<code>");
$codeBlock.text(blob.content);
$renderedContent.append($("<pre>").append($codeBlock));
await applySingleBlockSyntaxHighlight($codeBlock, mime_types.normalizeMimeTypeForCKEditor(note.mime));
}
function renderImage(entity, $renderedContent, options = {}) {

View File

@@ -217,8 +217,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
*/
this.runOnBackend = async (func, params = []) => {
if (func?.constructor.name === "AsyncFunction" || func?.startsWith?.("async ")) {
toastService.showError("You're passing an async function to api.runOnBackend() which will likely not work as you intended. "
+ "Either make the function synchronous (by removing 'async' keyword), or use api.runAsyncOnBackendWithManualTransactionHandling()");
toastService.showError(t("frontend_script_api.async_warning"));
}
return await this.__runOnBackendInner(func, params, true);
@@ -240,8 +239,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
*/
this.runAsyncOnBackendWithManualTransactionHandling = async (func, params = []) => {
if (func?.constructor.name === "Function" || func?.startsWith?.("function")) {
toastService.showError("You're passing a synchronous function to api.runAsyncOnBackendWithManualTransactionHandling(), " +
"while you should likely use api.runOnBackend() instead.");
toastService.showError(t("frontend_script_api.sync_warning"));
}
return await this.__runOnBackendInner(func, params, false);

View File

@@ -26,21 +26,6 @@ function setupGlobs() {
// for CKEditor integration (button on block toolbar)
window.glob.importMarkdownInline = async () => appContext.triggerCommand("importMarkdownInline");
window.glob.SEARCH_HELP_TEXT = `
<strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="search.html">complete help on search</button>
<p>
<ul>
<li>Just enter any text for full text search</li>
<li><code>#abc</code> - returns notes with label abc</li>
<li><code>#year = 2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li>
<li><code>#rock #pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li>
<li><code>#rock or #pop</code> - only one of the labels must be present</li>
<li><code>#year &lt;= 2000</code> - numerical comparison (also &gt;, &gt;=, &lt;).</li>
<li><code>note.dateCreated >= MONTH-1</code> - notes created in the last month</li>
<li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li>
</ul>
</p>`;
window.onerror = function (msg, url, lineNo, columnNo, error) {
const string = msg.toLowerCase();
@@ -64,6 +49,28 @@ function setupGlobs() {
return false;
};
window.addEventListener("unhandledrejection", (e) => {
const string = e?.reason?.message?.toLowerCase();
let message = "Uncaught error: ";
if (string?.includes("script error")) {
message += 'No details available';
} else {
message += [
`Message: ${e.reason.message}`,
`Line: ${e.reason.lineNumber}`,
`Column: ${e.reason.columnNumber}`,
`Error object: ${JSON.stringify(e.reason)}`,
`Stack: ${e.reason && e.reason.stack}`
].join(', ');
}
ws.logError(message);
return false;
});
for (const appCssNoteId of glob.appCssNoteIds || []) {
libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`, false);
}

View File

@@ -2,6 +2,7 @@ import appContext from "../components/app_context.js";
import treeService from "./tree.js";
import dialogService from "./dialog.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
function getHoistedNoteId() {
const activeNoteContext = appContext.tabManager.getActiveContext();
@@ -53,7 +54,7 @@ async function checkNoteAccess(notePath, noteContext) {
const hoistedNote = await froca.getNote(hoistedNoteId);
if ((!hoistedNote.hasAncestor('_hidden') || resolvedNotePath.includes('_lbBookmarks'))
&& !await dialogService.confirm(`Requested note '${requestedNote.title}' is outside of hoisted note '${hoistedNote.title}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?`)) {
&& !await dialogService.confirm(t("hoisted_note.confirm_unhoisting", { requestedNote: requestedNote.title, hoistedNote: hoistedNote.title }))) {
return false;
}

View File

@@ -12,8 +12,9 @@ export async function initLocale() {
lng: locale,
fallbackLng: "en",
backend: {
loadPath: `/${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
}
loadPath: `${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
},
returnEmptyString: false
});
}

View File

@@ -8,9 +8,9 @@ function copyImageReferenceToClipboard($imageWrapper) {
const success = document.execCommand('copy');
if (success) {
toastService.showMessage("A reference to the image has been copied to clipboard. This can be pasted in any text note.");
toastService.showMessage(t("image.copied-to-clipboard"));
} else {
toastService.showAndLogError("Could not copy the image reference to clipboard.");
toastService.showAndLogError(t("image.cannot-copy"));
}
}
finally {

View File

@@ -36,7 +36,7 @@ export async function uploadFiles(entityType, parentNoteId, files, options) {
type: 'POST',
timeout: 60 * 60 * 1000,
error: function (xhr) {
toastService.showError(`Import failed: ${xhr.responseText}`);
toastService.showError(t("import.failed", { message: xhr.responseText }));
},
contentType: false, // NEEDED, DON'T REMOVE THIS
processData: false, // NEEDED, DON'T REMOVE THIS

View File

@@ -1,9 +1,12 @@
import mimeTypesService from "./mime_types.js";
import optionsService from "./options.js";
import { getStylesheetUrl } from "./syntax_highlight.js";
const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
const CODE_MIRROR = {
js: [
"libraries/codemirror/codemirror.js",
// "node_modules/codemirror/lib/codemirror.js",
"node_modules/codemirror/lib/codemirror.js",
"node_modules/codemirror/addon/display/placeholder.js",
"node_modules/codemirror/addon/edit/matchbrackets.js",
"node_modules/codemirror/addon/edit/matchtags.js",
@@ -85,18 +88,44 @@ const MIND_ELIXIR = {
]
};
const HIGHLIGHT_JS = {
js: () => {
const mimeTypes = mimeTypesService.getMimeTypes();
const scriptsToLoad = new Set();
scriptsToLoad.add("node_modules/@highlightjs/cdn-assets/highlight.min.js");
for (const mimeType of mimeTypes) {
if (mimeType.enabled && mimeType.highlightJs) {
scriptsToLoad.add(`node_modules/@highlightjs/cdn-assets/languages/${mimeType.highlightJs}.min.js`);
}
}
const currentTheme = optionsService.get("codeBlockTheme");
loadHighlightingTheme(currentTheme);
return Array.from(scriptsToLoad);
}
};
async function requireLibrary(library) {
if (library.css) {
library.css.map(cssUrl => requireCss(cssUrl));
}
if (library.js) {
for (const scriptUrl of library.js) {
for (const scriptUrl of unwrapValue(library.js)) {
await requireScript(scriptUrl);
}
}
}
function unwrapValue(value) {
if (typeof value === "function") {
return value();
}
return value;
}
// we save the promises in case of the same script being required concurrently multiple times
const loadedScriptPromises = {};
@@ -128,9 +157,36 @@ async function requireCss(url, prependAssetPath = true) {
}
}
let highlightingThemeEl = null;
function loadHighlightingTheme(theme) {
if (!theme) {
return;
}
if (theme === "none") {
// Deactivate the theme.
if (highlightingThemeEl) {
highlightingThemeEl.remove();
highlightingThemeEl = null;
}
return;
}
if (!highlightingThemeEl) {
highlightingThemeEl = $(`<link rel="stylesheet" type="text/css" />`);
$("head").append(highlightingThemeEl);
}
const url = getStylesheetUrl(theme);
if (url) {
highlightingThemeEl.attr("href", url);
}
}
export default {
requireCss,
requireLibrary,
loadHighlightingTheme,
CKEDITOR,
CODE_MIRROR,
ESLINT,
@@ -144,5 +200,6 @@ export default {
EXCALIDRAW,
MARKJS,
I18NEXT,
MIND_ELIXIR
MIND_ELIXIR,
HIGHLIGHT_JS
}

View File

@@ -1,162 +1,167 @@
import options from "./options.js";
/**
* A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics.
*/
const MIME_TYPE_AUTO = "text-x-trilium-auto";
const MIME_TYPES_DICT = [
{ default: true, title: "Plain text", mime: "text/plain" },
{ default: true, title: "Plain text", mime: "text/plain", highlightJs: "plaintext" },
{ title: "APL", mime: "text/apl" },
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
{ title: "ASP.NET", mime: "application/x-aspx" },
{ title: "Asterisk", mime: "text/x-asterisk" },
{ title: "Brainfuck", mime: "text/x-brainfuck" },
{ default: true, title: "C", mime: "text/x-csrc" },
{ default: true, title: "C#", mime: "text/x-csharp" },
{ default: true, title: "C++", mime: "text/x-c++src" },
{ title: "Clojure", mime: "text/x-clojure" },
{ title: "Brainfuck", mime: "text/x-brainfuck", highlightJs: "brainfuck" },
{ default: true, title: "C", mime: "text/x-csrc", highlightJs: "c" },
{ default: true, title: "C#", mime: "text/x-csharp", highlightJs: "csharp" },
{ default: true, title: "C++", mime: "text/x-c++src", highlightJs: "cpp" },
{ title: "Clojure", mime: "text/x-clojure", highlightJs: "clojure" },
{ title: "ClojureScript", mime: "text/x-clojurescript" },
{ title: "Closure Stylesheets (GSS)", mime: "text/x-gss" },
{ title: "CMake", mime: "text/x-cmake" },
{ title: "CMake", mime: "text/x-cmake", highlightJs: "cmake" },
{ title: "Cobol", mime: "text/x-cobol" },
{ title: "CoffeeScript", mime: "text/coffeescript" },
{ title: "Common Lisp", mime: "text/x-common-lisp" },
{ title: "CoffeeScript", mime: "text/coffeescript", highlightJs: "coffeescript" },
{ title: "Common Lisp", mime: "text/x-common-lisp", highlightJs: "lisp" },
{ title: "CQL", mime: "text/x-cassandra" },
{ title: "Crystal", mime: "text/x-crystal" },
{ default: true, title: "CSS", mime: "text/css" },
{ title: "Crystal", mime: "text/x-crystal", highlightJs: "crystal" },
{ default: true, title: "CSS", mime: "text/css", highlightJs: "css" },
{ title: "Cypher", mime: "application/x-cypher-query" },
{ title: "Cython", mime: "text/x-cython" },
{ title: "D", mime: "text/x-d" },
{ title: "Dart", mime: "application/dart" },
{ title: "diff", mime: "text/x-diff" },
{ title: "Django", mime: "text/x-django" },
{ title: "Dockerfile", mime: "text/x-dockerfile" },
{ title: "D", mime: "text/x-d", highlightJs: "d" },
{ title: "Dart", mime: "application/dart", highlightJs: "dart" },
{ title: "diff", mime: "text/x-diff", highlightJs: "diff" },
{ title: "Django", mime: "text/x-django", highlightJs: "django" },
{ title: "Dockerfile", mime: "text/x-dockerfile", highlightJs: "dockerfile" },
{ title: "DTD", mime: "application/xml-dtd" },
{ title: "Dylan", mime: "text/x-dylan" },
{ title: "EBNF", mime: "text/x-ebnf" },
{ title: "EBNF", mime: "text/x-ebnf", highlightJs: "ebnf" },
{ title: "ECL", mime: "text/x-ecl" },
{ title: "edn", mime: "application/edn" },
{ title: "Eiffel", mime: "text/x-eiffel" },
{ title: "Elm", mime: "text/x-elm" },
{ title: "Elm", mime: "text/x-elm", highlightJs: "elm" },
{ title: "Embedded Javascript", mime: "application/x-ejs" },
{ title: "Embedded Ruby", mime: "application/x-erb" },
{ title: "Erlang", mime: "text/x-erlang" },
{ title: "Embedded Ruby", mime: "application/x-erb", highlightJs: "erb" },
{ title: "Erlang", mime: "text/x-erlang", highlightJs: "erlang" },
{ title: "Esper", mime: "text/x-esper" },
{ title: "F#", mime: "text/x-fsharp" },
{ title: "F#", mime: "text/x-fsharp", highlightJs: "fsharp" },
{ title: "Factor", mime: "text/x-factor" },
{ title: "FCL", mime: "text/x-fcl" },
{ title: "Forth", mime: "text/x-forth" },
{ title: "Fortran", mime: "text/x-fortran" },
{ title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" },
{ title: "Gas", mime: "text/x-gas" },
{ title: "Gherkin", mime: "text/x-feature" },
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm" },
{ default: true, title: "Go", mime: "text/x-go" },
{ default: true, title: "Groovy", mime: "text/x-groovy" },
{ title: "HAML", mime: "text/x-haml" },
{ default: true, title: "Haskell", mime: "text/x-haskell" },
{ title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" },
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" },
{ default: true, title: "Go", mime: "text/x-go", highlightJs: "go" },
{ default: true, title: "Groovy", mime: "text/x-groovy", highlightJs: "groovy" },
{ title: "HAML", mime: "text/x-haml", highlightJs: "haml" },
{ default: true, title: "Haskell", mime: "text/x-haskell", highlightJs: "haskell" },
{ title: "Haskell (Literate)", mime: "text/x-literate-haskell" },
{ title: "Haxe", mime: "text/x-haxe" },
{ default: true, title: "HTML", mime: "text/html" },
{ default: true, title: "HTTP", mime: "message/http" },
{ title: "Haxe", mime: "text/x-haxe", highlightJs: "haxe" },
{ default: true, title: "HTML", mime: "text/html", highlightJs: "xml" },
{ default: true, title: "HTTP", mime: "message/http", highlightJs: "http" },
{ title: "HXML", mime: "text/x-hxml" },
{ title: "IDL", mime: "text/x-idl" },
{ default: true, title: "Java", mime: "text/x-java" },
{ title: "Java Server Pages", mime: "application/x-jsp" },
{ default: true, title: "Java", mime: "text/x-java", highlightJs: "java" },
{ title: "Java Server Pages", mime: "application/x-jsp", highlightJs: "java" },
{ title: "Jinja2", mime: "text/jinja2" },
{ default: true, title: "JS backend", mime: "application/javascript;env=backend" },
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend" },
{ default: true, title: "JSON", mime: "application/json" },
{ title: "JSON-LD", mime: "application/ld+json" },
{ title: "JSX", mime: "text/jsx" },
{ title: "Julia", mime: "text/x-julia" },
{ default: true, title: "Kotlin", mime: "text/x-kotlin" },
{ title: "LaTeX", mime: "text/x-latex" },
{ title: "LESS", mime: "text/x-less" },
{ title: "LiveScript", mime: "text/x-livescript" },
{ title: "Lua", mime: "text/x-lua" },
{ title: "MariaDB SQL", mime: "text/x-mariadb" },
{ default: true, title: "Markdown", mime: "text/x-markdown" },
{ title: "Mathematica", mime: "text/x-mathematica" },
{ default: true, title: "JS backend", mime: "application/javascript;env=backend", highlightJs: "javascript" },
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend", highlightJs: "javascript" },
{ default: true, title: "JSON", mime: "application/json", highlightJs: "json" },
{ title: "JSON-LD", mime: "application/ld+json", highlightJs: "json" },
{ title: "JSX", mime: "text/jsx", highlightJs: "javascript" },
{ title: "Julia", mime: "text/x-julia", highlightJs: "julia" },
{ default: true, title: "Kotlin", mime: "text/x-kotlin", highlightJs: "kotlin" },
{ title: "LaTeX", mime: "text/x-latex", highlightJs: "latex" },
{ title: "LESS", mime: "text/x-less", highlightJs: "less" },
{ title: "LiveScript", mime: "text/x-livescript", highlightJs: "livescript" },
{ title: "Lua", mime: "text/x-lua", highlightJs: "lua" },
{ title: "MariaDB SQL", mime: "text/x-mariadb", highlightJs: "sql" },
{ default: true, title: "Markdown", mime: "text/x-markdown", highlightJs: "markdown" },
{ title: "Mathematica", mime: "text/x-mathematica", highlightJs: "mathematica" },
{ title: "mbox", mime: "application/mbox" },
{ title: "mIRC", mime: "text/mirc" },
{ title: "Modelica", mime: "text/x-modelica" },
{ title: "MS SQL", mime: "text/x-mssql" },
{ title: "MS SQL", mime: "text/x-mssql", highlightJs: "sql" },
{ title: "mscgen", mime: "text/x-mscgen" },
{ title: "msgenny", mime: "text/x-msgenny" },
{ title: "MUMPS", mime: "text/x-mumps" },
{ title: "MySQL", mime: "text/x-mysql" },
{ title: "Nginx", mime: "text/x-nginx-conf" },
{ title: "NSIS", mime: "text/x-nsis" },
{ title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" },
{ title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" },
{ title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" },
{ title: "NTriples", mime: "application/n-triples" },
{ title: "Objective-C", mime: "text/x-objectivec" },
{ title: "OCaml", mime: "text/x-ocaml" },
{ title: "Objective-C", mime: "text/x-objectivec", highlightJs: "objectivec" },
{ title: "OCaml", mime: "text/x-ocaml", highlightJs: "ocaml" },
{ title: "Octave", mime: "text/x-octave" },
{ title: "Oz", mime: "text/x-oz" },
{ title: "Pascal", mime: "text/x-pascal" },
{ title: "Pascal", mime: "text/x-pascal", highlightJs: "delphi" },
{ title: "PEG.js", mime: "null" },
{ default: true, title: "Perl", mime: "text/x-perl" },
{ title: "PGP", mime: "application/pgp" },
{ default: true, title: "PHP", mime: "text/x-php" },
{ title: "Pig", mime: "text/x-pig" },
{ title: "PLSQL", mime: "text/x-plsql" },
{ title: "PostgreSQL", mime: "text/x-pgsql" },
{ title: "PowerShell", mime: "application/x-powershell" },
{ title: "Properties files", mime: "text/x-properties" },
{ title: "ProtoBuf", mime: "text/x-protobuf" },
{ title: "PLSQL", mime: "text/x-plsql", highlightJs: "sql" },
{ title: "PostgreSQL", mime: "text/x-pgsql", highlightJs: "pgsql" },
{ title: "PowerShell", mime: "application/x-powershell", highlightJs: "powershell" },
{ title: "Properties files", mime: "text/x-properties", highlightJs: "properties" },
{ title: "ProtoBuf", mime: "text/x-protobuf", highlightJs: "protobuf" },
{ title: "Pug", mime: "text/x-pug" },
{ title: "Puppet", mime: "text/x-puppet" },
{ default: true, title: "Python", mime: "text/x-python" },
{ title: "Q", mime: "text/x-q" },
{ title: "R", mime: "text/x-rsrc" },
{ title: "Puppet", mime: "text/x-puppet", highlightJs: "puppet" },
{ default: true, title: "Python", mime: "text/x-python", highlightJs: "python" },
{ title: "Q", mime: "text/x-q", highlightJs: "q" },
{ title: "R", mime: "text/x-rsrc", highlightJs: "r" },
{ title: "reStructuredText", mime: "text/x-rst" },
{ title: "RPM Changes", mime: "text/x-rpm-changes" },
{ title: "RPM Spec", mime: "text/x-rpm-spec" },
{ default: true, title: "Ruby", mime: "text/x-ruby" },
{ title: "Rust", mime: "text/x-rustsrc" },
{ title: "SAS", mime: "text/x-sas" },
{ default: true, title: "Ruby", mime: "text/x-ruby", highlightJs: "ruby" },
{ title: "Rust", mime: "text/x-rustsrc", highlightJs: "rust" },
{ title: "SAS", mime: "text/x-sas", highlightJs: "sas" },
{ title: "Sass", mime: "text/x-sass" },
{ title: "Scala", mime: "text/x-scala" },
{ title: "Scheme", mime: "text/x-scheme" },
{ title: "SCSS", mime: "text/x-scss" },
{ default: true, title: "Shell (bash)", mime: "text/x-sh" },
{ title: "SCSS", mime: "text/x-scss", highlightJs: "scss" },
{ default: true, title: "Shell (bash)", mime: "text/x-sh", highlightJs: "shell" },
{ title: "Sieve", mime: "application/sieve" },
{ title: "Slim", mime: "text/x-slim" },
{ title: "Smalltalk", mime: "text/x-stsrc" },
{ title: "Smalltalk", mime: "text/x-stsrc", highlightJs: "smalltalk" },
{ title: "Smarty", mime: "text/x-smarty" },
{ title: "SML", mime: "text/x-sml" },
{ title: "SML", mime: "text/x-sml", highlightJs: "sml" },
{ title: "Solr", mime: "text/x-solr" },
{ title: "Soy", mime: "text/x-soy" },
{ title: "SPARQL", mime: "application/sparql-query" },
{ title: "Spreadsheet", mime: "text/x-spreadsheet" },
{ default: true, title: "SQL", mime: "text/x-sql" },
{ title: "SQLite", mime: "text/x-sqlite" },
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium" },
{ default: true, title: "SQL", mime: "text/x-sql", highlightJs: "sql" },
{ title: "SQLite", mime: "text/x-sqlite", highlightJs: "sql" },
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium", highlightJs: "sql" },
{ title: "Squirrel", mime: "text/x-squirrel" },
{ title: "sTeX", mime: "text/x-stex" },
{ title: "Stylus", mime: "text/x-styl" },
{ title: "Stylus", mime: "text/x-styl", highlightJs: "stylus" },
{ default: true, title: "Swift", mime: "text/x-swift" },
{ title: "SystemVerilog", mime: "text/x-systemverilog" },
{ title: "Tcl", mime: "text/x-tcl" },
{ title: "Tcl", mime: "text/x-tcl", highlightJs: "tcl" },
{ title: "Textile", mime: "text/x-textile" },
{ title: "TiddlyWiki ", mime: "text/x-tiddlywiki" },
{ title: "Tiki wiki", mime: "text/tiki" },
{ title: "TOML", mime: "text/x-toml" },
{ title: "TOML", mime: "text/x-toml", highlightJs: "ini" },
{ title: "Tornado", mime: "text/x-tornado" },
{ title: "troff", mime: "text/troff" },
{ title: "TTCN", mime: "text/x-ttcn" },
{ title: "TTCN_CFG", mime: "text/x-ttcn-cfg" },
{ title: "Turtle", mime: "text/turtle" },
{ title: "Twig", mime: "text/x-twig" },
{ title: "TypeScript", mime: "application/typescript" },
{ title: "Twig", mime: "text/x-twig", highlightJs: "twig" },
{ title: "TypeScript", mime: "application/typescript", highlightJs: "typescript" },
{ title: "TypeScript-JSX", mime: "text/typescript-jsx" },
{ title: "VB.NET", mime: "text/x-vb" },
{ title: "VBScript", mime: "text/vbscript" },
{ title: "VB.NET", mime: "text/x-vb", highlightJs: "vbnet" },
{ title: "VBScript", mime: "text/vbscript", highlightJs: "vbscript" },
{ title: "Velocity", mime: "text/velocity" },
{ title: "Verilog", mime: "text/x-verilog" },
{ title: "VHDL", mime: "text/x-vhdl" },
{ title: "Verilog", mime: "text/x-verilog", highlightJs: "verilog" },
{ title: "VHDL", mime: "text/x-vhdl", highlightJs: "vhdl" },
{ title: "Vue.js Component", mime: "text/x-vue" },
{ title: "Web IDL", mime: "text/x-webidl" },
{ default: true, title: "XML", mime: "text/xml" },
{ title: "XQuery", mime: "application/xquery" },
{ default: true, title: "XML", mime: "text/xml", highlightJs: "xml" },
{ title: "XQuery", mime: "application/xquery", highlightJs: "xquery" },
{ title: "xu", mime: "text/x-xu" },
{ title: "Yacas", mime: "text/x-yacas" },
{ default: true, title: "YAML", mime: "text/x-yaml" },
{ default: true, title: "YAML", mime: "text/x-yaml", highlightJs: "yaml" },
{ title: "Z80", mime: "text/x-z80" }
];
@@ -173,7 +178,7 @@ function loadMimeTypes() {
}
}
async function getMimeTypes() {
function getMimeTypes() {
if (mimeTypes === null) {
loadMimeTypes();
}
@@ -181,7 +186,46 @@ async function getMimeTypes() {
return mimeTypes;
}
export default {
getMimeTypes,
loadMimeTypes
let mimeToHighlightJsMapping = null;
/**
* Obtains the corresponding language tag for highlight.js for a given MIME type.
*
* The mapping is built the first time this method is built and then the results are cached for better performance.
*
* @param {string} mimeType The MIME type of the code block, in the CKEditor-normalized format (e.g. `text-c-src` instead of `text/c-src`).
* @returns the corresponding highlight.js tag, for example `c` for `text-c-src`.
*/
function getHighlightJsNameForMime(mimeType) {
if (!mimeToHighlightJsMapping) {
const mimeTypes = getMimeTypes();
mimeToHighlightJsMapping = {};
for (const mimeType of mimeTypes) {
// The mime stored by CKEditor is text-x-csrc instead of text/x-csrc so we keep this format for faster lookup.
const normalizedMime = normalizeMimeTypeForCKEditor(mimeType.mime);
mimeToHighlightJsMapping[normalizedMime] = mimeType.highlightJs;
}
}
return mimeToHighlightJsMapping[mimeType];
}
/**
* Given a MIME type in the usual format (e.g. `text/csrc`), it returns a MIME type that can be passed down to the CKEditor
* code plugin.
*
* @param {string} mimeType The MIME type to normalize, in the usual format (e.g. `text/c-src`).
* @returns the normalized MIME type (e.g. `text-c-src`).
*/
function normalizeMimeTypeForCKEditor(mimeType) {
return mimeType.toLowerCase()
.replace(/[\W_]+/g,"-");
}
export default {
MIME_TYPE_AUTO,
getMimeTypes,
loadMimeTypes,
getHighlightJsNameForMime,
normalizeMimeTypeForCKEditor
}

View File

@@ -105,28 +105,23 @@ function initNoteAutocomplete($el, options) {
$el.addClass("note-autocomplete-input");
const $clearTextButton = $("<a>")
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
.prop("title", "Clear text field");
const $clearTextButton = $("<button>")
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
.prop("title", "Clear text field");
const $showRecentNotesButton = $("<a>")
.addClass("input-group-text show-recent-notes-button bx bx-time")
.prop("title", "Show recent notes");
const $showRecentNotesButton = $("<button>")
.addClass("input-group-text show-recent-notes-button bx bx-time")
.prop("title", "Show recent notes");
const $goToSelectedNoteButton = $("<a>")
const $goToSelectedNoteButton = $("<button>")
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
const $sideButtons = $("<div>")
.addClass("input-group-append")
.append($clearTextButton)
.append($showRecentNotesButton);
$el.after($clearTextButton).after($showRecentNotesButton);
if (!options.hideGoToSelectedNoteButton) {
$sideButtons.append($goToSelectedNoteButton);
$el.after($goToSelectedNoteButton);
}
$el.after($sideButtons);
$clearTextButton.on('click', () => clearText($el));
$showRecentNotesButton.on('click', e => {
@@ -180,13 +175,13 @@ function initNoteAutocomplete($el, options) {
}
if (suggestion.action === 'create-note') {
const {success, noteType, templateNoteId} = await noteCreateService.chooseNoteType();
const { success, noteType, templateNoteId } = await noteCreateService.chooseNoteType();
if (!success) {
return;
}
const {note} = await noteCreateService.createNote(suggestion.parentNoteId, {
const { note } = await noteCreateService.createNote(suggestion.parentNoteId, {
title: suggestion.noteTitle,
activate: false,
type: noteType,

View File

@@ -5,6 +5,7 @@ import ws from "./ws.js";
import froca from "./froca.js";
import treeService from "./tree.js";
import toastService from "./toast.js";
import { t } from "./i18n.js";
async function createNote(parentNotePath, options = {}) {
options = Object.assign({
@@ -119,7 +120,7 @@ async function duplicateSubtree(noteId, parentNotePath) {
activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`);
const origNote = await froca.getNote(noteId);
toastService.showMessage(`Note "${origNote.title}" has been duplicated`);
toastService.showMessage(t("note_create.duplicated", { title: origNote.title }));
}
export default {

View File

@@ -366,12 +366,13 @@ class NoteListRenderer {
separateWordSearch: false,
caseSensitive: false
});
}
}
$content.append($renderedContent);
$content.addClass(`type-${type}`);
} catch (e) {
console.log(`Caught error while rendering note '${note.noteId}' of type '${note.type}': ${e.message}, stack: ${e.stack}`);
console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`);
console.error(e);
$content.append("rendering error");
}

View File

@@ -1,19 +1,20 @@
import server from "./server.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
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" },
{ 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" },
{ title: "Mind Map", command, type: "mindMap", uiIcon: "bx bx-sitemap" }
{ title: t("note_types.text"), command: command, type: "text", uiIcon: "bx bx-note" },
{ title: t("note_types.code"), command: command, type: "code", uiIcon: "bx bx-code" },
{ title: t("note_types.saved-search"), command: command, type: "search", uiIcon: "bx bx-file-find" },
{ title: t("note_types.relation-map"), command: command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.render-note"), command: command, type: "render", uiIcon: "bx bx-extension" },
{ title: t("note_types.book"), command: command, type: "book", uiIcon: "bx bx-book" },
{ title: t("note_types.mermaid-diagram"), command: command, type: "mermaid", uiIcon: "bx bx-selection" },
{ title: t("note_types.canvas"), command: command, type: "canvas", uiIcon: "bx bx-pen" },
{ title: t("note_types.web-view"), command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" }
];
const templateNoteIds = await server.get("search-templates");

View File

@@ -166,6 +166,23 @@ function getHost() {
return `${url.protocol}//${url.hostname}:${url.port}`;
}
async function openDirectory(directory) {
try {
if (utils.isElectron()) {
const electron = utils.dynamicRequire('electron');
const res = await electron.shell.openPath(directory);
if (res) {
console.error('Failed to open directory:', res);
}
} else {
console.error('Not running in an Electron environment.');
}
} catch (err) {
// Handle file system errors (e.g. path does not exist or is inaccessible)
console.error('Error:', err.message);
}
}
export default {
download,
downloadFileNote,
@@ -176,4 +193,5 @@ export default {
openAttachmentExternally,
openNoteCustom,
openAttachmentCustom,
openDirectory
}

View File

@@ -6,6 +6,7 @@ import appContext from "../components/app_context.js";
import froca from "./froca.js";
import utils from "./utils.js";
import options from "./options.js";
import { t } from './i18n.js';
let protectedSessionDeferred = null;
@@ -50,7 +51,7 @@ async function setupProtectedSession(password) {
const response = await server.post('login/protected', { password: password });
if (!response.success) {
toastService.showError("Wrong password.", 3000);
toastService.showError(t("protected_session.wrong_password"), 3000);
return;
}
@@ -72,7 +73,7 @@ ws.subscribeToMessages(async message => {
protectedSessionDeferred = null;
}
toastService.showMessage("Protected session has been started.");
toastService.showMessage(t("protected_session.started"));
}
else if (message.type === 'protectedSessionLogout') {
utils.reloadFrontendApp(`Protected session logout`);
@@ -85,10 +86,10 @@ async function protectNote(noteId, protect, includingSubtree) {
await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`);
}
function makeToast(message, protectingLabel, text) {
function makeToast(message, title, text) {
return {
id: message.taskId,
title: `${protectingLabel} status`,
title,
message: text,
icon: message.data.protect ? "check-shield" : "shield"
};
@@ -99,15 +100,19 @@ ws.subscribeToMessages(async message => {
return;
}
const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting";
const isProtecting = message.data.protect;
const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title");
if (message.type === 'taskError') {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'taskProgressCount') {
toastService.showPersistent(makeToast(message, protectingLabel,`${protectingLabel} in progress: ${message.progressCount}`));
const count = message.progressCount;
const text = ( isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count }));
toastService.showPersistent(makeToast(message, title, text));
} else if (message.type === 'taskSucceeded') {
const toast = makeToast(message, protectingLabel, `${protectingLabel} finished successfully.`);
const text = (isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully"))
const toast = makeToast(message, title, text);
toast.closeAfter = 3000;
toastService.showPersistent(toast);

View File

@@ -1,3 +1,4 @@
import { t } from './i18n.js';
import server from './server.js';
import toastService from "./toast.js";
@@ -5,7 +6,7 @@ async function syncNow(ignoreNotConfigured = false) {
const result = await server.post('sync/now');
if (result.success) {
toastService.showMessage("Sync finished successfully.");
toastService.showMessage(t("sync.finished-successfully"));
}
else {
if (result.message.length > 200) {
@@ -13,7 +14,7 @@ async function syncNow(ignoreNotConfigured = false) {
}
if (!ignoreNotConfigured || result.errorCode !== 'NOT_CONFIGURED') {
toastService.showError(`Sync failed: ${result.message}`);
toastService.showError(t("sync.failed", { message: result.message }));
}
}
}

View File

@@ -0,0 +1,94 @@
import library_loader from "./library_loader.js";
import mime_types from "./mime_types.js";
import options from "./options.js";
export function getStylesheetUrl(theme) {
if (!theme) {
return null;
}
const defaultPrefix = "default:";
if (theme.startsWith(defaultPrefix)) {
return `${window.glob.assetPath}/node_modules/@highlightjs/cdn-assets/styles/${theme.substr(defaultPrefix.length)}.min.css`;
}
return null;
}
/**
* Identifies all the code blocks (as `pre code`) under the specified hierarchy and uses the highlight.js library to obtain the highlighted text which is then applied on to the code blocks.
*
* @param $container the container under which to look for code blocks and to apply syntax highlighting to them.
*/
export async function applySyntaxHighlight($container) {
if (!isSyntaxHighlightEnabled()) {
return;
}
const codeBlocks = $container.find("pre code");
for (const codeBlock of codeBlocks) {
const normalizedMimeType = extractLanguageFromClassList(codeBlock);
if (!normalizedMimeType) {
continue;
}
applySingleBlockSyntaxHighlight($(codeBlock, normalizedMimeType));
}
}
/**
* Applies syntax highlight to the given code block (assumed to be <pre><code>), using highlight.js.
*
* @param {*} $codeBlock
* @param {*} normalizedMimeType
*/
export async function applySingleBlockSyntaxHighlight($codeBlock, normalizedMimeType) {
$codeBlock.parent().toggleClass("hljs");
const text = $codeBlock.text();
if (!window.hljs) {
await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
}
let highlightedText = null;
if (normalizedMimeType === mime_types.MIME_TYPE_AUTO) {
highlightedText = hljs.highlightAuto(text);
} else if (normalizedMimeType) {
const language = mime_types.getHighlightJsNameForMime(normalizedMimeType);
if (language) {
highlightedText = hljs.highlight(text, { language });
} else {
console.warn(`Unknown mime type: ${normalizedMimeType}.`);
}
}
if (highlightedText) {
$codeBlock.html(highlightedText.value);
}
}
/**
* Indicates whether syntax highlighting should be enabled for code blocks, by querying the value of the `codeblockTheme` option.
* @returns whether syntax highlighting should be enabled for code blocks.
*/
export function isSyntaxHighlightEnabled() {
const theme = options.get("codeBlockTheme");
return theme && theme !== "none";
}
/**
* Given a HTML element, tries to extract the `language-` class name out of it.
*
* @param {string} el the HTML element from which to extract the language tag.
* @returns the normalized MIME type (e.g. `text-css` instead of `language-text-css`).
*/
function extractLanguageFromClassList(el) {
const prefix = "language-";
for (const className of el.classList) {
if (className.startsWith(prefix)) {
return className.substr(prefix.length);
}
}
return null;
}

View File

@@ -2,18 +2,21 @@ import ws from "./ws.js";
import utils from "./utils.js";
function toast(options) {
const $toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto"><span class="bx bx-${options.icon}"></span> <span class="toast-title"></span></strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body"></div>
</div>`);
const $toast = $(
`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">
<span class="bx bx-${options.icon}"></span>
<span class="toast-title"></span>
</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body"></div>
</div>`
);
$toast.find('.toast-title').text(options.title);
$toast.find('.toast-body').text(options.message);
$toast.find('.toast-body').html(options.message);
if (options.id) {
$toast.attr("id", `toast-${options.id}`);

View File

@@ -201,7 +201,7 @@ function getMimeTypeClass(mime) {
function closeActiveDialog() {
if (glob.activeDialog) {
glob.activeDialog.modal('hide');
bootstrap.Modal.getOrCreateInstance(glob.activeDialog).hide();
glob.activeDialog = null;
}
}
@@ -245,8 +245,7 @@ async function openDialog($dialog, closeActDialog = true) {
}
saveFocusedElement();
$dialog.modal();
bootstrap.Modal.getOrCreateInstance($dialog).show();
$dialog.on('hidden.bs.modal', () => {
$(".aa-input").autocomplete("close");
@@ -528,6 +527,58 @@ function downloadSvg(nameWithoutExtension, svgContent) {
document.body.removeChild(element);
}
/**
* Compares two semantic version strings.
* Returns:
* 1 if v1 is greater than v2
* 0 if v1 is equal to v2
* -1 if v1 is less than v2
*
* @param {string} v1 First version string
* @param {string} v2 Second version string
* @returns {number}
*/
function compareVersions(v1, v2) {
// Remove 'v' prefix and everything after dash if present
v1 = v1.replace(/^v/, '').split('-')[0];
v2 = v2.replace(/^v/, '').split('-')[0];
const v1parts = v1.split('.').map(Number);
const v2parts = v2.split('.').map(Number);
// Pad shorter version with zeros
while (v1parts.length < 3) v1parts.push(0);
while (v2parts.length < 3) v2parts.push(0);
// Compare major version
if (v1parts[0] !== v2parts[0]) {
return v1parts[0] > v2parts[0] ? 1 : -1;
}
// Compare minor version
if (v1parts[1] !== v2parts[1]) {
return v1parts[1] > v2parts[1] ? 1 : -1;
}
// Compare patch version
if (v1parts[2] !== v2parts[2]) {
return v1parts[2] > v2parts[2] ? 1 : -1;
}
return 0;
}
/**
* Compares two semantic version strings and returns `true` if the latest version is greater than the current version.
* @param {string} latestVersion
* @param {string} currentVersion
* @returns {boolean}
*/
function isUpdateAvailable(latestVersion, currentVersion) {
return compareVersions(latestVersion, currentVersion) > 0;
}
export default {
reloadFrontendApp,
parseDate,
@@ -568,5 +619,7 @@ export default {
areObjectsEqual,
copyHtmlToClipboard,
createImageSrcUrl,
downloadSvg
downloadSvg,
compareVersions,
isUpdateAvailable
};

View File

@@ -114,10 +114,10 @@ async function handleMessage(event) {
await executeFrontendUpdate(message.data.entityChanges);
}
else if (message.type === 'sync-hash-check-failed') {
toastService.showError("Sync check failed!", 60000);
toastService.showError(t("ws.sync-check-failed"), 60000);
}
else if (message.type === 'consistency-checks-failed') {
toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000);
toastService.showError(t("ws.consistency-checks-failed"), 50 * 60000);
}
else if (message.type === 'api-log-messages') {
appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages});
@@ -189,7 +189,7 @@ async function consumeFrontendUpdateData() {
else {
console.log("nonProcessedEntityChanges causing the timeout", nonProcessedEntityChanges);
toastService.showError(`Encountered error "${e.message}", check out the console.`);
toastService.showError(t("ws.encountered-error", { message: e.message }));
}
}

View File

@@ -97,7 +97,7 @@ const TPL = `
<tr class="attr-row-promoted"
title="${t('attribute_detail.promoted_title')}">
<th>${t('attribute_detail.promoted')}</th>
<td><input type="checkbox" class="attr-input-promoted form-control form-control-sm" /></td>
<td><input type="checkbox" class="attr-input-promoted form-check" /></td>
</tr>
<tr class="attr-row-promoted-alias">
<th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th>
@@ -134,9 +134,7 @@ const TPL = `
<td>
<div class="input-group">
<input type="number" class="form-control attr-input-number-precision" style="text-align: right">
<div class="input-group-append">
<span class="input-group-text">${t('attribute_detail.digits')}</span>
</div>
<span class="input-group-text">${t('attribute_detail.digits')}</span>
</div>
</td>
</tr>
@@ -150,7 +148,7 @@ const TPL = `
</tr>
<tr title="${t('attribute_detail.inheritable_title')}">
<th>${t('attribute_detail.inheritable')}</th>
<td><input type="checkbox" class="attr-input-inheritable form-control form-control-sm" /></td>
<td><input type="checkbox" class="attr-input-inheritable form-check" /></td>
</tr>
</table>
@@ -349,7 +347,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
this.$inputTargetNote = this.$widget.find('.attr-input-target-note');
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, {allowCreatingNotes: true})
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, { allowCreatingNotes: true })
.on('autocomplete:noteselected', (event, suggestion, dataset) => {
if (!suggestion.notePath) {
return false;
@@ -403,7 +401,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
});
}
async showAttributeDetail({allAttributes, attribute, isOwned, x, y, focus}) {
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus }) {
if (!attribute) {
this.hide();
@@ -545,7 +543,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
}
return {left, right};
return { left, right };
}
async saveAndClose() {
@@ -589,19 +587,19 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
async updateRelatedNotes() {
let {results, count} = await server.post('search-related', this.attribute);
let { results, count } = await server.post('search-related', this.attribute);
for (const res of results) {
res.noteId = res.notePathArray[res.notePathArray.length - 1];
}
results = results.filter(({noteId}) => noteId !== this.noteId);
results = results.filter(({ noteId }) => noteId !== this.noteId);
if (results.length === 0) {
this.$relatedNotesContainer.hide();
} else {
this.$relatedNotesContainer.show();
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", {attributeType: this.attribute.type, attributeName: this.attribute.name}));
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", { attributeType: this.attribute.type, attributeName: this.attribute.name }));
this.$relatedNotesList.empty();
@@ -611,7 +609,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
for (const note of displayedNotes) {
const notePath = note.getBestNotePathString(hoistedNoteId);
const $noteLink = await linkService.createLink(notePath, {showNotePath: true});
const $noteLink = await linkService.createLink(notePath, { showNotePath: true });
this.$relatedNotesList.append(
$("<li>").append($noteLink)
@@ -619,7 +617,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
if (results.length > DISPLAYED_NOTES) {
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", {count: count - DISPLAYED_NOTES}));
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", { count: count - DISPLAYED_NOTES }));
} else {
this.$relatedNotesMoreNotes.hide();
}

View File

@@ -175,7 +175,7 @@ const editorConfig = {
toolbar: {
items: []
},
placeholder: "Type the labels and relations here",
placeholder: t("attribute_editor.placeholder"),
mention: mentionSetup
};
@@ -347,8 +347,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
this.$editor.on("click", e => this.handleEditorClick(e));
/** @property {BalloonEditor} */
this.textEditor = await BalloonEditor.create(this.$editor[0], editorConfig);
this.textEditor = await CKEditor.BalloonEditor.create(this.$editor[0], editorConfig);
this.textEditor.model.document.on('change:data', () => this.dataChanged());
this.textEditor.editing.view.document.on('enter', (event, data) => {
// disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422
@@ -358,9 +357,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
// disable spellcheck for attribute editor
this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot()));
//await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector');
//CKEditorInspector.attach(this.textEditor);
}
dataChanged() {

View File

@@ -1,4 +1,5 @@
import Component from "../components/component.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import toastService from "../services/toast.js";
@@ -84,15 +85,8 @@ class BasicWidget extends Component {
render() {
try {
this.doRender();
} catch (e) {
toastService.showPersistent({
title: t("toast.widget-error.title"),
icon: "alert",
message: t("toast.widget-error.message", {
title: this.widgetTitle,
message: e.message
})
});
} catch (e) {
this.logRenderingError(e);
}
this.$widget.attr('data-component-id', this.componentId);
@@ -131,6 +125,35 @@ class BasicWidget extends Component {
return this.$widget;
}
logRenderingError(e) {
console.log("Got issue in widget ", this);
console.error(e);
let noteId = this._noteId;
if (this._noteId) {
froca.getNote(noteId, true).then((note) => {
toastService.showPersistent({
title: t("toast.widget-error.title"),
icon: "alert",
message: t("toast.widget-error.message-custom", {
id: noteId,
title: note.title,
message: e.message
})
});
});
return;
}
toastService.showPersistent({
title: t("toast.widget-error.title"),
icon: "alert",
message: t("toast.widget-error.message-unknown", {
message: e.message
})
});
}
/**
* Indicates if the widget is enabled. Widgets are enabled by default. Generally setting this to `false` will cause the widget not to be displayed, however it will still be available on the DOM but hidden.
* @returns

View File

@@ -15,7 +15,7 @@ const TPL = `
<td class="button-column">
<div style="display: flex">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('execute_script.help_text')}

View File

@@ -21,7 +21,7 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("add_label.help_text")}</p>
@@ -30,7 +30,7 @@ const TPL = `
<li>${t("add_label.help_text_item2")}</li>
</ul>
<p>${t("add_label.help_text_note")}</p>
${t("add_label.help_text_note")}
</div>
</div>

View File

@@ -21,11 +21,11 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("update_label_value.help_text")}</p>
<p>${t("update_label_value.help_text_note")}</p>
${t("update_label_value.help_text_note")}
</div>
</div>

View File

@@ -10,13 +10,13 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("delete_note.delete_matched_notes_description")}</p>
<p>${t("delete_note.undelete_notes_instruction")}</p>
<p>${t("delete_note.erase_notes_instruction")}</p>
${t("delete_note.erase_notes_instruction")}
</div>
</div>

View File

@@ -9,7 +9,7 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('delete_revisions.all_past_note_revisions')}
</div>

View File

@@ -18,11 +18,11 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('move_note.on_all_matched_notes')}:</p>
<ul>
<ul style="margin-bottom: 0;">
<li>${t('move_note.move_note_new_parent')}</li>
<li>${t('move_note.clone_note_new_parent')}</li>
<li>${t('move_note.nothing_will_happen')}</li>

View File

@@ -16,7 +16,7 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('rename_note.evaluated_as_js_string')}</p>

View File

@@ -25,9 +25,9 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('add_relation.create_relation_on_all_matched_notes')}</p>
${t('add_relation.create_relation_on_all_matched_notes')}
</div>
</div>

View File

@@ -25,11 +25,11 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('update_relation_target.on_all_matched_notes')}:</p>
<ul>
<ul style="margin-bottom: 0;">
<li>${t('update_relation_target.create_given_relation')}</li>
<li>${t('update_relation_target.change_target_note')}</li>
</ul>

View File

@@ -1,7 +1,7 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `<button class="button-widget bx"
data-toggle="tooltip"
data-bs-toggle="tooltip"
title=""></button>`;
export default class AbstractButtonWidget extends NoteContextAwareWidget {
@@ -22,10 +22,13 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.tooltip = new bootstrap.Tooltip(this.$widget, {
html: true, title: () => this.getTitle(), trigger: 'hover'
})
if (this.settings.onContextMenu) {
this.$widget.on("contextmenu", e => {
this.$widget.tooltip("hide");
this.tooltip.hide();
this.settings.onContextMenu(e);
@@ -35,12 +38,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
this.$widget.attr("data-placement", this.settings.titlePlacement);
this.$widget.tooltip({
html: true,
title: () => this.getTitle(),
trigger: "hover"
});
super.doRender();
}

View File

@@ -27,7 +27,7 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true"
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"
style="position: relative; top: 3px;"></button>
@@ -61,7 +61,8 @@ export default class AttachmentActionsWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input");
this.$uploadNewRevisionInput.on('change', async () => {
@@ -84,7 +85,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
.addClass("disabled")
.append($('<span class="disabled-tooltip"> (?)</span>')
.attr("title", t('attachments_actions.open_externally_detail_page'))
);
);
if (isElectron) {
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton
@@ -94,7 +95,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
);
}
}
if (!isElectron){
if (!isElectron) {
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton
.addClass("disabled")
@@ -138,7 +139,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
return;
}
const {note: newNote} = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
const { note: newNote } = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
toastService.showMessage(t('attachments_actions.convert_success', { title: this.attachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
@@ -155,6 +156,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
return;
}
await server.put(`attachments/${this.attachmentId}/rename`, {title: attachmentTitle});
await server.put(`attachments/${this.attachmentId}/rename`, { title: attachmentTitle });
}
}

View File

@@ -74,12 +74,12 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
doRender() {
super.doRender();
this.$month = this.$dropdownContent.find('[data-calendar-area="month"]');
this.$weekHeader = this.$dropdownContent.find(".calendar-week");
this.manageFirstDayOfWeek();
// Month navigation
this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]');
this.$monthSelect.on("input", (e) => {
@@ -88,10 +88,10 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
});
this.$next = this.$dropdownContent.find('[data-calendar-toggle="next"]');
this.$next.on('click', () => {
this.date.setMonth(this.date.getMonth() + 1);
this.date.setMonth(this.date.getMonth() + 1);
this.createMonth();
});
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
this.$previous.on('click', e => {
this.date.setMonth(this.date.getMonth() - 1);
this.createMonth();
@@ -108,7 +108,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.date.setFullYear(this.date.getFullYear() + 1);
this.createMonth();
});
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
this.$previousYear.on('click', e => {
this.date.setFullYear(this.date.getFullYear() - 1);
this.createMonth();
@@ -123,11 +123,13 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (note) {
appContext.tabManager.getActiveContext().setNote(note.noteId);
this.hideDropdown();
this.dropdown.hide();
}
else {
toastService.showError(t("calendar.cannot_find_day_note"));
}
ev.stopPropagation();
});
// Prevent dismissing the calendar popup by clicking on an empty space inside it.
@@ -138,9 +140,9 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.firstDayOfWeek = options.getInt("firstDayOfWeek");
// Generate the list of days of the week taking into consideration the user's selected first day of week.
let localeDaysOfWeek = [ ...DAYS_OF_WEEK ];
let localeDaysOfWeek = [...DAYS_OF_WEEK];
const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
localeDaysOfWeek = [ ...localeDaysOfWeek, ...daysToBeAddedAtEnd ];
localeDaysOfWeek = [...localeDaysOfWeek, ...daysToBeAddedAtEnd];
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`));
}
@@ -184,7 +186,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (dateNoteId) {
$newDay.addClass('calendar-date-exists');
$newDay.attr("href", `#root/${dateNoteId}`);
$newDay.attr("data-href", `#root/${dateNoteId}`);
}
if (this.isEqual(this.date, this.activeDate)) {
@@ -236,11 +238,11 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.$yearSelect.val(this.date.getFullYear());
}
async entitiesReloadedEvent({loadResults}) {
async entitiesReloadedEvent({ loadResults }) {
if (!loadResults.getOptionNames().includes("firstDayOfWeek")) {
return;
}
this.manageFirstDayOfWeek();
this.createMonth();
}

View File

@@ -11,7 +11,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget {
if (this.settings.command) {
this.$widget.on("click", () => {
this.$widget.tooltip("hide");
this.tooltip.hide();
this.triggerCommand(this._command);
});

View File

@@ -5,7 +5,7 @@ import UpdateAvailableWidget from "./update_available.js";
import options from "../../services/options.js";
const TPL = `
<div class="dropdown global-menu dropright">
<div class="dropdown global-menu dropend">
<style>
.global-menu {
width: 53px;
@@ -70,6 +70,7 @@ const TPL = `
background-color: var(--button-background-color);
padding: 3px;
margin-left: 3px;
text-decoration: none;
}
.global-menu .zoom-buttons a:hover {
@@ -102,10 +103,9 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" data-placement="right"
aria-haspopup="true" aria-expanded="false"
class="icon-action global-menu-button" title="${t('global_menu.menu')}">
<svg viewBox="0 0 256 256">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action global-menu-button">
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}">
<g>
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
@@ -126,7 +126,7 @@ const TPL = `
<ul class="dropdown-menu dropdown-menu-right">
<li class="dropdown-item" data-trigger-command="showOptions">
<span class="bx bx-slider"></span>
<span class="bx bx-cog"></span>
${t('global_menu.options')}
</li>
@@ -177,7 +177,7 @@ const TPL = `
<li class="dropdown-item dropdown-submenu">
<span class="dropdown-toggle">
<span class="bx bx-empty"></span>
<span class="bx bx-chip"></span>
${t('global_menu.advanced')}
</span>
@@ -195,43 +195,43 @@ const TPL = `
</li>
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
<span class="bx bx-empty"></span>
<span class="bx bx-data"></span>
${t('global_menu.open_sql_console_history')}
</li>
<li class="dropdown-item" data-trigger-command="showSearchHistory">
<span class="bx bx-empty"></span>
<span class="bx bx-search-alt"></span>
${t('global_menu.open_search_history')}
</li>
<li class="dropdown-item" data-trigger-command="showBackendLog">
<span class="bx bx-empty"></span>
<span class="bx bx-detail"></span>
${t('global_menu.show_backend_log')}
<kbd data-command="showBackendLog"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
title="${t('global_menu.reload_hint')}">
<span class="bx bx-empty"></span>
<span class="bx bx-refresh"></span>
${t('global_menu.reload_frontend')}
<kbd data-command="reloadFrontendApp"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showHiddenSubtree">
<span class="bx bx-empty"></span>
<span class="bx bx-hide"></span>
${t('global_menu.show_hidden_subtree')}
</li>
</ul>
</li>
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
<span class="bx bx-info-circle"></span>
<span class="bx bx-help-circle"></span>
${t('global_menu.show_help')}
<kbd data-command="showHelp"></kbd>
</li>
<li class="dropdown-item show-about-dialog-button">
<span class="bx bx-empty"></span>
<span class="bx bx-info-circle"></span>
${t('global_menu.about')}
</li>
@@ -259,10 +259,9 @@ export default class GlobalMenuWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$dropdown = this.$widget.find("[data-toggle='dropdown']");
const $button = this.$widget.find(".global-menu-button");
$button.tooltip({ trigger: "hover" });
$button.on("click", () => $button.tooltip("hide"));
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" });
this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog"));
@@ -278,8 +277,13 @@ export default class GlobalMenuWidget extends BasicWidget {
return;
}
this.$dropdown.dropdown('toggle');
this.dropdown.toggle();
});
this.$widget.on('click', '.dropdown-submenu', e => {
if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass('dropdown-toggle')) {
e.stopPropagation();
}
})
this.$widget.find(".global-menu-button-update-available").append(
this.updateAvailableWidget.render()
@@ -292,7 +296,12 @@ export default class GlobalMenuWidget extends BasicWidget {
}
this.$zoomState = this.$widget.find(".zoom-state");
this.$widget.on('show.bs.dropdown', () => this.updateZoomState());
this.$widget.on('show.bs.dropdown', () => {
this.updateZoomState();
this.tooltip.hide();
this.tooltip.disable();
});
this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable());
this.$widget.find(".zoom-buttons").on("click",
// delay to wait for the actual zoom change
@@ -324,7 +333,8 @@ export default class GlobalMenuWidget extends BasicWidget {
const latestVersion = await this.fetchLatestVersion();
this.updateAvailableWidget.updateVersionStatus(latestVersion);
this.$updateToLatestVersionButton.toggle(latestVersion > glob.triliumVersion);
// Show "click to download" button in options menu if there's a new version available
this.$updateToLatestVersionButton.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
this.$updateToLatestVersionButton.find(".version-text").text(`Version ${latestVersion} is available, click to download.`);
}
@@ -342,10 +352,10 @@ export default class GlobalMenuWidget extends BasicWidget {
}
activeContextChangedEvent() {
this.$dropdown.dropdown('hide');
this.dropdown.hide();
}
noteSwitchedEvent() {
this.$dropdown.dropdown('hide');
this.dropdown.hide();
}
}

View File

@@ -17,7 +17,7 @@ const TPL = `
}
.note-actions .dropdown-menu {
width: 15em;
min-width: 15em;
}
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
@@ -27,7 +27,7 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true"
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
<div class="dropdown-menu dropdown-menu-right">
@@ -82,7 +82,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.$importNoteButton = this.$widget.find('.import-files-button');
this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId}));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown('toggle'));
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");
@@ -127,18 +127,18 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
}
async convertNoteIntoAttachmentCommand() {
if (!await dialogService.confirm(`Are you sure you want to convert note '${this.note.title}' into an attachment of the parent note?`)) {
if (!await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title }))) {
return;
}
const {attachment: newAttachment} = await server.post(`notes/${this.noteId}/convert-to-attachment`);
if (!newAttachment) {
toastService.showMessage(`Converting note '${this.note.title}' failed.`);
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
return;
}
toastService.showMessage(`Note '${newAttachment.title}' has been converted to attachment.`);
toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newAttachment.ownerId, {
viewScope: {

View File

@@ -1,17 +1,19 @@
import BasicWidget from "../basic_widget.js";
const TPL = `
<div class="dropdown right-dropdown-widget dropright">
<div class="dropdown right-dropdown-widget dropend">
<style>
.right-dropdown-widget {
height: 53px;
}
</style>
<button type="button" data-toggle="dropdown" data-placement="right"
<button type="button" data-bs-toggle="dropdown" data-placement="right"
aria-haspopup="true" aria-expanded="false"
class="bx right-dropdown-button launcher-button"></button>
<div class="tooltip-trigger"></div>
<div class="dropdown-menu dropdown-menu-right"></div>
</div>
`;
@@ -28,12 +30,16 @@ export default class RightDropdownButtonWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$dropdownMenu = this.$widget.find(".dropdown-menu");
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
const $button = this.$widget.find(".right-dropdown-button")
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
this.tooltip = new bootstrap.Tooltip(this.$tooltip);
this.$widget.find(".right-dropdown-button")
.addClass(this.iconClass)
.attr("title", this.title)
.tooltip({ trigger: "hover" })
.on("click", () => $button.tooltip("hide"));
.on("click", () => this.tooltip.hide())
.on('mouseenter', () => this.tooltip.show())
.on('mouseleave', () => this.tooltip.hide());
this.$widget.on('show.bs.dropdown', async () => {
await this.dropdownShown();
@@ -51,10 +57,5 @@ export default class RightDropdownButtonWidget extends BasicWidget {
}
// to be overridden
async dropdownShow() {}
hideDropdown() {
this.$widget.dropdown("hide");
this.$dropdownMenu.removeClass("show");
}
async dropdownShow() { }
}

View File

@@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
export default class ShowHighlightsListWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}
constructor() {
super();
this.icon("bx-highlight")
.title(t("show_highlights_list_widget_button.show_highlights_list"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.highlightsListTemporarilyHidden = false;
appContext.triggerEvent("showHighlightsListWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}
async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.highlightsListTemporarilyHidden);
}
async reEvaluateHighlightsListWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'hideHighlightWidget')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
export default class ShowTocWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}
constructor() {
super();
this.icon("bx-objects-horizontal-left")
.title(t("show_toc_widget_button.show_toc"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.tocTemporarilyHidden = false;
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}
async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
}
async reEvaluateTocWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'toc')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@@ -1,5 +1,6 @@
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import utils from "../../services/utils.js";
const TPL = `
<div style="display: none;">
@@ -34,6 +35,6 @@ export default class UpdateAvailableWidget extends BasicWidget {
}
updateVersionStatus(latestVersion) {
this.$widget.toggle(latestVersion > glob.triliumVersion);
this.$widget.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
}
}

View File

@@ -8,7 +8,11 @@ export default class Container extends BasicWidget {
renderChildren() {
for (const widget of this.children) {
this.$widget.append(widget.render());
try {
this.$widget.append(widget.render());
} catch (e) {
widget.logRenderingError(e);
}
}
}
}

View File

@@ -216,7 +216,7 @@ export default class RibbonContainer extends NoteContextAwareWidget {
this.$tabContainer.empty();
for (const ribbonWidget of this.ribbonWidgets) {
const ret = ribbonWidget.getTitle(note);
const ret = await ribbonWidget.getTitle(note);
if (!ret.show) {
continue;
@@ -351,6 +351,16 @@ export default class RibbonContainer extends NoteContextAwareWidget {
}
}
/**
* Executed as soon as the user presses the "Edit" floating button in a read-only text note.
*
* <p>
* We need to refresh the ribbon for cases such as the classic editor which relies on the read-only state.
*/
readOnlyTemporarilyDisabledEvent() {
this.refresh();
}
getActiveRibbonWidget() {
return this.ribbonWidgets.find(ch => ch.componentId === this.lastActiveComponentId)
}

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